diff --git a/android/app/build.gradle.kts b/android/app/build.gradle.kts index e852236b..81543587 100644 --- a/android/app/build.gradle.kts +++ b/android/app/build.gradle.kts @@ -34,7 +34,6 @@ android { defaultConfig { applicationId = "com.cloudsolutions.HMGPatientApp" // minSdk = 24 -// minSdk = 26 minSdk = 29 targetSdk = 35 compileSdk = 36 diff --git a/android/app/src/main/AndroidManifest.xml b/android/app/src/main/AndroidManifest.xml index cd5030d2..0e1749d6 100644 --- a/android/app/src/main/AndroidManifest.xml +++ b/android/app/src/main/AndroidManifest.xml @@ -41,15 +41,20 @@ tools:node="remove" /> - - - - + + + + + + + + + @@ -88,11 +93,8 @@ - - - diff --git a/android/app/src/main2/AndroidManifest.xml b/android/app/src/main2/AndroidManifest.xml index e07739b9..535bd4ab 100644 --- a/android/app/src/main2/AndroidManifest.xml +++ b/android/app/src/main2/AndroidManifest.xml @@ -45,6 +45,7 @@ + - diff --git a/assets/animations/lottie/HourGlass.json b/assets/animations/lottie/HourGlass.json new file mode 100644 index 00000000..d0c87fd3 --- /dev/null +++ b/assets/animations/lottie/HourGlass.json @@ -0,0 +1 @@ +{"nm":"Hourglass","h":500,"w":500,"meta":{"g":"LottieFiles AE 1.1.0"},"layers":[{"ty":4,"nm":"Side 2 Matte","sr":1,"st":-25,"op":76,"ip":0,"hasMask":false,"td":1,"ao":0,"ks":{"a":{"a":0,"k":[-6552.114,-5982.041,0]},"s":{"a":0,"k":[100,100,100]},"sk":{"a":0,"k":0},"p":{"a":0,"k":[-6552,-6594.232,0]},"r":{"a":0,"k":0},"sa":{"a":0,"k":0},"o":{"a":0,"k":100}},"ef":[],"shapes":[{"ty":"gr","nm":"Group 1","it":[{"ty":"sh","nm":"Path 1","d":1,"ks":{"a":0,"k":{"c":true,"i":[[0,0],[0,0],[0,0],[-2.437,0],[0,0]],"o":[[0,0],[0,0],[0,0],[2.437,0],[0,0]],"v":[[-6469.931,-6116.998],[-6634.298,-6117.35],[-6555.846,-5981.885],[-6552.176,-5979.025],[-6548.382,-5982.198]]}}},{"ty":"fl","nm":"Fill 1","c":{"a":0,"k":[0.18,0.188,0.224]},"r":1,"o":{"a":0,"k":100}},{"ty":"tr","a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"sk":{"a":0,"k":0},"p":{"a":0,"k":[0,0]},"r":{"a":0,"k":0},"sa":{"a":0,"k":0},"o":{"a":0,"k":100}}]}],"ind":1,"parent":7},{"ty":4,"nm":"Sand 2","sr":1,"st":-25,"op":76,"ip":0,"tt":1,"hasMask":false,"ao":0,"ks":{"a":{"a":0,"k":[0,0,0]},"s":{"a":0,"k":[100,100,100]},"sk":{"a":0,"k":0},"p":{"a":0,"k":[-6552.114,-5982.041,0]},"r":{"a":0,"k":0},"sa":{"a":0,"k":0},"o":{"a":0,"k":100}},"ef":[],"shapes":[{"ty":"gr","nm":"Shape 1","it":[{"ty":"sh","nm":"Path 1","d":1,"ks":{"a":1,"k":[{"o":{"x":0.167,"y":0.167},"i":{"x":0.833,"y":0.833},"s":[{"c":true,"i":[[0.486,0.846],[0.625,-1.344],[-0.28,-0.728],[0.057,-0.656],[-0.144,-3.463],[0,0],[-0.063,3.469],[-0.062,1.125],[-0.268,0.626]],"o":[[-0.844,-1.469],[-0.34,0.73],[0.235,0.612],[-0.125,1.438],[0.144,3.463],[0,0],[0.063,-3.469],[0.026,-0.477],[0.364,-0.851]],"v":[[1.781,1.656],[-1.906,1.969],[-1.715,4.139],[-1.281,6.031],[-3.988,12.194],[-1.25,23],[5.062,8.062],[1,5.969],[1.59,4.283]]}],"t":0},{"o":{"x":0.167,"y":0.167},"i":{"x":0.833,"y":0.833},"s":[{"c":true,"i":[[0,0],[0,0],[2.534,0.174],[-4.393,-4.956],[4.426,-20.319],[0,0],[4.269,23.889],[-9.5,9],[-0.283,0.932]],"o":[[0,0],[0,0],[-2.13,-0.147],[9.75,11],[-4.333,19.892],[0,0],[-5.875,-32.875],[3.487,-3.304],[0.384,-1.268]],"v":[[14,-138.75],[-3.5,-136.5],[-10.497,-138.771],[-9,-132.75],[-3.676,2.319],[-1.25,23],[3.375,1.375],[10,-131.75],[15.02,-137.747]]}],"t":9},{"o":{"x":0.167,"y":0.167},"i":{"x":0.833,"y":0.833},"s":[{"c":true,"i":[[0,0],[0,0],[-20.182,-2.387],[-4.393,-4.956],[4.426,-20.319],[0,0],[4.269,23.889],[-9.5,9],[-15.597,1.657]],"o":[[0,0],[0,0],[16.964,2.006],[9.75,11],[-4.333,19.892],[0,0],[-5.875,-32.875],[3.487,-3.304],[21.209,-2.253]],"v":[[91.5,-136],[-89.75,-137.5],[-49.161,-134.405],[-11,-124.25],[-3.676,2.319],[-1.25,23],[3.375,1.375],[13.5,-125.5],[47.123,-132.85]]}],"t":25},{"o":{"x":0.167,"y":0.167},"i":{"x":0.833,"y":0.833},"s":[{"c":true,"i":[[0,0],[0,0],[-21.104,-9.934],[-6.5,-8],[0,-21.193],[0,0],[0,24.267],[-7.401,10.793],[-19.85,6.269]],"o":[[0,0],[0,0],[21.691,10.211],[9.269,11.408],[0,20.359],[0,0],[0,-31.5],[6,-8.75],[22.388,-7.071]],"v":[[91.5,-136],[-89.75,-137.5],[-72.691,-114.461],[-9.25,-108.5],[-3.301,0.194],[-1.25,23],[2.75,-0.25],[13,-106.75],[64.35,-110.519]]}],"t":36},{"o":{"x":0.167,"y":0.167},"i":{"x":0.833,"y":0.833},"s":[{"c":true,"i":[[0,0],[0,0],[-22.88,-4.534],[-1.313,-0.25],[-1.613,-0.182],[-1.375,-0.062],[-1.75,0.062],[-1.937,0.188],[-19.225,4.769]],"o":[[0,0],[0,0],[14.941,2.961],[1.312,0.25],[1.613,0.182],[1.375,0.062],[1.75,-0.062],[1.937,-0.188],[22.788,-5.653]],"v":[[91.5,-136],[-89.75,-137.5],[-64.441,-101.961],[-11.188,-97],[-4.863,-96.557],[-0.75,-96.5],[3.75,-96.5],[10.188,-96.625],[64.1,-102.019]]}],"t":50},{"s":[{"c":true,"i":[[14.5,-2],[25,7],[-12.809,-15.539],[-1.313,-0.25],[-1.613,-0.182],[-1.375,-0.062],[-1.75,0.062],[-1.937,0.188],[-6.715,12.881]],"o":[[-18.589,2.564],[-23.131,-6.477],[9.688,11.753],[1.312,0.25],[1.613,0.182],[1.375,0.062],[1.75,-0.062],[1.937,-0.188],[3.9,-7.481]],"v":[[38.5,-89],[-32.75,-76.5],[-45.191,-56.711],[-10.188,-2.5],[-4.613,0.193],[-0.25,0.5],[3.25,0.25],[9.938,-0.875],[57.6,-77.519]]}],"t":75}]}},{"ty":"st","nm":"Stroke 1","lc":1,"lj":1,"ml":4,"o":{"a":0,"k":100},"w":{"a":0,"k":0},"c":{"a":0,"k":[0,0.58,0.616]}},{"ty":"fl","nm":"Fill 1","c":{"a":0,"k":[0.18,0.188,0.224]},"r":1,"o":{"a":0,"k":100}},{"ty":"tr","a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"sk":{"a":0,"k":0},"p":{"a":0,"k":[0,0]},"r":{"a":0,"k":0},"sa":{"a":0,"k":0},"o":{"a":0,"k":100}}]}],"ind":2,"parent":1},{"ty":4,"nm":"Side 1 Matte","sr":1,"st":-25,"op":76,"ip":0,"hasMask":false,"td":1,"ao":0,"ks":{"a":{"a":0,"k":[-6552.114,-5982.041,0]},"s":{"a":0,"k":[100,100,100]},"sk":{"a":0,"k":0},"p":{"a":0,"k":[250,250,0]},"r":{"a":0,"k":0},"sa":{"a":0,"k":0},"o":{"a":0,"k":100}},"ef":[],"shapes":[{"ty":"gr","nm":"Group 1","it":[{"ty":"sh","nm":"Path 1","d":1,"ks":{"a":0,"k":{"c":true,"i":[[0,0],[0,0],[0,0],[-0.99,-0.125],[0,0]],"o":[[0,0],[0,0],[0,0],[0.99,0.125],[0,0]],"v":[[-6469.931,-6116.998],[-6634.298,-6117.35],[-6555.846,-5981.885],[-6552.229,-5980.042],[-6548.382,-5982.198]]}}},{"ty":"fl","nm":"Fill 1","c":{"a":0,"k":[0.18,0.188,0.224]},"r":1,"o":{"a":0,"k":100}},{"ty":"tr","a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"sk":{"a":0,"k":0},"p":{"a":0,"k":[0,0]},"r":{"a":0,"k":0},"sa":{"a":0,"k":0},"o":{"a":0,"k":100}}]}],"ind":3},{"ty":4,"nm":"Sand 1","sr":1,"st":-25,"op":76,"ip":0,"tt":1,"hasMask":false,"ao":0,"ks":{"a":{"a":0,"k":[0,0,0]},"s":{"a":0,"k":[100,100,100]},"sk":{"a":0,"k":0},"p":{"a":1,"k":[{"o":{"x":0.167,"y":0.167},"i":{"x":0.4,"y":1},"s":[250,250,0],"t":0,"ti":[0,0,0],"to":[0,0,0]},{"s":[250,331,0],"t":50}]},"r":{"a":0,"k":0},"sa":{"a":0,"k":0},"o":{"a":0,"k":100}},"ef":[],"shapes":[{"ty":"gr","nm":"Shape 1","it":[{"ty":"sh","nm":"Path 1","d":1,"ks":{"a":1,"k":[{"o":{"x":0.167,"y":0.167},"i":{"x":0.833,"y":0.833},"s":[{"c":true,"i":[[14.5,-2],[9.627,-3.183],[8.064,-1.099],[7.079,1.982],[-12.809,-15.539],[-1.156,-1.094],[-0.582,-0.244],[-0.656,0],[-0.719,0.281],[-1.219,1.562],[-6.715,12.881]],"o":[[-7.462,1.029],[-7.564,2.501],[-7.239,0.986],[-23.131,-6.477],[9.688,11.753],[1.156,1.094],[0.582,0.244],[0.656,0],[0.719,-0.281],[1.219,-1.562],[3.9,-7.481]],"v":[[38.5,-89],[12.486,-81.442],[-11.141,-75.441],[-32.75,-76.5],[-45.191,-56.711],[-10.188,-2.5],[-3.019,2.287],[0.094,2.719],[2.75,2.344],[9.938,-0.875],[57.6,-77.519]]}],"t":0},{"o":{"x":0.167,"y":0.167},"i":{"x":0.833,"y":0.833},"s":[{"c":true,"i":[[14.5,-2],[2.585,2.626],[5.445,-3.528],[6.397,1.791],[-12.809,-15.539],[-1.156,-1.094],[-0.582,-0.244],[-0.656,0],[-0.719,0.281],[-1.219,1.562],[-6.715,12.881]],"o":[[-8.027,1.107],[-5.58,-5.669],[-5.652,3.663],[-23.131,-6.477],[9.688,11.753],[1.156,1.094],[0.582,0.244],[0.656,0],[0.719,-0.281],[1.219,-1.562],[3.9,-7.481]],"v":[[38.5,-89],[8.79,-71.474],[-8.07,-72.57],[-32.75,-76.5],[-45.191,-56.711],[-10.188,-2.5],[-3.019,2.287],[0.094,2.719],[2.75,2.344],[9.938,-0.875],[57.6,-77.519]]}],"t":39},{"s":[{"c":true,"i":[[14.5,-2],[9.627,-3.183],[8.064,-1.099],[7.079,1.982],[-12.809,-15.539],[-1.156,-1.094],[-0.582,-0.244],[-0.656,0],[-0.719,0.281],[-1.219,1.562],[-6.715,12.881]],"o":[[-7.462,1.029],[-7.564,2.501],[-7.239,0.986],[-23.131,-6.477],[9.688,11.753],[1.156,1.094],[0.582,0.244],[0.656,0],[0.719,-0.281],[1.219,-1.562],[3.9,-7.481]],"v":[[38.5,-89],[12.486,-81.442],[-11.141,-75.441],[-32.75,-76.5],[-45.191,-56.711],[-10.188,-2.5],[-3.019,2.287],[0.094,2.719],[2.75,2.344],[9.938,-0.875],[57.6,-77.519]]}],"t":45}]}},{"ty":"st","nm":"Stroke 1","lc":1,"lj":1,"ml":4,"o":{"a":0,"k":100},"w":{"a":0,"k":0},"c":{"a":0,"k":[0,0.58,0.616]}},{"ty":"fl","nm":"Fill 1","c":{"a":0,"k":[0.18,0.188,0.224]},"r":1,"o":{"a":0,"k":100}},{"ty":"tr","a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"sk":{"a":0,"k":0},"p":{"a":0,"k":[0,0]},"r":{"a":0,"k":0},"sa":{"a":0,"k":0},"o":{"a":0,"k":100}}]}],"ind":4},{"ty":4,"nm":"Shadow Matte","sr":1,"st":-25,"op":76,"ip":0,"hasMask":false,"td":1,"ao":0,"ks":{"a":{"a":0,"k":[-6552,-6594.232,0]},"s":{"a":0,"k":[100,100,100]},"sk":{"a":0,"k":0},"p":{"a":0,"k":[-6552,-6594.232,0]},"r":{"a":0,"k":0},"sa":{"a":0,"k":0},"o":{"a":0,"k":100}},"ef":[],"shapes":[{"ty":"gr","nm":"Group 3","it":[{"ty":"sh","nm":"Path 1","d":1,"ks":{"a":0,"k":{"c":true,"i":[[8.207,0],[0,0],[-1.738,-7.444],[-0.605,-1.021],[0,0],[-0.214,-1.589],[0.143,-1.057],[0.823,-1.388],[0,0],[0.269,-1.149],[-8.207,0],[0,0],[1.738,7.444],[0.605,1.021],[0,0],[0.214,1.589],[-0.143,1.057],[-0.823,1.388],[0,0],[-0.269,1.149]],"o":[[0,0],[-8.207,0],[0.269,1.149],[0,0],[0.823,1.388],[0.143,1.057],[-0.214,1.589],[0,0],[-0.605,1.021],[-1.738,7.444],[0,0],[8.207,0],[-0.269,-1.149],[0,0],[-0.823,-1.388],[-0.143,-1.057],[0.214,-1.589],[0,0],[0.605,-1.021],[1.738,-7.444]],"v":[[-6458.712,-6744.95],[-6645.288,-6744.95],[-6657.545,-6730.055],[-6656.154,-6726.804],[-6581.067,-6600.158],[-6579.392,-6595.673],[-6579.392,-6592.49],[-6581.067,-6588.004],[-6656.154,-6461.358],[-6657.545,-6458.107],[-6645.288,-6443.213],[-6458.712,-6443.213],[-6446.455,-6458.107],[-6447.846,-6461.358],[-6522.933,-6588.004],[-6524.608,-6592.49],[-6524.608,-6595.673],[-6522.933,-6600.158],[-6447.846,-6726.804],[-6446.455,-6730.055]]}}},{"ty":"fl","nm":"Fill 1","c":{"a":0,"k":[1,1,1]},"r":1,"o":{"a":0,"k":100}},{"ty":"tr","a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"sk":{"a":0,"k":0},"p":{"a":0,"k":[0,0]},"r":{"a":0,"k":0},"sa":{"a":0,"k":0},"o":{"a":0,"k":100}}]}],"ind":5,"parent":7},{"ty":4,"nm":"Shadow","sr":1,"st":-25,"op":76,"ip":0,"tt":1,"hasMask":false,"ao":0,"ks":{"a":{"a":0,"k":[0,0,0]},"s":{"a":0,"k":[100,100,100]},"sk":{"a":0,"k":0},"p":{"a":0,"k":[250,250,0]},"r":{"a":0,"k":0},"sa":{"a":0,"k":0},"o":{"a":0,"k":10}},"ef":[],"shapes":[{"ty":"gr","nm":"Shape 1","it":[{"ty":"sh","nm":"Path 1","d":1,"ks":{"a":1,"k":[{"o":{"x":0.6,"y":0},"i":{"x":0.833,"y":0.833},"s":[{"c":true,"i":[[0,0],[49.135,-3.183],[-13.025,-0.599],[-29.109,-5.019],[13.5,-22],[-0.77,-28.49],[29.438,-20.455],[28.507,-1.289],[-25.5,-5.5],[0,0]],"o":[[0,0],[-12.896,0.835],[43.5,2],[29,5],[-13.5,22],[1,37],[-21.22,14.745],[-24.059,1.087],[25.5,5.5],[0,0]],"v":[[229.5,-239],[-92.882,-163.672],[-71.5,-153],[41,-141.5],[66.5,-106],[13,1],[57,123],[-37.5,133.501],[-42.5,152],[238,239.5]]}],"t":50},{"o":{"x":0.167,"y":0.167},"i":{"x":0.833,"y":0.833},"s":[{"c":true,"i":[[0,0],[48.989,-4.946],[-18.192,-25.462],[-22.077,-19.192],[7.048,-23.757],[2.816,-27.802],[1.115,-25.846],[53.077,0.154],[-25.5,-5.5],[0,0]],"o":[[0,0],[-46.272,4.672],[18.192,25.462],[22.077,19.192],[-7.039,24.308],[-3.749,36.459],[-1.115,25.846],[-53.077,-0.154],[25.5,5.5],[0,0]],"v":[[237.269,-237.077],[115.272,-238.672],[25.808,-162.462],[78.923,-108.192],[124.885,-41.538],[37.692,15.808],[61.615,97.154],[1.577,168.654],[-49.038,228.923],[241.615,239.193]]}],"t":57},{"o":{"x":0.167,"y":0.167},"i":{"x":0.833,"y":0.833},"s":[{"c":true,"i":[[0,0],[49.135,-3.183],[-39.5,-8],[-27.395,11.046],[-3.726,-32.793],[13,-36],[36,18],[28.507,-1.289],[-25.5,-5.5],[0,0]],"o":[[0,0],[-12.896,0.835],[39.5,8],[31,-12.5],[2.5,22],[-11.892,32.933],[-23.112,-11.556],[-24.059,1.088],[25.5,5.5],[0,0]],"v":[[220.5,-166.5],[-18.882,-189.672],[10.5,-128.5],[117,-126],[138.5,-50],[130,54],[81,152],[-12.5,143],[-12,233],[217,157]]}],"t":63},{"o":{"x":0.167,"y":0.167},"i":{"x":0.4,"y":1},"s":[{"c":true,"i":[[0,0],[49.135,-3.183],[-16.242,-32.949],[-16.167,-13.833],[-28.167,-13.5],[-3.833,-25.667],[28.167,-23.833],[28.337,-16.478],[-25.5,-5.5],[0,0]],"o":[[0,0],[-12.896,0.835],[23.333,47.333],[16.167,13.833],[28.167,13.5],[4.387,24.133],[-28.167,23.833],[-20.032,9.541],[25.5,5.5],[0,0]],"v":[[222.833,-223.333],[-65.548,-175.506],[-15.333,-124.333],[29.833,-53.833],[75.833,-31.5],[138.167,21.833],[78.167,128.167],[2.667,206.334],[-43.833,233.5],[235.333,241.5]]}],"t":68},{"s":[{"c":true,"i":[[0,0],[49.135,-3.183],[-13.025,-0.599],[-29.109,-5.019],[13.5,-22],[-0.77,-28.49],[29.438,-20.455],[28.507,-1.289],[-25.5,-5.5],[0,0]],"o":[[0,0],[-12.896,0.835],[43.5,2],[29,5],[-13.5,22],[1,37],[-21.22,14.745],[-24.059,1.087],[25.5,5.5],[0,0]],"v":[[229.5,-239],[-92.882,-163.672],[-71.5,-153],[41,-141.5],[66.5,-106],[13,1],[57,123],[-37.5,133.501],[-42.5,152],[238,239.5]]}],"t":75}]}},{"ty":"st","nm":"Stroke 1","lc":1,"lj":1,"ml":4,"o":{"a":0,"k":100},"w":{"a":0,"k":0},"c":{"a":0,"k":[0,0.58,0.616]}},{"ty":"fl","nm":"Fill 1","c":{"a":0,"k":[0.18,0.188,0.224]},"r":1,"o":{"a":0,"k":100}},{"ty":"tr","a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"sk":{"a":0,"k":0},"p":{"a":0,"k":[0,0]},"r":{"a":0,"k":0},"sa":{"a":0,"k":0},"o":{"a":0,"k":100}}]}],"ind":6},{"ty":4,"nm":"Main Hourglass","sr":1,"st":-25,"op":76,"ip":0,"hasMask":false,"ao":0,"ks":{"a":{"a":0,"k":[-6552,-6594.232,0]},"s":{"a":0,"k":[100,100,100]},"sk":{"a":0,"k":0},"p":{"a":0,"k":[250,250,0]},"r":{"a":1,"k":[{"o":{"x":0.6,"y":0},"i":{"x":0.4,"y":1},"s":[0],"t":-25},{"o":{"x":0.6,"y":0},"i":{"x":0.4,"y":1},"s":[180],"t":0},{"o":{"x":0.6,"y":0},"i":{"x":0.4,"y":1},"s":[180],"t":50},{"s":[360],"t":75}]},"sa":{"a":0,"k":0},"o":{"a":0,"k":100}},"ef":[],"shapes":[{"ty":"gr","nm":"Group 1","it":[{"ty":"sh","nm":"Path 1","d":1,"ks":{"a":0,"k":{"c":true,"i":[[3.931,0],[0,0],[0,3.931],[0,0],[-3.931,0],[0,0],[0,-3.931],[0,0]],"o":[[0,0],[-3.931,0],[0,0],[0,-3.931],[0,0],[3.931,0],[0,0],[0,3.931]],"v":[[-6436.617,-6754.998],[-6667.383,-6754.998],[-6674.5,-6762.115],[-6674.5,-6762.115],[-6667.383,-6769.232],[-6436.617,-6769.232],[-6429.5,-6762.115],[-6429.5,-6762.115]]}}},{"ty":"fl","nm":"Fill 1","c":{"a":0,"k":[0.18,0.188,0.224]},"r":1,"o":{"a":0,"k":100}},{"ty":"tr","a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"sk":{"a":0,"k":0},"p":{"a":0,"k":[0,0]},"r":{"a":0,"k":0},"sa":{"a":0,"k":0},"o":{"a":0,"k":100}}]},{"ty":"gr","nm":"Group 2","it":[{"ty":"sh","nm":"Path 1","d":1,"ks":{"a":0,"k":{"c":true,"i":[[3.931,0],[0,0],[0,3.931],[0,0],[-3.931,0],[0,0],[0,-3.931],[0,0]],"o":[[0,0],[-3.931,0],[0,0],[0,-3.931],[0,0],[3.931,0],[0,0],[0,3.931]],"v":[[-6436.617,-6419.232],[-6667.383,-6419.232],[-6674.5,-6426.35],[-6674.5,-6426.35],[-6667.383,-6433.467],[-6436.617,-6433.467],[-6429.5,-6426.35],[-6429.5,-6426.35]]}}},{"ty":"fl","nm":"Fill 1","c":{"a":0,"k":[0.18,0.188,0.224]},"r":1,"o":{"a":0,"k":100}},{"ty":"tr","a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"sk":{"a":0,"k":0},"p":{"a":0,"k":[0,0]},"r":{"a":0,"k":0},"sa":{"a":0,"k":0},"o":{"a":0,"k":100}}]},{"ty":"gr","nm":"Group 3","it":[{"ty":"sh","nm":"Path 1","d":1,"ks":{"a":0,"k":{"c":true,"i":[[8.207,0],[0,0],[-1.738,-7.444],[-0.605,-1.021],[0,0],[-0.214,-1.589],[0.143,-1.057],[0.823,-1.388],[0,0],[0.269,-1.149],[-8.207,0],[0,0],[1.738,7.444],[0.605,1.021],[0,0],[0.214,1.589],[-0.143,1.057],[-0.823,1.388],[0,0],[-0.269,1.149]],"o":[[0,0],[-8.207,0],[0.269,1.149],[0,0],[0.823,1.388],[0.143,1.057],[-0.214,1.589],[0,0],[-0.605,1.021],[-1.738,7.444],[0,0],[8.207,0],[-0.269,-1.149],[0,0],[-0.823,-1.388],[-0.143,-1.057],[0.214,-1.589],[0,0],[0.605,-1.021],[1.738,-7.444]],"v":[[-6458.712,-6744.95],[-6645.288,-6744.95],[-6657.545,-6730.055],[-6656.154,-6726.804],[-6581.067,-6600.158],[-6579.392,-6595.673],[-6579.392,-6592.49],[-6581.067,-6588.004],[-6656.154,-6461.358],[-6657.545,-6458.107],[-6645.288,-6443.213],[-6458.712,-6443.213],[-6446.455,-6458.107],[-6447.846,-6461.358],[-6522.933,-6588.004],[-6524.608,-6592.49],[-6524.608,-6595.673],[-6522.933,-6600.158],[-6447.846,-6726.804],[-6446.455,-6730.055]]}}},{"ty":"fl","nm":"Fill 1","c":{"a":0,"k":[1,1,1]},"r":1,"o":{"a":0,"k":100}},{"ty":"tr","a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"sk":{"a":0,"k":0},"p":{"a":0,"k":[0,0]},"r":{"a":0,"k":0},"sa":{"a":0,"k":0},"o":{"a":0,"k":100}}]}],"ind":7}],"v":"4.8.0","fr":25,"op":76,"ip":0,"assets":[]} \ No newline at end of file diff --git a/assets/fonts/cairo/Cairo-Bold.ttf b/assets/fonts/cairo/Cairo-Bold.ttf new file mode 100644 index 00000000..041abaf6 Binary files /dev/null and b/assets/fonts/cairo/Cairo-Bold.ttf differ diff --git a/assets/fonts/cairo/Cairo-Bold/Cairo-Bold.eot b/assets/fonts/cairo/Cairo-Bold/Cairo-Bold.eot new file mode 100644 index 00000000..ed5f0c34 Binary files /dev/null and b/assets/fonts/cairo/Cairo-Bold/Cairo-Bold.eot differ diff --git a/assets/fonts/cairo/Cairo-Bold/Cairo-Bold.otf b/assets/fonts/cairo/Cairo-Bold/Cairo-Bold.otf new file mode 100644 index 00000000..8bc25fbb Binary files /dev/null and b/assets/fonts/cairo/Cairo-Bold/Cairo-Bold.otf differ diff --git a/assets/fonts/cairo/Cairo-Bold/Cairo-Bold.ttf b/assets/fonts/cairo/Cairo-Bold/Cairo-Bold.ttf new file mode 100644 index 00000000..4f026897 Binary files /dev/null and b/assets/fonts/cairo/Cairo-Bold/Cairo-Bold.ttf differ diff --git a/assets/fonts/cairo/Cairo-Bold/Cairo-Bold.woff b/assets/fonts/cairo/Cairo-Bold/Cairo-Bold.woff new file mode 100644 index 00000000..91e3d063 Binary files /dev/null and b/assets/fonts/cairo/Cairo-Bold/Cairo-Bold.woff differ diff --git a/assets/fonts/cairo/Cairo-ExtraBold.ttf b/assets/fonts/cairo/Cairo-ExtraBold.ttf new file mode 100644 index 00000000..4fc547de Binary files /dev/null and b/assets/fonts/cairo/Cairo-ExtraBold.ttf differ diff --git a/assets/fonts/cairo/Cairo-Light.ttf b/assets/fonts/cairo/Cairo-Light.ttf new file mode 100644 index 00000000..7e0f9427 Binary files /dev/null and b/assets/fonts/cairo/Cairo-Light.ttf differ diff --git a/assets/fonts/cairo/Cairo-Light/Cairo-Light.eot b/assets/fonts/cairo/Cairo-Light/Cairo-Light.eot new file mode 100644 index 00000000..d76c5391 Binary files /dev/null and b/assets/fonts/cairo/Cairo-Light/Cairo-Light.eot differ diff --git a/assets/fonts/cairo/Cairo-Light/Cairo-Light.otf b/assets/fonts/cairo/Cairo-Light/Cairo-Light.otf new file mode 100644 index 00000000..f8812a9c Binary files /dev/null and b/assets/fonts/cairo/Cairo-Light/Cairo-Light.otf differ diff --git a/assets/fonts/cairo/Cairo-Light/Cairo-Light.ttf b/assets/fonts/cairo/Cairo-Light/Cairo-Light.ttf new file mode 100644 index 00000000..840cbc3e Binary files /dev/null and b/assets/fonts/cairo/Cairo-Light/Cairo-Light.ttf differ diff --git a/assets/fonts/cairo/Cairo-Light/Cairo-Light.woff b/assets/fonts/cairo/Cairo-Light/Cairo-Light.woff new file mode 100644 index 00000000..9534bb4b Binary files /dev/null and b/assets/fonts/cairo/Cairo-Light/Cairo-Light.woff differ diff --git a/assets/fonts/cairo/Cairo-Medium.ttf b/assets/fonts/cairo/Cairo-Medium.ttf new file mode 100644 index 00000000..5b8d0f70 Binary files /dev/null and b/assets/fonts/cairo/Cairo-Medium.ttf differ diff --git a/assets/fonts/cairo/Cairo-Regular.ttf b/assets/fonts/cairo/Cairo-Regular.ttf new file mode 100644 index 00000000..36ac46bf Binary files /dev/null and b/assets/fonts/cairo/Cairo-Regular.ttf differ diff --git a/assets/fonts/cairo/Cairo-Regular/Cairo-Regular.ttf b/assets/fonts/cairo/Cairo-Regular/Cairo-Regular.ttf new file mode 100644 index 00000000..3eb09f86 Binary files /dev/null and b/assets/fonts/cairo/Cairo-Regular/Cairo-Regular.ttf differ diff --git a/assets/fonts/cairo/Cairo-SemiBold.ttf b/assets/fonts/cairo/Cairo-SemiBold.ttf new file mode 100644 index 00000000..fd01d422 Binary files /dev/null and b/assets/fonts/cairo/Cairo-SemiBold.ttf differ diff --git a/assets/images/png/default_MEP.png b/assets/images/png/default_MEP.png new file mode 100644 index 00000000..d79b58d2 Binary files /dev/null and b/assets/images/png/default_MEP.png differ diff --git a/assets/images/png/full_body_back_female.png b/assets/images/png/full_body_back_female.png index 9b3aa74d..b1ea5cac 100644 Binary files a/assets/images/png/full_body_back_female.png and b/assets/images/png/full_body_back_female.png differ diff --git a/assets/images/png/full_body_back_male.png b/assets/images/png/full_body_back_male.png index 2eda5e8f..73a0b8b3 100644 Binary files a/assets/images/png/full_body_back_male.png and b/assets/images/png/full_body_back_male.png differ diff --git a/assets/images/png/full_body_front_female.png b/assets/images/png/full_body_front_female.png index c89608b0..68ce3738 100644 Binary files a/assets/images/png/full_body_front_female.png and b/assets/images/png/full_body_front_female.png differ diff --git a/assets/images/png/full_body_front_male.png b/assets/images/png/full_body_front_male.png index d1f34b4e..01e603a4 100644 Binary files a/assets/images/png/full_body_front_male.png and b/assets/images/png/full_body_front_male.png differ diff --git a/assets/images/svg/add_new_family_icon.svg b/assets/images/svg/add_new_family_icon.svg new file mode 100644 index 00000000..ad7ee356 --- /dev/null +++ b/assets/images/svg/add_new_family_icon.svg @@ -0,0 +1,6 @@ + + + + + + diff --git a/assets/images/svg/appointment_rating_icon.svg b/assets/images/svg/appointment_rating_icon.svg new file mode 100644 index 00000000..83d89feb --- /dev/null +++ b/assets/images/svg/appointment_rating_icon.svg @@ -0,0 +1,6 @@ + + + + + + diff --git a/assets/images/svg/calendar_filled_icon.svg b/assets/images/svg/calendar_filled_icon.svg new file mode 100644 index 00000000..116ce8ef --- /dev/null +++ b/assets/images/svg/calendar_filled_icon.svg @@ -0,0 +1,9 @@ + + + + + + + + + diff --git a/assets/images/svg/gallery.svg b/assets/images/svg/gallery.svg index ec1c45ef..1387f976 100644 --- a/assets/images/svg/gallery.svg +++ b/assets/images/svg/gallery.svg @@ -1,11 +1,5 @@ - - - - - - - - - - + + + + diff --git a/assets/images/svg/invoice_list_icon.svg b/assets/images/svg/invoice_list_icon.svg new file mode 100644 index 00000000..6b9e698f --- /dev/null +++ b/assets/images/svg/invoice_list_icon.svg @@ -0,0 +1,5 @@ + + + + + diff --git a/assets/images/svg/my_radiology_icon.svg b/assets/images/svg/my_radiology_icon.svg index 7b5ebe46..1848607f 100644 --- a/assets/images/svg/my_radiology_icon.svg +++ b/assets/images/svg/my_radiology_icon.svg @@ -1,3 +1,4 @@ - - + + + diff --git a/assets/images/svg/online_checkin_icon.svg b/assets/images/svg/online_checkin_icon.svg new file mode 100644 index 00000000..4f21533f --- /dev/null +++ b/assets/images/svg/online_checkin_icon.svg @@ -0,0 +1,5 @@ + + + + + diff --git a/assets/images/svg/open_camera.svg b/assets/images/svg/open_camera.svg index 171452a0..3dfda03c 100644 --- a/assets/images/svg/open_camera.svg +++ b/assets/images/svg/open_camera.svg @@ -1,7 +1,4 @@ - - - - - - + + + diff --git a/assets/langs/ar-SA.json b/assets/langs/ar-SA.json index f9cc3035..08adc616 100644 --- a/assets/langs/ar-SA.json +++ b/assets/langs/ar-SA.json @@ -301,6 +301,12 @@ "medicinesSubtitle": "الوصفات", "vitalSigns": "علامات حيوية", "vitalSignsSubTitle": "التقارير", + "vitalSignNormal": "طبيعي", + "vitalSignLow": "منخفض", + "vitalSignHigh": "مرتفع", + "vitalSignObese": "بدين", + "vitalSignOverweight": "زيادة الوزن", + "vitalSignUnderweight": "نقص الوزن", "myMedical": "نشط", "myMedicalSubtitle": "الأدوية", "myDoctor": "أطبائي", @@ -694,7 +700,7 @@ "retouch": "روتوش", "bikini": "بيكيني", "totalMinutes": "إجمالي الدقائق", - "feedback": "ملاحظات", + "feedback": "رأيك يهمنا", "likeToHear": "نود سماع ملاحظاتك، ومخاوفك بشأن خدمات الرعاية الصحية وتجربة الخدمات الإلكترونية. يرجى استخدام النموذج أدناه", "emptySubject": "يرجى إدخال الموضوع", "emptyMessage": "يرجى إدخال الرسالة", @@ -702,10 +708,10 @@ "complainAppo": "شكوى بخصوص الموعد", "complainAppoAlt": "شكوى بدون موعد", "messageType": "نوع الرسالة", - "feedbackType": "نوع الملاحظات", + "feedbackType": "نوع الرسالة", "compliment": "تقدير", "suggestion": "اقتراح", - "yourFeedback": "تم إرسال ملاحظاتك", + "yourFeedback": "تم إرسال رأيك", "selectPart": "يرجى اختيار الجزء الذي تشتكي منه", "number": "رقم", "notClassified": "غير مصنف", @@ -717,6 +723,11 @@ "bloodDonation": "التبرع بالدم", "bloodDonationInfo": "من خلال هذه الخدمة، يمكنك تسجيل اسمك كمتبرع بالدم حيث سيتواصل معك بنك الدم في مجموعة الحبيب الطبية في حالة الحاجة إلى فصيلة الدم.", "bloodInstruction": "أدخل المعلومات المطلوبة، من أجل التسجيل في خدمة التبرع بالدم", + "aboutBloodDonation": "عن التبرع بالدم", + "bloodDonationDescription": "التبرع بالدم هو عمل نبيل ينقذ الأرواح. كل تبرع واحد يمكن أن ينقذ ما يصل إلى ثلاثة أشخاص. من خلال التبرع بالدم، فإنك تساعد المرضى الذين يحتاجون إلى عمليات نقل دم، وحالات الطوارئ، والعلاجات الطبية.", + "bloodDonationBenefit1": "عملية آمنة وسريعة (10-15 دقيقة)", + "bloodDonationBenefit2": "يمكنك التبرع كل 8-12 أسبوع", + "bloodDonationBenefit3": "فحص صحي مجاني لدمك", "viewTerms": "لعرض الشروط والأحكام", "wantConnectHmgNetwork": "عزيزي العميل، لا يوجد وصول إلى الإنترنت، هل ترغب في الاتصال بشبكة مجموعة الحبيب الطبية لاستخدام تطبيقنا، تأكد من أنك في نطاق شبكة مجموعة الحبيب الطبية", "failedToAccessHmgServices": "متصل بشبكة مجموعة الحبيب الطبية،\n\nلكن فشل في الوصول إلى خدمات مجموعة الحبيب الطبية", @@ -936,7 +947,7 @@ "hospitalInformation": "معلومات المستشفى", "fetchingAppointmentShare": "جاري جلب تفاصيل الموعد...", "bookingYourAppointment": "جاري حجز موعدك...", - "selectLiveCareClinic": "اختر عيادة لايف كير", + "selectLiveCareClinic": "عيادة لايف كير", "checkingForExistingDentalPlan": "جاري التحقق من وجود خطة أسنان حالية، يرجى الانتظار...", "dentalTreatmentPlan": "خطة علاج الأسنان", "youHaveExistingTreatmentPlan": "لديك خطة علاج حالية: ", @@ -1249,7 +1260,7 @@ "quickLinks": "روابط سريعة", "viewMedicalFileLandingPage": "عرض الملف الطبي", "immediateLiveCareRequest": "طلب لايف كير فوري", - "yourTurnIsAfterPatients": "دورك بعد {count} مريض.", + "yourTurnIsAfterPatients": "دورك بعد {count} مريض. يرجى الانتظار حتى يحين دورك، وسيناديك الطبيب قريباً.", "dontHaveHHCOrders": "ليس لديك أي أوامر رعاية صحية منزلية حتى الآن.", "hhcOrders": "أوامر الرعاية الصحية المنزلية", "requestedServices": "الخدمات المطلوبة", @@ -1578,7 +1589,7 @@ "noFreeSlot": "لا توجد مواعيد متاحة في التاريخ المحدد. يرجى اختيار تاريخ آخر أو إعادة المحاولة لاحقاً.", "noThanksIKnowTheClinic": "لا، شكراً. أنا أعرف العيادة.", "unableToSendOTP": "تعذر إرسال رمز التحقق لمرة واحدة", - "loadingAIAnalysis": "جارٍ تحليل نتائج المختبر، يرجى التحلي بالصبر والسماح للذكاء الاصطناعي بالقيام بالمهمة، قد يستغرق هذا بعض الوقت.", + "loadingAIAnalysis": "نقوم بتحليل نتائجك، يرجى التحلي بالصبر والسماح للذكاء الاصطناعي بالقيام بالمهمة، قد يستغرق هذا بعض الوقت.", "symptoms": "أعراض", "insuranceInActive": "التأمين غير نشط", "insuranceInActiveContactSupport": "لديك تأمين ساري المفعول، ولكنه غير مُفعّل حاليًا في مجموعة حبيب الطبية. يُرجى التواصل مع خدمة عملاء مجموعة حبيب الطبية.", @@ -1653,5 +1664,61 @@ "oneWaySubtitle": "نقل من الموقع إلى المستشفى", "twoWaySubtitle": "نقل من الموقع إلى المستشفى والعودة مرة أخرى", "toHospitalSubtitle": "نقل من موقعك الحالي إلى المستشفى", - "fromHospitalSubtitle": "نقل من المستشفى إلى منزلك" + "fromHospitalSubtitle": "نقل من المستشفى إلى منزلك", + "clinicSchedule": "جدول العيادة", + "doctorAddedToFavourite": "تمت إضافة إلى قائمة المفضلة", + "doctorRemovedFromFavourite": "تمت إزالة من قائمة الأطباء المفضلين", + "addNewFavorite": "إضافة مفضلة جديدة", + "favourites": "المفضلة", + "pendingAncillaryOrders": "مدفوعات طلبات الاضافية", + "liveCarePermissions": "يتطلب لايف كير أذونات الكاميرا والميكروفون وإشعارات، يرجى السماح لها بالمتابعة.", + "containsImage": "يحتوي على صورة", + "servicesSelected": "الخدمة (الخدمات) المختارة", + "selectMedicalFileFromFamily": "اختر ملف طبي من عائلتك", + "anyFileFromHMG": "أي ملف طبي نشط من مجموعة حبيب الطبية", + "noDistance": "المسافة غير متوفرة", + "enterAmount": "يرجى إدخال المبلغ للمتابعة.", + "selectHospitalContinue": "يرجى اختيار المستشفى للمتابعة.", + "year": "سنة", + "notifyPrescription": "أبلغني قبل وقت الاستهلاك", + "doctorRatingAppointment": "طبيب\nتصنيف", + "ratingSubmitted": "تم إرسال التقييم", + "rateTheDoctor": "يرجى تقييم الطبيب", + "downloadInvoice": "تحميل الفاتورة", + "approvalDetails": "تفاصيل الموافقة", + "obese": "سمين", + "overWeight": "وزن زائد", + "underWeight": "وزن ناقص", + "nearestGate": "أقرب بوابة", + "RRTDDetails": "فريق الاستجابة السريعة يقوم بتقديم رعاية صحية متكاملة لجميع الحالات الطارئة و المستقرة، الخدمة تشمل جميع الأعمار بما في ذلك حالات الأطفال.", + "fromHospitalSubtitle": "نقل من المستشفى إلى منزلك", + "normalMapType": "افتراضي", + "satelliteMapType": "قمر صناعي", + "terrainMapType": "تضاريس", + "hybridMapType": "هجين", + "openCamera": "افتح الكاميرا", + "openGallery": "افتح المعرض", + "openFiles": "فتح الملفات", + "generateAiAnalysisRadResult": "بإنشاء تحليل الذكاء الاصطناعي لهذا التقرير", + "grantLocationPermission": "يرجى منح إذن الوصول إلى الموقع من إعدادات التطبيق للحصول على نتائج أفضل.", + "NoBookedAppointments": "لا توجد مواعيد محجوزة", + "aiDisclaimerRad": "سيتم مشاركة بيانات نتائج الأشعة الخاصة بك بشكل آمن مع محلل الذكاء الاصطناعي لدينا لتحليلها. يساعد هذا في توفير رؤى صحية مخصصة. هل ترغب في المتابعة؟", + "imageSizeTooLarge": "حجم الصورة كبير جداً. الحد الأقصى للحجم هو 1 ميجابايت", + "profileImageUpdatedSuccessfully": "تم تحديث صورة الملف الشخصي بنجاح", + "failedToProcessImage": "فشل في معالجة الصورة. يرجى المحاولة مرة أخرى", + "cameraGalleryPermissionMessage": "مطلوب إذن الكاميرا والمعرض لتحميل صورة الملف الشخصي. يرجى منح الأذونات من إعدادات التطبيق.", + "cameraGalleryPermissionRequired": "مطلوب أذونات الكاميرا والمعرض", + "cameraPermissionMessage": "مطلوب إذن الكاميرا لالتقاط الصور. يرجى منح الإذن من إعدادات التطبيق.", + "cameraPermissionRequired": "مطلوب إذن الكاميرا", + "galleryPermissionMessage": "مطلوب إذن المعرض لتحديد الصور. يرجى منح الإذن من إعدادات التطبيق.", + "galleryPermissionRequired": "مطلوب إذن المعرض", + "failedToCheckPermissions": "فشل في التحقق من الأذونات. يرجى المحاولة مرة أخرى", + "takePhotoWithCamera": "التقط صورة بالكاميرا", + "selectFromPhotoLibrary": "اختر من مكتبة الصور", + "selectDocumentFiles": "اختر ملفات المستندات", + "profilePicture": "صورة الملف الشخصي", + "aiDisclaimerRad": "سيتم مشاركة بيانات نتائج الأشعة الخاصة بك بشكل آمن مع محلل الذكاء الاصطناعي لدينا لتحليلها. يساعد هذا في توفير رؤى صحية مخصصة. هل ترغب في المتابعة؟", + "sendingAskDrRequest": "جارٍ إرسال الطلب...", + "askDrRequestSentSuccess": "تم إرسال الطلب بنجاح، وسيتواصل معك الطبيب قريباً.", + "onlineCheckIn24HoursOnly": "لا تتوفر خدمة تسجيل الوصول عبر الإنترنت إلا خلال 4 ساعات من موعد الحجز.لا تتوفر خدمة تسجيل الوصول عبر الإنترنت إلا خلال 4 ساعات من موعد الحجز." } diff --git a/assets/langs/en-US.json b/assets/langs/en-US.json index 1bbd7cb7..18484fc1 100644 --- a/assets/langs/en-US.json +++ b/assets/langs/en-US.json @@ -300,6 +300,12 @@ "medicinesSubtitle": "Prescriptions", "vitalSigns": "Vital Signs", "vitalSignsSubTitle": "Reports", + "vitalSignNormal": "Normal", + "vitalSignLow": "Low", + "vitalSignHigh": "High", + "vitalSignObese": "Obese", + "vitalSignOverweight": "Overweight", + "vitalSignUnderweight": "Underweight", "myMedical": "Active", "myMedicalSubtitle": "Medications", "myDoctor": "My Doctors", @@ -710,6 +716,11 @@ "bloodDonation": "Blood Donation", "bloodDonationInfo": "Through this service, you can register your name as a blood donor where the blood bank in the Habib Medical Group will communicate you in case of need for blood type.", "bloodInstruction": "Enter the required information, in order to register for Blood Donation Service", + "aboutBloodDonation": "About Blood Donation", + "bloodDonationDescription": "Blood donation is a noble act that saves lives. Every single donation can save up to three people. By donating blood, you help patients in need of transfusions, emergencies, and medical treatments.", + "bloodDonationBenefit1": "Safe and quick process (10-15 minutes)", + "bloodDonationBenefit2": "You can donate every 8-12 weeks", + "bloodDonationBenefit3": "Free health check of your blood", "viewTerms": "To view the terms and conditions", "wantConnectHmgNetwork": "Dear customer there is no internet access, do you want to connect with HMG network to use our app, make sure you are in range of HMG network", "failedToAccessHmgServices": "Connected with HMG Network,\n\nBut failed to access HMG services", @@ -930,7 +941,7 @@ "hospitalInformation": "Hospital Information", "fetchingAppointmentShare": "Fetching Appointment Share...", "bookingYourAppointment": "Booking your appointment...", - "selectLiveCareClinic": "Select LiveCare Clinic", + "selectLiveCareClinic": "LiveCare Clinic", "checkingForExistingDentalPlan": "Checking for an existing dental plan, Please wait...", "dentalTreatmentPlan": "Dental treatment plan", "youHaveExistingTreatmentPlan": "You have an existing treatment plan: ", @@ -1241,7 +1252,7 @@ "quickLinks": "Quick Links", "viewMedicalFileLandingPage": "View medical file", "immediateLiveCareRequest": "Immediate LiveCare Request", - "yourTurnIsAfterPatients": "Your turn is after {count} patients.", + "yourTurnIsAfterPatients": "Your turn is after {count} patients. Please wait for your turn, The doctor will call you shortly.", "dontHaveHHCOrders": "You don't have any Home Health Care orders yet.", "hhcOrders": "HHC Orders", "requestedServices": "Requested Services", @@ -1309,7 +1320,7 @@ "rejectedYourFamilyMemberRequest": "{status} your family member request", "notAvailable": "N/A", "selectAProfile": "Please select a profile", - "switchFamilyFile": "Switch from the below list of medical file", + "switchFamilyFile": "Switch from the below list of medical files", "medicalFiles": "Medical Files", "addANewFamilyMember": "Add a new family member", "viewInvoiceDetails": "View invoice details", @@ -1572,7 +1583,7 @@ "contextDependent": "Context Dependent", "calculatedValue": "Calculated Value", "calculationFormula": "Calculation Formula", - "loadingAIAnalysis": "Analysing your lab results, Please be patient and let the AI do the magic, This might take some time.", + "loadingAIAnalysis": "Analysing your results, Please be patient and let the AI do the magic, This might take some time.", "symptoms": "Symptoms", "insuranceInActive": "Insurance Inactive", "insuranceInActiveContactSupport": "You have a valid insurance but it is currently inactive in HMG. Please contact HMG customer support to activate it.", @@ -1645,5 +1656,61 @@ "oneWaySubtitle": "Pickup from location to hospital", "twoWaySubtitle": "Round trip from location to hospital and back", "toHospitalSubtitle": "Transfer from your current location to the hospital", - "fromHospitalSubtitle": "Transfer from the hospital back to your home" + "fromHospitalSubtitle": "Transfer from the hospital back to your home", + "clinicSchedule": "Clinic Schedule", + "doctorAddedToFavourite": "Added to favorites list", + "doctorRemovedFromFavourite": "Removed from favorites list", + "favourites": "Favourites", + "addNewFavorite": "Add new favourite", + "pendingAncillaryOrders": "Pending ancillary orders", + "liveCarePermissions": "LiveCare requires Camera, Microphone & Notifications permissions to enable virtual consultation between patient & doctor, Open app settings to allow them.", + "containsImage": "Contains Image", + "servicesSelected": "Service(s) Selected", + "selectMedicalFileFromFamily": "Select a medical file from your family", + "anyFileFromHMG": "Any active medical file from HMG", + "noDistance": "Distance not available", + "enterAmount": "Please enter amount to continue.", + "selectHospitalContinue": "Please select hospital to continue.", + "year": "Year", + "notifyPrescription": "Notify me before the consumption time", + "doctorRatingAppointment": "Doctor\nRating", + "ratingSubmitted": "Rating Submitted", + "rateTheDoctor": "Please rate the doctor", + "downloadInvoice": "Download Invoice", + "approvalDetails": "Approval Details", + "obese": "Obese", + "overWeight": "OverWeight", + "underWeight": "UnderWeight", + "nearestGate": "Nearest Gate", + "RRTDDetails": "The Rapid response team provides a comprehensive medical service for all sorts of urgent and stable cases of all ages, including adult and paediatric patients.", + "fromHospitalSubtitle": "Transfer from the hospital back to your home", + "normalMapType": "Default", + "satelliteMapType": "Satellite", + "terrainMapType": "Terrain", + "hybridMapType": "Hybrid", + "openCamera": "Open Camera", + "openGallery": "Open Gallery", + "openFiles": "Open Files", + "generateAiAnalysisRadResult": "Generate AI analysis for this result", + "grantLocationPermission": "Please grant location permission from app settings to see better results.", + "NoBookedAppointments": "No Booked Appointments", + "aiDisclaimerRad": "Your radiology result data will be securely shared with our AI Analyzer for analysis. This helps provide personalized health insights. Do you want to proceed?", + "imageSizeTooLarge": "Image size is too large. Maximum size is 1MB", + "profileImageUpdatedSuccessfully": "Profile image updated successfully", + "failedToProcessImage": "Failed to process image. Please try again", + "cameraGalleryPermissionMessage": "Camera and Gallery permissions are required to upload profile picture. Please grant permissions from app settings.", + "cameraGalleryPermissionRequired": "Camera and Gallery permissions are required", + "cameraPermissionMessage": "Camera permission is required to take photos. Please grant permission from app settings.", + "cameraPermissionRequired": "Camera permission is required", + "galleryPermissionMessage": "Gallery permission is required to select photos. Please grant permission from app settings.", + "galleryPermissionRequired": "Gallery permission is required", + "failedToCheckPermissions": "Failed to check permissions. Please try again", + "takePhotoWithCamera": "Take a photo with your camera", + "selectFromPhotoLibrary": "Select from your photo library", + "selectDocumentFiles": "Select document files", + "profilePicture": "Profile Picture", + "aiDisclaimerRad": "Your radiology result data will be securely shared with our AI Analyzer for analysis. This helps provide personalized health insights. Do you want to proceed?", + "sendingAskDrRequest": "Sending Request...", + "askDrRequestSentSuccess": "Request has been sent successfully, The doctor will get back to you soon.", + "onlineCheckIn24HoursOnly": "Online Check-In is only available within 4 hours of the appointment time." } diff --git a/ios/Podfile.lock b/ios/Podfile.lock index 22b68560..b796a719 100644 --- a/ios/Podfile.lock +++ b/ios/Podfile.lock @@ -142,6 +142,11 @@ PODS: - flutter_callkit_incoming (0.0.1): - CryptoSwift - Flutter + - flutter_image_compress_common (1.0.0): + - Flutter + - Mantle + - SDWebImage + - SDWebImageWebPCoder - flutter_inappwebview_ios (0.0.1): - Flutter - flutter_inappwebview_ios/Core (= 0.0.1) @@ -242,6 +247,18 @@ PODS: - just_audio (0.0.1): - Flutter - FlutterMacOS + - libwebp (1.5.0): + - libwebp/demux (= 1.5.0) + - libwebp/mux (= 1.5.0) + - libwebp/sharpyuv (= 1.5.0) + - libwebp/webp (= 1.5.0) + - libwebp/demux (1.5.0): + - libwebp/webp + - libwebp/mux (1.5.0): + - libwebp/demux + - libwebp/sharpyuv (1.5.0) + - libwebp/webp (1.5.0): + - libwebp/sharpyuv - local_auth_darwin (0.0.1): - Flutter - FlutterMacOS @@ -249,6 +266,9 @@ PODS: - Flutter - manage_calendar_events (0.0.1): - Flutter + - Mantle (2.2.0): + - Mantle/extobjc (= 2.2.0) + - Mantle/extobjc (2.2.0) - map_launcher (0.0.1): - Flutter - MapboxCommon (23.11.0) @@ -296,9 +316,12 @@ PODS: - PromisesObjC (2.4.0) - PromisesSwift (2.4.0): - PromisesObjC (= 2.4.0) - - SDWebImage (5.21.7): - - SDWebImage/Core (= 5.21.7) - - SDWebImage/Core (5.21.7) + - SDWebImage (5.21.5): + - SDWebImage/Core (= 5.21.5) + - SDWebImage/Core (5.21.5) + - SDWebImageWebPCoder (0.15.0): + - libwebp (~> 1.0) + - SDWebImage/Core (~> 5.17) - share_plus (0.0.1): - Flutter - shared_preferences_foundation (0.0.1): @@ -342,6 +365,7 @@ DEPENDENCIES: - FLAnimatedImage - Flutter (from `Flutter`) - flutter_callkit_incoming (from `.symlinks/plugins/flutter_callkit_incoming/ios`) + - flutter_image_compress_common (from `.symlinks/plugins/flutter_image_compress_common/ios`) - flutter_inappwebview_ios (from `.symlinks/plugins/flutter_inappwebview_ios/ios`) - flutter_ios_voip_kit_karmm (from `.symlinks/plugins/flutter_ios_voip_kit_karmm/ios`) - flutter_local_notifications (from `.symlinks/plugins/flutter_local_notifications/ios`) @@ -395,6 +419,8 @@ SPEC REPOS: - GoogleDataTransport - GoogleMaps - GoogleUtilities + - libwebp + - Mantle - MapboxCommon - MapboxCoreMaps - MapboxCoreNavigation @@ -411,6 +437,7 @@ SPEC REPOS: - PromisesObjC - PromisesSwift - SDWebImage + - SDWebImageWebPCoder - Solar-dev - SwiftProtobuf - SwiftyGif @@ -446,6 +473,8 @@ EXTERNAL SOURCES: :path: Flutter flutter_callkit_incoming: :path: ".symlinks/plugins/flutter_callkit_incoming/ios" + flutter_image_compress_common: + :path: ".symlinks/plugins/flutter_image_compress_common/ios" flutter_inappwebview_ios: :path: ".symlinks/plugins/flutter_inappwebview_ios/ios" flutter_ios_voip_kit_karmm: @@ -532,6 +561,7 @@ SPEC CHECKSUMS: FLAnimatedImage: bbf914596368867157cc71b38a8ec834b3eeb32b Flutter: cabc95a1d2626b1b06e7179b784ebcf0c0cde467 flutter_callkit_incoming: 417dd1b46541cdd5d855ad795ccbe97d1c18155e + flutter_image_compress_common: ec1d45c362c9d30a3f6a0426c297f47c52007e3e flutter_inappwebview_ios: 6f63631e2c62a7c350263b13fa5427aedefe81d4 flutter_ios_voip_kit_karmm: 7ea37381a8841c92d186edf1f4604df5cc437579 flutter_local_notifications: ff50f8405aaa0ccdc7dcfb9022ca192e8ad9688f @@ -550,9 +580,11 @@ SPEC CHECKSUMS: image_picker_ios: 4f2f91b01abdb52842a8e277617df877e40f905b in_app_review: 436034b18594851a7328d7f1c2ed5ec235b79cfc just_audio: a42c63806f16995daf5b219ae1d679deb76e6a79 + libwebp: 02b23773aedb6ff1fd38cec7a77b81414c6842a8 local_auth_darwin: 63c73d6d28cc3e239be2b6aa460ea6e317cd5100 location: d5cf8598915965547c3f36761ae9cc4f4e87d22e manage_calendar_events: 9b2889799340398027b3e3f5c4891d41599ec257 + Mantle: c5aa8794a29a022dfbbfc9799af95f477a69b62d map_launcher: 8a4e484aaddff55680a7da9bc9e25f512067fb87 MapboxCommon: 119f3759f7dc9457f0695848108ab323eb643cb4 MapboxCoreMaps: ca17f67baced23f8c952166ac6314c35bad3f66c @@ -569,17 +601,18 @@ SPEC CHECKSUMS: OrderedSet: e539b66b644ff081c73a262d24ad552a69be3a94 package_info_plus: c0502532a26c7662a62a356cebe2692ec5fe4ec4 path_provider_foundation: 0b743cbb62d8e47eab856f09262bb8c1ddcfe6ba - PayFortSDK: e12f79051ae15b2e29536771a45354900180bfcb + PayFortSDK: 233eabe9a45601fdbeac67fa6e5aae46ed8faf82 permission_handler_apple: 9878588469a2b0d0fc1e048d9f43605f92e6cec2 Polyline: 2a1f29f87f8d9b7de868940f4f76deb8c678a5b1 PromisesObjC: f5707f49cb48b9636751c5b2e7d227e43fba9f47 PromisesSwift: 9d77319bbe72ebf6d872900551f7eeba9bce2851 - SDWebImage: e9fc87c1aab89a8ab1bbd74eba378c6f53be8abf + SDWebImage: e9c98383c7572d713c1a0d7dd2783b10599b9838 + SDWebImageWebPCoder: 0e06e365080397465cc73a7a9b472d8a3bd0f377 share_plus: 8b6f8b3447e494cca5317c8c3073de39b3600d1f shared_preferences_foundation: 5086985c1d43c5ba4d5e69a4e8083a389e2909e6 Solar-dev: 4612dc9878b9fed2667d23b327f1d4e54e16e8d0 sqflite_darwin: 5a7236e3b501866c1c9befc6771dfd73ffb8702d - SwiftProtobuf: 9e106a71456f4d3f6a3b0c8fd87ef0be085efc38 + SwiftProtobuf: e1b437c8e31a4c5577b643249a0bb62ed4f02153 SwiftyGif: 706c60cf65fa2bc5ee0313beece843c8eb8194d4 Turf: aa2ede4298009639d10db36aba1a7ebaad072a5e url_launcher_ios: bb13df5870e8c4234ca12609d04010a21be43dfa diff --git a/ios/Runner/Info.plist b/ios/Runner/Info.plist index bbe54c9a..5b3b2681 100644 --- a/ios/Runner/Info.plist +++ b/ios/Runner/Info.plist @@ -89,6 +89,8 @@ This app requires motion detection access to function properly. NSPhotoLibraryUsageDescription This app requires photo library access to select image as document & upload it. + NSPhotoLibraryAddUsageDescription + This app requires photo library access to upload profile picture and documents. NSRemindersUsageDescription This app requires calendar access to set reminders for Virtual & Normal Appointments. NSSpeechRecognitionUsageDescription diff --git a/ios/Runner/ring_30Sec.mp3 b/ios/Runner/ring_30Sec.mp3 new file mode 100644 index 00000000..b5bb4b88 Binary files /dev/null and b/ios/Runner/ring_30Sec.mp3 differ diff --git a/lib/core/api_consts.dart b/lib/core/api_consts.dart index 5fb73a37..bf9144fa 100644 --- a/lib/core/api_consts.dart +++ b/lib/core/api_consts.dart @@ -25,6 +25,7 @@ class ApiConsts { static String GET_TAMARA_PAYMENT_STATUS = 'https://mdlaboratories.com/tamaralive/api/OnlineTamara/order_status?orderid='; static String QLINE_URL = "https://ms.hmg.com/nscapi/api/PatientCall/PatientInQueue_Detail"; + // static String QLINE_URL = "https://qline.hmg.com/api/PatientCall/PatientInQueue_Detail"; static String CHAT_URL = "https://chat.hmg.com/Index.aspx?RequestedId="; @@ -44,7 +45,7 @@ class ApiConsts { GET_TAMARA_INSTALLMENTS_URL = "https://mdlaboratories.com/tamaralive/Home/GetInstallments"; GET_TAMARA_PAYMENT_STATUS = 'https://mdlaboratories.com/tamaralive/api/OnlineTamara/order_status?orderid='; rcBaseUrl = 'https://rc.hmg.com/'; - QLINE_URL = "https://ms.hmg.com/nscapi/api/PatientCall/PatientInQueue_Detail"; + QLINE_URL = "https://qline.hmg.com/api/PatientCall/PatientInQueue_Detail"; CHAT_URL = "https://chat.hmg.com/Index.aspx?RequestedId="; break; case AppEnvironmentTypeEnum.dev: @@ -80,7 +81,7 @@ class ApiConsts { GET_TAMARA_INSTALLMENTS_URL = "https://epharmacy.hmg.com/tamara/Home/getinstallments"; GET_TAMARA_PAYMENT_STATUS = 'https://epharmacy.hmg.com/tamara/api/OnlineTamara/order_status?orderid='; rcBaseUrl = 'https://rc.hmg.com/'; - QLINE_URL = "https://ms.hmg.com/nscapi/api/PatientCall/PatientInQueue_Detail"; + QLINE_URL = "https://qline.hmg.com/api/PatientCall/PatientInQueue_Detail"; CHAT_URL = "https://chat.hmg.com/geneysChat/Index.aspx?RequestedId="; break; case AppEnvironmentTypeEnum.qa: @@ -230,8 +231,18 @@ class ApiConsts { static String getPatientBloodGroup = "services/PatientVarification.svc/REST/BloodDonation_GetBloodGroupDetails"; static String getPatientBloodAgreement = "Services/PatientVarification.svc/REST/CheckUserAgreementForBloodDonation"; static String getPatientBloodTypeNew = "Services/Patients.svc/REST/HIS_GetPatientBloodType_New"; - static String getAiOverViewLabOrders = "Services/Patients.svc/REST/HMGAI_Lab_Analyze_Orders_API"; - static String getAiOverViewLabOrder = "Services/Patients.svc/REST/HMGAI_Lab_Analyzer_API"; + // static String getAiOverViewLabOrders = "Services/Patients.svc/REST/HMGAI_Lab_Analyze_Orders_API"; + // static String getAiOverViewLabOrder = "Services/Patients.svc/REST/HMGAI_Lab_Analyzer_API"; + + static String getAiAnalysisURL = "https://api.labpredict.com"; + static String getAiRadiologyAnalysis = "$getAiAnalysisURL/radiology"; + static String getAiOverViewLabOrdersNew = "$getAiAnalysisURL/analyze_lab_orders"; + static String getAiOverViewLabOrderNew = "$getAiAnalysisURL/analyze"; + + static String googleCloudStorageARTranslationFileURL = "https://storage.googleapis.com/hmg-patientapp-translations/ar-SA.json"; + static String googleCloudStorageENTranslationFileURL = "https://storage.googleapis.com/hmg-patientapp-translations/en-US.json"; + + static String googleCloudStorageENTranslationFileBaseURL = "https://storage.googleapis.com/hmg-patientapp-translations"; // ************ static values for Api **************** static final double appVersionID = 20.9; @@ -450,6 +461,7 @@ var IS_LAST_APPOITMENT_RATED = 'Services/Doctors.svc/REST/IsLastAppoitmentRated_ var GET_APPOINTMENT_DETAILS_BY_NO = 'Services/MobileNotifications.svc/REST/GetAppointmentDetailsByApptNo'; var NEW_RATE_APPOINTMENT_URL = "Services/Doctors.svc/REST/AppointmentsRating_InsertAppointmentRate"; var NEW_RATE_DOCTOR_URL = "Services/Doctors.svc/REST/DoctorsRating_InsertDoctorRate"; +var GET_DOCTORS_RATING_CHECK = "Services/Doctors.svc/REST/GetDoctorsRatingCheck"; //var GET_QR_PARKING = 'Services/SWP.svc/REST/GetQRParkingByID'; @@ -464,7 +476,8 @@ var GET_ACTIVE_APPOINTMENTS_LIST_URL = "Services/Doctors.svc/Rest/Dr_GetAppointm var GET_DOCTORS_LIST_URL = "Services/Doctors.svc/REST/SearchDoctorsByTime"; //URL to dental doctors list -var GET_DENTAL_DOCTORS_LIST_URL = "Services/Doctors.svc/REST/Dental_DoctorChiefComplaintMapping"; +// var GET_DENTAL_DOCTORS_LIST_URL = "Services/Doctors.svc/REST/Dental_DoctorChiefComplaintMapping"; +var GET_DENTAL_DOCTORS_LIST_URL = "Services/Doctors.svc/REST/Dental_DoctorChiefComplaintMapping_Nearest"; //URL to get doctor free slots var GET_DOCTOR_FREE_SLOTS = "Services/Doctors.svc/REST/GetDoctorFreeSlots"; @@ -815,8 +828,8 @@ var FILTERED_PRODUCTS = 'products?categoryids='; var GET_DOCTOR_LIST_CALCULATION = "Services/Doctors.svc/REST/GetCallculationDoctors"; -var GET_ALL_APPOINTMENTS_FOR_DENTAL_CLINIC = "Services/Patients.svc/REST/GetDentalAppointments"; - +// var GET_ALL_APPOINTMENTS_FOR_DENTAL_CLINIC = "Services/Patients.svc/REST/GetDentalAppointments"; +var GET_ALL_APPOINTMENTS_FOR_DENTAL_CLINIC ='Services/Patients.svc/REST/GetAllInvoices'; var GET_DENTAL_APPOINTMENT_INVOICE = "Services/Patients.svc/REST/HIS_eInvoiceForDentalByAppointmentNo"; var SEND_DENTAL_APPOINTMENT_INVOICE_EMAIL = "Services/Notifications.svc/REST/SendInvoiceForDental"; @@ -934,6 +947,12 @@ const PROFILE_SETTING = 'Services/Patients.svc/REST/GetPateintInfoForUpdate'; const SAVE_SETTING = 'Services/Patients.svc/REST/UpdatePateintInfo'; +const GET_PROFILE_IMAGE = 'Services/Patients.svc/REST/Patient_GetProfileImageData'; + +const UPLOAD_PROFILE_IMAGE = 'Services/Patients.svc/REST/Patient_InsertProfileImageData'; + +const DOWNLOAD_INVOICE_PDF = 'Services/Notifications.svc/REST/DownloadInvoiceReport'; + class ApiKeyConstants { static final String googleMapsApiKey = 'AIzaSyB6TERnxIr0yJ3qG4ULBZbu0sAD4tGqtng'; } diff --git a/lib/core/app_assets.dart b/lib/core/app_assets.dart index d90cd40c..44ca3b10 100644 --- a/lib/core/app_assets.dart +++ b/lib/core/app_assets.dart @@ -220,6 +220,7 @@ class AppAssets { static const String ibw = '$svgBasePath/ibw.svg'; static const String general_health = '$svgBasePath/general_health.svg'; static const String women_health = '$svgBasePath/women_health.svg'; + static const String add_new_family_icon = '$svgBasePath/add_new_family_icon.svg'; static const String height = '$svgBasePath/height.svg'; static const String weight = '$svgBasePath/weight.svg'; @@ -236,6 +237,7 @@ class AppAssets { static const String forward_top_nav_icon = '$svgBasePath/forward_top_nav_icon.svg'; static const String back_top_nav_icon = '$svgBasePath/back_top_nav_icon.svg'; static const String bluetooth = '$svgBasePath/bluetooth.svg'; + static const String calendar_filled_icon = '$svgBasePath/calendar_filled_icon.svg'; //smartwatch static const String watchActivity = '$svgBasePath/watch_activity.svg'; @@ -354,6 +356,12 @@ class AppAssets { static const String darkModeIcon = '$svgBasePath/dark_mode_icon.svg'; static const String biometricLockIcon = '$svgBasePath/biometric_lock_icon.svg'; static const String imageIcon = '$svgBasePath/image_icon.svg'; + static const String appointmentRatingIcon = '$svgBasePath/appointment_rating_icon.svg'; + static const String invoiceListIcon = '$svgBasePath/invoice_list_icon.svg'; + static const String open_camera = '$svgBasePath/open_camera.svg'; + static const String gallery = '$svgBasePath/gallery.svg'; + static const String files = '$svgBasePath/files.svg'; + static const String online_checkin_icon = '$svgBasePath/online_checkin_icon.svg'; // PNGS // static const String hmgLogo = '$pngBasePath/hmg_logo.png'; @@ -392,6 +400,7 @@ class AppAssets { static const String fullBodyFrontFemale = '$pngBasePath/full_body_front_female.png'; static const String fullBodyBackFemale = '$pngBasePath/full_body_back_female.png'; static const String bmiFullBody = '$pngBasePath/bmi_image_1.png'; + static const String defaultMEP = '$pngBasePath/default_MEP.png'; } class AppAnimations { @@ -412,4 +421,6 @@ class AppAnimations { static const String ambulance = '$lottieBasePath/ambulance.json'; static const String ambulanceAlert = '$lottieBasePath/ambulance_alert.json'; static const String rrtAmbulance = '$lottieBasePath/rrt_ambulance.json'; + + static const String hourGlass = '$lottieBasePath/HourGlass.json'; } diff --git a/lib/core/app_state.dart b/lib/core/app_state.dart index 7d3ab1a4..85fe9cb2 100644 --- a/lib/core/app_state.dart +++ b/lib/core/app_state.dart @@ -10,14 +10,19 @@ import 'package:hmg_patient_app_new/features/authentication/models/resp_models/a import 'package:hmg_patient_app_new/features/authentication/models/resp_models/check_user_staus_nhic_response_model.dart'; import 'package:hmg_patient_app_new/features/authentication/models/resp_models/select_device_by_imei.dart'; import 'package:hmg_patient_app_new/services/navigation_service.dart'; +import 'package:hmg_patient_app_new/services/cache_service.dart'; import '../features/authentication/models/request_models/registration_payload_model.dart'; class AppState { NavigationService navigationService; + CacheService cacheService; - AppState({required this.navigationService}){ + static const String _profileImageKey = 'profile_image_data'; + + AppState({required this.navigationService, required this.cacheService}){ checkIfGMSAvailableOrNot(); + _loadProfileImageFromCache(); } double userLat = 0.0; @@ -84,6 +89,31 @@ class AppState { set setUserBloodGroup(String value) => _userBloodGroup = value; + String? _profileImageData; + + String? get getProfileImageData => _profileImageData; + + set setProfileImageData(String? value) { + _profileImageData = value; + // Persist to cache + if (value != null && value.isNotEmpty) { + cacheService.saveString(key: _profileImageKey, value: value); + } else { + cacheService.remove(key: _profileImageKey); + } + } + + /// Load profile image from cache on app initialization + void _loadProfileImageFromCache() { + _profileImageData = cacheService.getString(key: _profileImageKey); + } + + /// Clear profile image from cache (e.g., on logout) + void clearProfileImageCache() { + _profileImageData = null; + cacheService.remove(key: _profileImageKey); + } + SelectDeviceByImeiRespModelElement? _selectDeviceByImeiRespModelElement; void setSelectDeviceByImeiRespModelElement(SelectDeviceByImeiRespModelElement? value) { @@ -175,4 +205,92 @@ class AppState { setRatedVisible(bool value) { isRatedVisible = value; } + + Map _userSwitchPayloads = {}; + int _childSwitchCounter = 0; + + Map get getUserSwitchPayloads => _userSwitchPayloads; + + void addUserSwitchPayload(dynamic payload, {bool isSuperUser = false}) { + var patientID = payload is Map ? payload['patientid'] : null; + + if (patientID == null) { + if (isSuperUser) { + _userSwitchPayloads['superUser'] = payload; + _childSwitchCounter = 0; + } else { + _childSwitchCounter++; + if (_childSwitchCounter == 1) { + _userSwitchPayloads['child'] = payload; + } else { + _userSwitchPayloads['child$_childSwitchCounter'] = payload; + } + } + return; + } + + String? existingKey; + for (var entry in _userSwitchPayloads.entries) { + if (entry.value is Map && entry.value['PatientID'] == patientID) { + existingKey = entry.key; + break; + } + } + + if (isSuperUser) { + if (existingKey != null && existingKey != 'superUser') { + _userSwitchPayloads.remove(existingKey); + } + _userSwitchPayloads['superUser'] = payload; + _childSwitchCounter = 0; + } else { + if (existingKey != null) { + _userSwitchPayloads[existingKey] = payload; + } else { + _childSwitchCounter++; + if (_childSwitchCounter == 1) { + _userSwitchPayloads['child'] = payload; + } else { + _userSwitchPayloads['child$_childSwitchCounter'] = payload; + } + } + } + } + + + dynamic getUserSwitchPayload({required String userType}) { + return _userSwitchPayloads[userType]; + } + + dynamic getUserSwitchPayloadByIndex({bool isSuperUser = false, int? childIndex}) { + if (isSuperUser) { + return _userSwitchPayloads['superUser']; + } else if (childIndex != null) { + if (childIndex == 1) { + return _userSwitchPayloads['child']; + } else { + return _userSwitchPayloads['child$childIndex']; + } + } + return null; + } + + void clearUserSwitchPayloads() { + _userSwitchPayloads.clear(); + _childSwitchCounter = 0; + } + + void clearChildUsersKeepSuperUser() { + // Keep the super user payload + final superUserPayload = _userSwitchPayloads['superUser']; + + // Clear all payloads + _userSwitchPayloads.clear(); + _childSwitchCounter = 0; + + // Restore super user if it existed + if (superUserPayload != null) { + _userSwitchPayloads['superUser'] = superUserPayload; + } + } } diff --git a/lib/core/dependencies.dart b/lib/core/dependencies.dart index 657d03ea..3b7253db 100644 --- a/lib/core/dependencies.dart +++ b/lib/core/dependencies.dart @@ -113,7 +113,12 @@ class AppDependencies { getIt.registerLazySingleton(() => LocalAuthentication()); getIt.registerLazySingleton(() => GAnalytics()); - getIt.registerLazySingleton(() => AppState(navigationService: getIt())); + + // Register CacheService before AppState so it can be injected + final sharedPreferences = await SharedPreferences.getInstance(); + getIt.registerLazySingleton(() => CacheServiceImp(sharedPreferences: sharedPreferences, loggerService: getIt())); + + getIt.registerLazySingleton(() => AppState(navigationService: getIt(), cacheService: getIt())); getIt.registerLazySingleton(() => LocationUtils( isShowConfirmDialog: false, navigationService: getIt(), @@ -126,8 +131,6 @@ class AppDependencies { navigationService: getIt(), )); - final sharedPreferences = await SharedPreferences.getInstance(); - getIt.registerLazySingleton(() => CacheServiceImp(sharedPreferences: sharedPreferences, loggerService: getIt())); final flutterLocalNotificationsPlugin = FlutterLocalNotificationsPlugin(); getIt.registerLazySingleton(() => NotificationServiceImp( diff --git a/lib/core/location_util.dart b/lib/core/location_util.dart index 95b154ba..c56c63d4 100644 --- a/lib/core/location_util.dart +++ b/lib/core/location_util.dart @@ -99,7 +99,7 @@ class LocationUtils { title: LocaleKeys.notice.tr(context: navigationService.navigatorKey.currentContext!), navigationService.navigatorKey.currentContext!, child: Utils.getWarningWidget( - loadingText: "Please grant location permission from app settings to see better results", + loadingText: LocaleKeys.grantLocationPermission.tr(), isShowActionButtons: true, onCancelTap: () { navigationService.pop(); @@ -124,7 +124,7 @@ class LocationUtils { title: LocaleKeys.notice.tr(context: navigationService.navigatorKey.currentContext!), navigationService.navigatorKey.currentContext!, child: Utils.getWarningWidget( - loadingText: "Please grant location permission from app settings to see better results", + loadingText: LocaleKeys.grantLocationPermission.tr(), isShowActionButtons: true, onCancelTap: () { navigationService.pop(); @@ -285,7 +285,7 @@ class LocationUtils { title: LocaleKeys.notice.tr(context: navigationService.navigatorKey.currentContext!), navigationService.navigatorKey.currentContext!, child: Utils.getWarningWidget( - loadingText: "Please grant location permission from app settings to see better results", + loadingText: LocaleKeys.grantLocationPermission.tr(), isShowActionButtons: true, onCancelTap: () { navigationService.pop(); diff --git a/lib/core/utils/image_compression_helper.dart b/lib/core/utils/image_compression_helper.dart new file mode 100644 index 00000000..71567621 --- /dev/null +++ b/lib/core/utils/image_compression_helper.dart @@ -0,0 +1,103 @@ +import 'dart:io'; +import 'package:flutter_image_compress/flutter_image_compress.dart'; +import 'package:path_provider/path_provider.dart'; +import 'package:path/path.dart' as path; + +class ImageCompressionHelper { + /// Compress and resize image to max 200x200 and ensure file size < 1MB + /// Returns the compressed image file + static Future compressAndResizeImage(File imageFile) async { + try { + // Check if file exists + if (!await imageFile.exists()) { + print('Image file does not exist'); + return null; + } + + print('Original file size: ${await imageFile.length() / 1024} KB'); + + // Get temporary directory + final tempDir = await getTemporaryDirectory(); + final targetPath = path.join( + tempDir.path, + 'compressed_${DateTime.now().millisecondsSinceEpoch}.jpg', + ); + + // Start with quality 85 + int quality = 85; + const int maxFileSizeBytes = 1048576; // 1MB + + // Compress the image + var result = await FlutterImageCompress.compressWithFile( + imageFile.absolute.path, + quality: quality, + format: CompressFormat.jpeg, + ); + + if (result == null || result.isEmpty) { + print('Compression returned null or empty'); + return null; + } + + // Write to file + File resultFile = File(targetPath); + await resultFile.writeAsBytes(result); + + int fileSize = await resultFile.length(); + print('First compression: ${fileSize / 1024} KB, quality: $quality'); + + // If file size is still > 1MB, reduce quality progressively + int attempts = 0; + while (fileSize > maxFileSizeBytes && quality > 10 && attempts < 10) { + quality -= 10; + attempts++; + + result = await FlutterImageCompress.compressWithFile( + imageFile.absolute.path, + quality: quality, + format: CompressFormat.jpeg, + ); + + if (result == null || result.isEmpty) { + print('Compression returned null on attempt $attempts'); + break; + } + + await resultFile.writeAsBytes(result); + fileSize = await resultFile.length(); + print('Compression attempt $attempts: ${fileSize / 1024} KB, quality: $quality'); + } + + // Final validation + if (await resultFile.exists()) { + final finalSize = await resultFile.length(); + print('Final compressed image size: ${finalSize / 1024} KB'); + + if (finalSize > maxFileSizeBytes) { + print('Warning: Final file size (${finalSize / 1024} KB) exceeds 1MB limit'); + } + + return resultFile; + } + + return null; + } catch (e, stackTrace) { + print('Error compressing image: $e'); + print('Stack trace: $stackTrace'); + return null; + } + } + + /// Get file size in KB + static Future getFileSizeInKB(File file) async { + int bytes = await file.length(); + return bytes / 1024; + } + + /// Get file size in MB + static Future getFileSizeInMB(File file) async { + int bytes = await file.length(); + return bytes / (1024 * 1024); + } +} + diff --git a/lib/core/utils/push_notification_handler.dart b/lib/core/utils/push_notification_handler.dart index d438d449..e2925c29 100644 --- a/lib/core/utils/push_notification_handler.dart +++ b/lib/core/utils/push_notification_handler.dart @@ -396,10 +396,10 @@ class PushNotificationHandler { final deviceInfo = DeviceInfoPlugin(); final androidInfo = await deviceInfo.androidInfo; - int sdkInt = androidInfo.version.sdkInt ?? 0; - if (sdkInt >= 33) { - await FlutterCallkitIncoming.requestFullIntentPermission(); - } + // int sdkInt = androidInfo.version.sdkInt ?? 0; + // if (sdkInt >= 33) { + // await FlutterCallkitIncoming.requestFullIntentPermission(); + // } } } diff --git a/lib/core/utils/utils.dart b/lib/core/utils/utils.dart index 18a8d6c8..bc4b82c0 100644 --- a/lib/core/utils/utils.dart +++ b/lib/core/utils/utils.dart @@ -355,7 +355,7 @@ class Utils { repeat: false, reverse: false, frameRate: FrameRate(60), width: width.w, height: height.h, fit: BoxFit.fill), SizedBox(height: 16.h), (noDataText ?? LocaleKeys.noDataAvailable.tr()) - .toText14(weight: FontWeight.w500, color: AppColors.greyTextColor, isCenter: true) + .toText14(isBold: true, color: AppColors.greyTextColor, isCenter: true) .paddingSymmetrical(64.w, 0.h), SizedBox(height: 16.h), callToActionButton @@ -371,7 +371,7 @@ class Utils { Lottie.asset(AppAnimations.loadingAnimation, repeat: true, reverse: false, frameRate: FrameRate(60), width: 80.h, height: 80.h, fit: BoxFit.fill), SizedBox(height: 8.h), - (loadingText ?? LocaleKeys.loadingText.tr()).toText16(color: AppColors.blackColor, isCenter: true), + (loadingText ?? LocaleKeys.loadingText.tr()).toText16(color: AppColors.blackColor, isCenter: true, isBold: true), SizedBox(height: 8.h), ], ).center; @@ -384,7 +384,7 @@ class Utils { children: [ Lottie.asset(AppAnimations.checkmark, repeat: true, reverse: false, frameRate: FrameRate(60), width: 100.h, height: 100.h, fit: BoxFit.fill), SizedBox(height: 8.h), - (loadingText ?? LocaleKeys.loadingText.tr()).toText16(color: AppColors.blackColor).paddingSymmetrical(24.h, 0.h), + (loadingText ?? LocaleKeys.loadingText.tr()).toText16(color: AppColors.blackColor, isBold: true).paddingSymmetrical(24.h, 0.h), SizedBox(height: 8.h), ], ).center; @@ -404,6 +404,85 @@ class Utils { ).center; } + static Widget getOnlineCheckInWidget({ + String? loadingText, + bool isShowActionButtons = false, + bool showOkButton = false, + Widget? bodyWidget, + Function? onConfirmTap, + Function? onCancelTap, + String? cancelText, + String? confirmText, + }) { + return Column( + mainAxisAlignment: MainAxisAlignment.center, + crossAxisAlignment: CrossAxisAlignment.center, + children: [ + Utils.buildSvgWithAssets(icon: AppAssets.online_checkin_icon, width: 80.h, height: 80.h, applyThemeColor: false), + SizedBox(height: 16.h), + (loadingText ?? LocaleKeys.loadingText.tr()).toText16(color: AppColors.blackColor, letterSpacing: 0, weight: FontWeight.w600), + SizedBox(height: 16.h), + bodyWidget ?? SizedBox.shrink(), + SizedBox(height: 16.h), + isShowActionButtons + ? Row( + children: [ + Expanded( + child: CustomButton( + text: cancelText ?? LocaleKeys.cancel.tr(), + onPressed: () { + if (onCancelTap != null) { + onCancelTap(); + } + }, + backgroundColor: AppColors.primaryRedColor, + borderColor: AppColors.primaryRedColor, + textColor: AppColors.whiteColor, + // icon: AppAssets.cancel, + // iconColor: AppColors.whiteColor, + ), + ), + SizedBox(width: 8.h), + Expanded( + child: CustomButton( + text: confirmText ?? LocaleKeys.confirm.tr(), + onPressed: () async { + if (onConfirmTap != null) { + onConfirmTap(); + } + }, + backgroundColor: AppColors.bgGreenColor, + borderColor: AppColors.bgGreenColor, + textColor: Colors.white, + // icon: AppAssets.confirm, + ), + ), + ], + ) + : showOkButton + ? Row( + children: [ + Expanded( + child: CustomButton( + text: LocaleKeys.ok.tr(), + onPressed: () async { + if (onConfirmTap != null) { + onConfirmTap(); + } + }, + backgroundColor: AppColors.bgGreenColor, + borderColor: AppColors.bgGreenColor, + textColor: Colors.white, + // icon: AppAssets.confirm, + ), + ), + ], + ) + : SizedBox.shrink(), + ], + ).center; + } + static Widget getWarningWidget({ String? loadingText, bool isShowActionButtons = false, @@ -844,7 +923,8 @@ class Utils { ), TextSpan( // text: NumberFormat.currency(locale: 'en_US', symbol: " ", decimalDigits: 0).format(habibWalletAmount), - text: " ${habibWalletAmount.toStringAsFixed(2)}", + // text: " ${(NumberFormat.decimalPattern().format(habibWalletAmount)).toStringAsFixed(2)}", + text: " ${(NumberFormat.decimalPattern().format(habibWalletAmount))}", style: TextStyle( color: resolvedTextColor, fontSize: fontSize ?? 32.f, diff --git a/lib/extensions/string_extensions.dart b/lib/extensions/string_extensions.dart index a68bdcf1..5db5b289 100644 --- a/lib/extensions/string_extensions.dart +++ b/lib/extensions/string_extensions.dart @@ -24,7 +24,7 @@ extension CapExtension on String { extension EmailValidator on String { Widget get toWidget => Text(this); - Widget toText8({Color? color, FontWeight? fontWeight, bool isBold = false, int? maxlines, FontStyle? fontStyle, TextOverflow? textOverflow}) => Text( + Widget toText8({bool isEnglishOnly = false, Color? color, FontWeight? fontWeight, bool isBold = false, int? maxlines, FontStyle? fontStyle, TextOverflow? textOverflow}) => Text( this, maxLines: maxlines, overflow: textOverflow, @@ -34,6 +34,7 @@ extension EmailValidator on String { fontWeight: fontWeight ?? (isBold ? FontWeight.bold : FontWeight.normal), color: color ?? AppColors.blackColor, letterSpacing: 0, + fontFamily: getIt.get().getLanguageCode() == "ar" ? 'CairoArabic' : 'Poppins', ), ); @@ -60,7 +61,7 @@ extension EmailValidator on String { color: color ?? AppColors.blackColor, letterSpacing: letterSpacing, decoration: isUnderLine ? TextDecoration.underline : null, - fontFamily: isEnglishOnly ? "Poppins" : getIt.get().getLanguageCode() == "ar" ? 'GESSTwo' : 'Poppins', + fontFamily: getIt.get().getLanguageCode() == "ar" ? 'CairoArabic' : 'Poppins', decorationColor: color ?? AppColors.blackColor), ); @@ -80,7 +81,7 @@ extension EmailValidator on String { maxLines: maxlines, overflow: textOverflow, style: TextStyle( - fontFamily: isEnglishOnly ? "Poppins" : getIt.get().getLanguageCode() == "ar" ? 'GESSTwo' : 'Poppins', + fontFamily: getIt.get().getLanguageCode() == "ar" ? 'CairoArabic' : 'Poppins', fontSize: 9.f, fontStyle: fontStyle ?? FontStyle.normal, fontWeight: weight ?? (isBold ? FontWeight.bold : FontWeight.normal), @@ -101,7 +102,7 @@ extension EmailValidator on String { color: color ?? AppColors.blackColor, letterSpacing: letterSpacing, decoration: isUnderLine ? TextDecoration.underline : null, - fontFamily: isEnglishOnly ? "Poppins" : getIt.get().getLanguageCode() == "ar" ? 'GESSTwo' : 'Poppins', + fontFamily: getIt.get().getLanguageCode() == "ar" ? 'CairoArabic' : 'Poppins', ), ); @@ -127,7 +128,7 @@ extension EmailValidator on String { height: height, decorationColor: isUnderLine ? AppColors.blackColor : null, decoration: isUnderLine ? TextDecoration.underline : null, - fontFamily: isEnglishOnly ? "Poppins" : getIt.get().getLanguageCode() == "ar" ? 'GESSTwo' : 'Poppins', + fontFamily: getIt.get().getLanguageCode() == "ar" ? 'CairoArabic' : 'Poppins', ), ); @@ -210,7 +211,7 @@ extension EmailValidator on String { height: height, fontWeight: weight ?? (isBold ? FontWeight.bold : FontWeight.normal), decoration: isUnderLine ? TextDecoration.underline : null, - fontFamily: fontFamily ?? (isEnglishOnly ? "Poppins" : getIt.get().getLanguageCode() == "ar" ? 'GESSTwo' : 'Poppins'), + fontFamily: fontFamily ?? (isEnglishOnly ? "Poppins" : getIt.get().getLanguageCode() == "ar" ? 'CairoArabic' : 'Poppins'), decorationColor: color ?? AppColors.blackColor), ); @@ -251,14 +252,14 @@ extension EmailValidator on String { overflow: textOverflow, fontWeight: weight ?? (isBold ? FontWeight.bold : FontWeight.normal), decoration: isUnderLine ? TextDecoration.underline : null, - fontFamily: fontFamily ?? (isEnglishOnly ? "Poppins" : getIt.get().getLanguageCode() == "ar" ? 'GESSTwo' : 'Poppins'), + fontFamily: fontFamily ?? (isEnglishOnly ? "Poppins" : getIt.get().getLanguageCode() == "ar" ? 'CairoArabic' : 'Poppins'), decorationColor: decorationColor ?? AppColors.errorColor), ); Widget toText17({bool isEnglishOnly = false, Color? color, bool isBold = false, bool isCenter = false}) => Text( this, textAlign: isCenter ? TextAlign.center : null, - style: TextStyle(color: color ?? AppColors.blackColor, fontSize: 17.f, letterSpacing: -1, fontWeight: isBold ? FontWeight.bold : FontWeight.normal, fontFamily: isEnglishOnly ? "Poppins" : getIt.get().getLanguageCode() == "ar" ? 'GESSTwo' : 'Poppins'), + style: TextStyle(color: color ?? AppColors.blackColor, fontSize: 17.f, letterSpacing: -1, fontWeight: isBold ? FontWeight.bold : FontWeight.normal, fontFamily: getIt.get().getLanguageCode() == "ar" ? 'CairoArabic' : 'Poppins'), ); Widget toText18({Color? color, FontWeight? weight, bool isBold = false, bool isCenter = false, int? maxlines, TextOverflow? textOverflow, bool isEnglishOnly = false,}) => Text( @@ -266,7 +267,7 @@ extension EmailValidator on String { textAlign: isCenter ? TextAlign.center : null, this, overflow: textOverflow, - style: TextStyle(fontSize: 18.f, fontWeight: weight ?? (isBold ? FontWeight.bold : FontWeight.normal), color: color ?? AppColors.blackColor, letterSpacing: -0.4, fontFamily: (isEnglishOnly ? "Poppins" : getIt.get().getLanguageCode() == "ar" ? 'GESSTwo' : 'Poppins'),), + style: TextStyle(fontSize: 18.f, fontWeight: weight ?? (isBold ? FontWeight.bold : FontWeight.normal), color: color ?? AppColors.blackColor, letterSpacing: -0.4, fontFamily: (isEnglishOnly ? "Poppins" : getIt.get().getLanguageCode() == "ar" ? 'CairoArabic' : 'Poppins'),), ); Widget toText19({Color? color, bool isBold = false}) => Text( @@ -300,7 +301,7 @@ extension EmailValidator on String { this, textAlign: isCenter ? TextAlign.center : null, style: TextStyle( - fontFamily: (isEnglishOnly ? "Poppins" : getIt.get().getLanguageCode() == "ar" ? 'GESSTwo' : 'Poppins'), + fontFamily: (isEnglishOnly ? "Poppins" : getIt.get().getLanguageCode() == "ar" ? 'CairoArabic' : 'Poppins'), height: 23 / 24, color: color ?? AppColors.blackColor, fontSize: 24.f, letterSpacing: letterSpacing ?? -1, fontWeight: isBold ? FontWeight.bold : fontWeight ?? FontWeight.normal), ); @@ -308,26 +309,26 @@ extension EmailValidator on String { this, textAlign: isCenter ? TextAlign.center : null, style: TextStyle( - fontFamily: (isEnglishOnly ? "Poppins" : getIt.get().getLanguageCode() == "ar" ? 'GESSTwo' : 'Poppins'), + fontFamily: (isEnglishOnly ? "Poppins" : getIt.get().getLanguageCode() == "ar" ? 'CairoArabic' : 'Poppins'), height: height ?? 23 / 26, color: color ?? AppColors.blackColor, fontSize: 26.f, letterSpacing: letterSpacing ?? -1, fontWeight: weight ?? (isBold ? FontWeight.bold : FontWeight.normal)), ); Widget toText28({bool isEnglishOnly = false, Color? color, bool isBold = false, double? height, bool isCenter = false, double? letterSpacing}) => Text( this, textAlign: isCenter ? TextAlign.center : null, - style: TextStyle(height: height ?? 23 / 28, color: color ?? AppColors.blackColor, fontSize: 28.f, letterSpacing: letterSpacing ?? -1, fontWeight: isBold ? FontWeight.bold : FontWeight.normal, fontFamily: isEnglishOnly ? "Poppins" : getIt.get().getLanguageCode() == "ar" ? 'GESSTwo' : 'Poppins'), + style: TextStyle(height: height ?? 23 / 28, color: color ?? AppColors.blackColor, fontSize: 28.f, letterSpacing: letterSpacing ?? -1, fontWeight: isBold ? FontWeight.bold : FontWeight.normal, fontFamily: getIt.get().getLanguageCode() == "ar" ? 'CairoArabic' : 'Poppins'), ); Widget toText32({bool isEnglishOnly = false, FontWeight? weight, Color? color, bool isBold = false, bool isCenter = false}) => Text( this, textAlign: isCenter ? TextAlign.center : null, style: TextStyle( - height: 32 / 32, color: color ?? AppColors.blackColor, fontSize: 32.f, letterSpacing: -1, fontFamily: isEnglishOnly ? "Poppins" : getIt.get().getLanguageCode() == "ar" ? 'GESSTwo' : 'Poppins', fontWeight: isBold ? FontWeight.bold : weight ?? FontWeight.normal), + height: 32 / 32, color: color ?? AppColors.blackColor, fontSize: 32.f, letterSpacing: -1, fontFamily: getIt.get().getLanguageCode() == "ar" ? 'CairoArabic' : 'Poppins', fontWeight: isBold ? FontWeight.bold : weight ?? FontWeight.normal), ); Widget toText44({Color? color, bool isBold = false, bool isEnglishOnly = false,}) => Text( this, - style: TextStyle(height: 32 / 32, color: color ?? AppColors.blackColor, fontSize: 44.f, letterSpacing: -1, fontWeight: isBold ? FontWeight.bold : FontWeight.normal, fontFamily: (isEnglishOnly ? "Poppins" : getIt.get().getLanguageCode() == "ar" ? 'GESSTwo' : 'Poppins'),), + style: TextStyle(height: 32 / 32, color: color ?? AppColors.blackColor, fontSize: 44.f, letterSpacing: -1, fontWeight: isBold ? FontWeight.bold : FontWeight.normal, fontFamily: (isEnglishOnly ? "Poppins" : getIt.get().getLanguageCode() == "ar" ? 'CairoArabic' : 'Poppins'),), ); Widget toSectionHeading({String upperHeading = "", String lowerHeading = ""}) { @@ -433,7 +434,7 @@ extension DynamicTextStyleExtension on BuildContext { String? fontFamily, bool isLanguageSwitcher = false}) { AppState appState = getIt.get(); - final family = appState.getLanguageCode() == "ar" ? 'GESSTwo' : 'Poppins'; + final family = appState.getLanguageCode() == "ar" ? 'CairoArabic' : 'Poppins'; return TextStyle( fontFamily: fontFamily ?? family, fontSize: fontSize, @@ -464,7 +465,7 @@ class FontUtils { /// Get the appropriate font family for a specific language static String getFontFamilyForLanguage(bool isArabic) { - return isArabic ? 'GESSTwo' : 'Poppins'; + return isArabic ? 'CairoArabic' : 'Poppins'; } } diff --git a/lib/extensions/widget_extensions.dart b/lib/extensions/widget_extensions.dart index 866c26a4..3c0079f2 100644 --- a/lib/extensions/widget_extensions.dart +++ b/lib/extensions/widget_extensions.dart @@ -155,6 +155,7 @@ extension SmoothContainerExtension on ShapeBorder { BorderRadius? customBorder, bool hasShadow = false, bool hasDenseShadow = false, + List? isCustomShadow, }) { final bgColor = backgroundColor ?? color; return ShapeDecoration( @@ -174,7 +175,7 @@ extension SmoothContainerExtension on ShapeBorder { offset: const Offset(1, 0), ) ] - : [], + : isCustomShadow ?? [], ); } } diff --git a/lib/features/authentication/authentication_repo.dart b/lib/features/authentication/authentication_repo.dart index cd8ad70d..c355819e 100644 --- a/lib/features/authentication/authentication_repo.dart +++ b/lib/features/authentication/authentication_repo.dart @@ -6,6 +6,7 @@ import 'package:hmg_patient_app_new/core/api_consts.dart'; import 'package:hmg_patient_app_new/core/app_state.dart'; import 'package:hmg_patient_app_new/core/common_models/generic_api_model.dart'; import 'package:hmg_patient_app_new/core/dependencies.dart'; +import 'package:hmg_patient_app_new/core/enums.dart'; import 'package:hmg_patient_app_new/core/exceptions/api_failure.dart'; import 'package:hmg_patient_app_new/features/authentication/models/resp_models/select_device_by_imei.dart'; import 'package:hmg_patient_app_new/services/logger_service.dart'; @@ -214,56 +215,6 @@ class AuthenticationRepoImp implements AuthenticationRepo { int? patientID, int? loginType}) async { AppState appState = getIt.get(); - // if (isRegister) { - // newRequest["activationCode"] = activationCode ?? "0000"; - // newRequest["isSilentLogin"] = activationCode != null ? false : true; - // } else { - // newRequest.activationCode = activationCode ?? "0000"; - // newRequest.isSilentLogin = activationCode != null ? false : true; - // newRequest.projectOutSA = newRequest.zipCode == '966' ? false : true; - // newRequest.isDentalAllowedBackend = false; - // newRequest.forRegisteration = newRequest.isRegister ?? false; - // newRequest.isRegister = false; - // } - // Map familyRequest = {}; - // if (isFormFamilyFile) { - // AppState appState = getIt.get(); - // familyRequest = {}; - // familyRequest['PatientShareRequestID'] = patientShareRequestID; - // familyRequest['ResponseID'] = responseID; - // familyRequest['Status'] = 3; - // familyRequest["PatientID"] = appState.getAuthenticatedUser()!.patientId ?? 0; - // familyRequest["LogInTokenID"] = appState.getFamilyFileTokenID; - // familyRequest["activationCode"] = activationCode ?? "0000"; - // familyRequest["PatientMobileNumber"] = newRequest.patientMobileNumber; - // familyRequest["PatientIdentificationID"] = newRequest.patientIdentificationID; - // } - // Map switchRequest = {}; - // if (isSwitchUser) { - // switchRequest = newRequest.toJson(); - // - // switchRequest['PatientID'] = responseID; - // switchRequest['IsSilentLogin'] = true; - // switchRequest['LogInTokenID'] = null; - // switchRequest['SearchType'] = 2; - // if (loginType != 0) { - // switchRequest['SuperUser'] = patientID; - // switchRequest['DeviceToken'] = null; - // } else { - // switchRequest["LoginType"] = 2; - // } - // - // if (appState.getSuperUserID == responseID) { - // // switchRequest['LoginType'] = 3; - // switchRequest['PatientIdentificationID'] = ""; - // // switchRequest['ProjectOutSA'] = newRequest.zipCode == '966' ? false : true; - // switchRequest.remove('NationalID'); - // switchRequest.remove('isDentalAllowedBackend'); - // switchRequest.remove('ProjectOutSA'); - // switchRequest.remove('ForRegisteration'); - // appState.setSuperUserID = null; - // } - // } if (isRegister) { newRequest["activationCode"] = activationCode ?? "0000"; @@ -321,7 +272,6 @@ class AuthenticationRepoImp implements AuthenticationRepo { } } - // Determine which API to use based on zipCode final zipCode = _getZipCodeFromRequest(newRequest); final isOthersCountry = zipCode == '0'; final endpoint = isFormFamilyFile @@ -336,15 +286,44 @@ class AuthenticationRepoImp implements AuthenticationRepo { GenericApiModel? apiResponse; Failure? failure; + // Determine the payload to send + var requestBody = isFormFamilyFile + ? familyRequest + : isRegister + ? newRequest + : isSwitchUser + ? switchRequest + : newRequest.toJson(); + + if (isSwitchUser) { + Map allUsersPayload; + var switchingPatientID; + var superUserPatientID; + bool isSwitchingToSuperUser = false; + + allUsersPayload = appState.getUserSwitchPayloads; + switchingPatientID = switchRequest["PatientID"]; + superUserPatientID = allUsersPayload['superUser']['PatientID']; + isSwitchingToSuperUser = (appState.getSuperUserID == responseID) || (allUsersPayload.containsKey('superUser') && superUserPatientID == switchingPatientID); + + if (isSwitchingToSuperUser) { + var superUserPayload = appState.getUserSwitchPayloadByIndex(isSuperUser: true); + if (superUserPayload != null) { + requestBody['DeviceToken'] = newRequest.deviceToken; + requestBody['ProjectOutSA'] = 0; + requestBody['PatientIdentificationID'] = ""; + requestBody.removeWhere((key, value) => ['NationalID', 'isDentalAllowedBackend', 'ForRegisteration'].contains(key)); + appState.setIsChildLoggedIn = false; + appState.clearChildUsersKeepSuperUser(); + } + } else { + appState.setIsChildLoggedIn = true; + } + } + await apiClient.post( endpoint, - body: isFormFamilyFile - ? familyRequest - : isRegister - ? newRequest - : isSwitchUser - ? switchRequest - : newRequest.toJson(), + body: requestBody, onFailure: (error, statusCode, {messageStatus, failureType}) { failure = failureType; }, @@ -611,7 +590,7 @@ class AuthenticationRepoImp implements AuthenticationRepo { } @override - Future>> getServicePrivilege() { + Future> getServicePrivilege() { Map mapDevice = {}; try { GenericApiModel? apiResponse; @@ -692,6 +671,7 @@ class AuthenticationRepoImp implements AuthenticationRepo { @override Future>> getPatientBloodType() async { + // Map requestBody = {"PatientType": 1, "PatientTypeID": 1, "TokenID": "@dm!n", "PatientID": 5957754}; Map requestBody = {}; try { GenericApiModel? apiResponse; diff --git a/lib/features/authentication/authentication_view_model.dart b/lib/features/authentication/authentication_view_model.dart index 8d313774..45c2d620 100644 --- a/lib/features/authentication/authentication_view_model.dart +++ b/lib/features/authentication/authentication_view_model.dart @@ -47,6 +47,7 @@ import 'package:hmg_patient_app_new/widgets/routes/custom_page_route.dart'; import 'package:sms_otp_auto_verify/sms_otp_auto_verify.dart'; import '../insurance/insurance_view_model.dart'; +import '../profile_settings/profile_settings_repo.dart'; import 'models/request_models/get_user_mobile_device_data.dart'; import 'models/request_models/insert_patient_mobile_deviceinfo.dart'; import 'models/request_models/patient_insert_device_imei_request.dart'; @@ -101,7 +102,10 @@ class AuthenticationViewModel extends ChangeNotifier { var healthId; int getDeviceLastLogin = 1; + bool isInitialLoginNavigated = false; + Future onLoginPressed() async { + setIsInitialLoginNavigated(false); try { LoaderBottomSheet.showLoader(); //TODO: We will remove this delay @@ -111,7 +115,7 @@ class AuthenticationViewModel extends ChangeNotifier { if (data != null) { await _handleExistingImeiData(data); } else { - await _handleNewImeiRegistration(); + await handleNewImeiRegistration(); } } catch (e) { log("Error in onLoginPressed: $e"); @@ -199,6 +203,11 @@ class AuthenticationViewModel extends ChangeNotifier { } } + setIsInitialLoginNavigated(bool isInitialLoginNavigated) { + this.isInitialLoginNavigated = isInitialLoginNavigated; + notifyListeners(); + } + void onTermAccepted() { isTermsAccepted = !isTermsAccepted; notifyListeners(); @@ -216,7 +225,7 @@ class AuthenticationViewModel extends ChangeNotifier { _appState.deviceToken = await Utils.getStringFromPrefs(CacheConst.pushToken); } - Future selectDeviceImei({required Function(dynamic data) onSuccess, Function(String)? onError}) async { + Future selectDeviceImei({bool isNavigate = true, required Function(dynamic data) onSuccess, Function(String)? onError}) async { // LoadingUtils.showFullScreenLoading(); // String firebaseToken = _appState.deviceToken; String firebaseToken = await Utils.getStringFromPrefs(CacheConst.pushToken); @@ -231,7 +240,9 @@ class AuthenticationViewModel extends ChangeNotifier { // LoadingUtils.hideFullScreenLoader(); // await _errorHandlerService.handleError(failure: failure); LoaderBottomSheet.hideLoader(); - _navigationService.pushPage(page: LoginScreen()); + if (isNavigate) { + _navigationService.pushPage(page: LoginScreen()); + } }, (apiResponse) { // LoadingUtils.hideFullScreenLoader(); @@ -263,33 +274,42 @@ class AuthenticationViewModel extends ChangeNotifier { } } - Future _handleNewImeiRegistration() async { - await selectDeviceImei(onSuccess: (dynamic respData) async { - try { - if (respData != null) { - dynamic data = await SelectDeviceByImeiRespModelElement.fromJson(respData.toJson()); - _appState.setSelectDeviceByImeiRespModelElement(data); - LoaderBottomSheet.hideLoader(); + Future handleNewImeiRegistration({bool isNavigate = true}) async { + print("handleNewImeiRegistration called"); + await selectDeviceImei( + isNavigate: isNavigate, + onSuccess: (dynamic respData) async { + try { + if (respData != null) { + dynamic data = await SelectDeviceByImeiRespModelElement.fromJson(respData.toJson()); + _appState.setSelectDeviceByImeiRespModelElement(data); + LoaderBottomSheet.hideLoader(); - // TODO: Navigate to SavedLogin when available - // SelectDeviceByImeiRespModelElement savedData = - // SelectDeviceByImeiRespModelElement.fromJson(respData); - _navigationService.pushPage(page: SavedLogin()); - // _navigationService.pushPage(page: LoginScreen()); - } else { - print("print login........"); + // TODO: Navigate to SavedLogin when available + // SelectDeviceByImeiRespModelElement savedData = + // SelectDeviceByImeiRespModelElement.fromJson(respData); + if (!isInitialLoginNavigated) { + _navigationService.pushPage(page: SavedLogin()); + } + // _navigationService.pushPage(page: LoginScreen()); + } else { + if (isNavigate) { + LoaderBottomSheet.hideLoader(); + _navigationService.pushPage(page: LoginScreen()); + } + } + } catch (e) { + if (isNavigate) { + log("Error processing IMEI registration response: $e"); + LoaderBottomSheet.hideLoader(); + _navigationService.pushPage(page: LoginScreen()); + } + } + }, + onError: (String error) { LoaderBottomSheet.hideLoader(); - _navigationService.pushPage(page: LoginScreen()); - } - } catch (e) { - log("Error processing IMEI registration response: $e"); - LoaderBottomSheet.hideLoader(); - _navigationService.pushPage(page: LoginScreen()); - } - }, onError: (String error) { - LoaderBottomSheet.hideLoader(); - _dialogService.showErrorBottomSheet(message: error, onOkPressed: () {}); - }); + _dialogService.showErrorBottomSheet(message: error, onOkPressed: () {}); + }); } Future checkUserAuthentication({required OTPTypeEnum otpTypeEnum, Function(dynamic)? onSuccess, Function(String)? onError}) async { @@ -385,7 +405,7 @@ class AuthenticationViewModel extends ChangeNotifier { mobileNumber: phoneNumber, selectedLoginType: otpTypeEnum.toInt(), zipCode: selectedCountrySignup.countryCode, - nationalId:nationalIdOrFileNumber, + nationalId: nationalIdOrFileNumber, isFileNo: isForRegister ? isPatientHasFile(request: payload) : false, patientId: isFormFamilyFile ? _appState.getAuthenticatedUser()!.patientId : 0, isForRegister: isForRegister, @@ -611,17 +631,35 @@ class AuthenticationViewModel extends ChangeNotifier { } else { if (activation.list != null && activation.list!.isNotEmpty) { if (isSwitchUser) { + // Clear profile image cache when switching users + _appState.clearProfileImageCache(); + log("🔄 Switching user - cleared profile image cache"); + if (_appState.getIsChildLoggedIn) { _appState.setSuperUserID = null; - _appState.setIsChildLoggedIn = false; - activation.list!.first.isParentUser = true; - } else { - _appState.setSuperUserID = _appState.getAuthenticatedUser()?.patientId; - _appState.setIsChildLoggedIn = true; + // _appState.setIsChildLoggedIn = false; activation.list!.first.isParentUser = false; + var allUsersPayload = _appState.getUserSwitchPayloads; + if ((allUsersPayload.containsKey('superUser') && allUsersPayload['superUser'] != null)) { + _appState.addUserSwitchPayload(activation.list!.first.toJson(), isSuperUser: false); + } else { + _appState.addUserSwitchPayload(activation.list!.first.toJson(), isSuperUser: true); + } + } else { + var allUsersPayload = _appState.getUserSwitchPayloads; + _appState.setSuperUserID = allUsersPayload['superUser']?['PatientID']; + activation.list!.first.isParentUser = true; + _appState.addUserSwitchPayload(activation.list!.first.toJson(), isSuperUser: true); } + + // Fetch profile image after switching user + await _fetchProfileImage(activation.list!.first.patientId); } else { activation.list!.first.isParentUser = true; + _appState.addUserSwitchPayload(activation.list!.first.toJson(), isSuperUser: true); + + // Fetch profile image after login + await _fetchProfileImage(activation.list!.first.patientId); } activation.list!.first.bloodGroup = activation.patientBloodType; activation.list!.first.zipCode = selectedCountrySignup == CountryEnum.others ? '0' : selectedCountrySignup.countryCode; @@ -629,8 +667,14 @@ class AuthenticationViewModel extends ChangeNotifier { _appState.setPrivilegeModelList(activation.list!.first.listPrivilege!); _appState.setUserBloodGroup = activation.patientBlodType ?? "N/A"; + // Refresh privileges after user switching to ensure correct privileges are loaded + if (isSwitchUser) { + print("🔄 Switching user - refreshing privileges"); + await getServicePrivilege(); + } + // Fetch patient blood type from new API - await getPatientBloodTypeNew(); + // await getPatientBloodTypeNew(); } // _appState.setUserBloodGroup = (activation.patientBlodType ?? ""); _appState.setAppAuthToken = activation.authenticationTokenId; @@ -722,6 +766,8 @@ class AuthenticationViewModel extends ChangeNotifier { } Future navigateToHomeScreen() async { + setIsInitialLoginNavigated(true); + getPatientBloodTypeNew(); Navigator.pushAndRemoveUntil( _navigationService.navigatorKey.currentContext!, CustomPageRoute( @@ -1018,7 +1064,9 @@ class AuthenticationViewModel extends ChangeNotifier { nationalId: _appState.getAuthenticatedUser()!.patientIdentificationNo, gender: _appState.getAuthenticatedUser()!.gender) .toJson()); - resultEither.fold((failure) async => await _errorHandlerService.handleError(failure: failure), (apiResponse) async { + resultEither.fold((failure) async { + // await _errorHandlerService.handleError(failure: failure); + }, (apiResponse) async { if (apiResponse.messageStatus == 2) {} if (apiResponse.messageStatus == 1) { log("Insert Device Data Success"); @@ -1141,6 +1189,7 @@ class AuthenticationViewModel extends ChangeNotifier { _appState.setIsAuthenticated = false; _appState.setAppAuthToken = ""; _appState.setUserBloodGroup = ""; + _appState.clearProfileImageCache(); // Clear profile image cache _appState.setSuperUserID = null; _appState.setIsChildLoggedIn = false; _appState.setFamilyFileTokenID = ""; @@ -1152,6 +1201,7 @@ class AuthenticationViewModel extends ChangeNotifier { _appState.setVidaPlusProjectList([]); _appState.setHMCProjectList([]); _appState.setProjectsDetailList([]); + _appState.clearUserSwitchPayloads(); await clearDefaultInputValues(); _navigationService.pushAndReplace(AppRoutes.landingScreen); } catch (e) { @@ -1171,7 +1221,7 @@ class AuthenticationViewModel extends ChangeNotifier { (apiResponse) { if (apiResponse.messageStatus == 1 && apiResponse.data != null) { // Extract blood type from response - String? bloodType = apiResponse.data['GetPatientBloodType']; + String? bloodType = apiResponse.data['GetPatientBloodType'][0]["BloodType"]; if (bloodType != null && bloodType.isNotEmpty) { _appState.setUserBloodGroup = bloodType; log("Blood type updated from new API: $bloodType"); @@ -1183,4 +1233,42 @@ class AuthenticationViewModel extends ChangeNotifier { log("Error calling getPatientBloodType: $e"); } } + + /// Fetch profile image after login and store in AppState + Future _fetchProfileImage(int? patientID) async { + if (patientID == null) return; + + try { + log("Fetching profile image for patient ID: $patientID"); + final profileSettingsRepo = GetIt.instance(); + final result = await profileSettingsRepo.getProfileImage(patientID: patientID); + + result.fold( + (failure) { + // Silently fail - profile image is optional + log("Failed to fetch profile image: ${failure.message}"); + }, + (apiResponse) { + if (apiResponse.data != null && apiResponse.data['Patient_GetProfileImageDataList'] != null) { + var imageList = apiResponse.data['Patient_GetProfileImageDataList']; + if (imageList is List && imageList.isNotEmpty) { + String? imageData = imageList[0]['ImageData']; + if (imageData != null && imageData.isNotEmpty) { + _appState.setProfileImageData = imageData; + log("✅ Profile image loaded and cached successfully"); + } else { + log("⚠️ Profile image data is empty"); + } + } else { + log("⚠️ Profile image list is empty"); + } + } else { + log("⚠️ No profile image data in response"); + } + }, + ); + } catch (e) { + log("❌ Error fetching profile image: $e"); + } + } } diff --git a/lib/features/blood_donation/blood_donation_view_model.dart b/lib/features/blood_donation/blood_donation_view_model.dart index 8ad5bdec..7aa67d4f 100644 --- a/lib/features/blood_donation/blood_donation_view_model.dart +++ b/lib/features/blood_donation/blood_donation_view_model.dart @@ -1,11 +1,14 @@ import 'package:easy_localization/easy_localization.dart'; import 'package:flutter/material.dart'; +import 'package:get_it/get_it.dart'; import 'package:hmg_patient_app_new/core/app_state.dart'; import 'package:hmg_patient_app_new/core/dependencies.dart'; import 'package:hmg_patient_app_new/core/enums.dart'; import 'package:hmg_patient_app_new/core/utils/doctor_response_mapper.dart'; +import 'package:hmg_patient_app_new/core/utils/size_utils.dart'; import 'package:hmg_patient_app_new/core/utils/utils.dart'; import 'package:hmg_patient_app_new/extensions/string_extensions.dart'; +import 'package:hmg_patient_app_new/extensions/widget_extensions.dart'; import 'package:hmg_patient_app_new/features/authentication/authentication_view_model.dart'; import 'package:hmg_patient_app_new/features/blood_donation/blood_donation_repo.dart'; import 'package:hmg_patient_app_new/features/blood_donation/models/blood_group_hospitals_model.dart'; @@ -20,6 +23,7 @@ import 'package:hmg_patient_app_new/generated/locale_keys.g.dart'; import 'package:hmg_patient_app_new/services/dialog_service.dart'; import 'package:hmg_patient_app_new/services/error_handler_service.dart'; import 'package:hmg_patient_app_new/services/navigation_service.dart'; +import 'package:hmg_patient_app_new/widgets/common_bottom_sheet.dart'; import 'package:hmg_patient_app_new/widgets/loader/bottomsheet_loader.dart'; class BloodDonationViewModel extends ChangeNotifier { @@ -263,7 +267,13 @@ class BloodDonationViewModel extends ChangeNotifier { await bloodDonationRepo.updateBloodGroup(request: payload); await addUserAgreementForBloodDonation(); LoaderBottomSheet.hideLoader(); - dialogService.showSuccessBottomSheetWithoutH(message: "Blood Group Updated Successfully", onOkPressed: () {}); + showCommonBottomSheetWithoutHeight(GetIt.instance().navigatorKey.currentContext!, + child: Utils.getSuccessWidget(loadingText: "Blood Group Updated Successfully").paddingSymmetrical(0.h, 24.h), + callBackFunc: () {}, + isFullScreen: false, + isCloseButtonVisible: false, + isAutoDismiss: true); + // dialogService.showSuccessBottomSheetWithoutH(message: "Blood Group Updated Successfully", onOkPressed: () {}); } Future addUserAgreementForBloodDonation() async { diff --git a/lib/features/blood_donation/widgets/hospital_selection.dart b/lib/features/blood_donation/widgets/hospital_selection.dart index f49b42d6..c7313228 100644 --- a/lib/features/blood_donation/widgets/hospital_selection.dart +++ b/lib/features/blood_donation/widgets/hospital_selection.dart @@ -26,7 +26,7 @@ class HospitalBottomSheetBodySelection extends StatelessWidget { return Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ - if(!isHideTitle) LocaleKeys.selectHospital.tr(context: context).toText16(weight: FontWeight.w500, color: AppColors.greyTextColor), + if(!isHideTitle) LocaleKeys.selectHospital.tr(context: context).toText16(isBold: true, color: AppColors.greyTextColor), SizedBox(height: 16.h), SizedBox( height: MediaQuery.sizeOf(context).height * .4, @@ -75,7 +75,8 @@ class HospitalBottomSheetBodySelection extends StatelessWidget { children: [ Utils.buildSvgWithAssets(icon: AppAssets.hmg).paddingOnly(right: 10), Expanded( - child: Text(hospital.projectName ?? "", style: TextStyle(fontWeight: FontWeight.w600, fontSize: 16.f, color: AppColors.blackColor)), + child: (hospital.projectName ?? "").toString().toText16(isBold: true, color: AppColors.blackColor), + // Text(hospital.projectName ?? "", style: TextStyle(isBold: true, fontSize: 16.f, color: AppColors.blackColor)), ) ], ); diff --git a/lib/features/book_appointments/book_appointments_repo.dart b/lib/features/book_appointments/book_appointments_repo.dart index 7a984399..0db37274 100644 --- a/lib/features/book_appointments/book_appointments_repo.dart +++ b/lib/features/book_appointments/book_appointments_repo.dart @@ -86,7 +86,7 @@ abstract class BookAppointmentsRepo { {required int projectID, required int clinicID, required int patientID, Function(dynamic)? onSuccess, Function(String)? onError}); Future>>> getDentalChiefComplaintDoctorsList(int projectID, int chiefComplaintID, - {Function(dynamic)? onSuccess, Function(String)? onError}); + {bool isGetNearestAppointments, Function(dynamic)? onSuccess, Function(String)? onError}); Future>>> getLaserClinics(int laserCategoryID, int projectID, int languageID, {Function(dynamic)? onSuccess, Function(String)? onError}); @@ -877,12 +877,13 @@ class BookAppointmentsRepoImp implements BookAppointmentsRepo { @override Future>>> getDentalChiefComplaintDoctorsList(int projectID, int chiefComplaintID, - {Function(dynamic)? onSuccess, Function(String)? onError}) async { + {bool isGetNearestAppointments = false, Function(dynamic)? onSuccess, Function(String)? onError}) async { Map mapDevice = { "ProjectID": projectID, "ChiefComplaintID": chiefComplaintID, "isDentalAllowedBackend": true, "IsPublicRequest": true, + "IsGetNearAppointment": isGetNearestAppointments }; try { diff --git a/lib/features/book_appointments/book_appointments_view_model.dart b/lib/features/book_appointments/book_appointments_view_model.dart index 327afeb0..7ac6ae45 100644 --- a/lib/features/book_appointments/book_appointments_view_model.dart +++ b/lib/features/book_appointments/book_appointments_view_model.dart @@ -331,6 +331,42 @@ class BookAppointmentsViewModel extends ChangeNotifier { notifyListeners(); } + void filterDoctors(String query) { + if (query.isEmpty) { + filteredDoctorList = List.from(doctorsList); + _groupDoctorsList(); + setIsNearestAppointmentSelected(isNearestAppointmentSelected); + notifyListeners(); + return; + } + + filteredDoctorList = doctorsList.where((doctor) { + final name = (doctor.name ?? '').toLowerCase(); + final clinicName = (doctor.clinicName ?? '').toLowerCase(); + final projectName = (doctor.projectName ?? '').toLowerCase(); + final q = query.toLowerCase(); + return name.contains(q) || clinicName.contains(q) || projectName.contains(q); + }).toList(); + + // Re-group filtered list + final clinicMap = >{}; + final hospitalMap = >{}; + + for (var doctor in filteredDoctorList) { + final clinicKey = (doctor.clinicName ?? 'Unknown').trim(); + clinicMap.putIfAbsent(clinicKey, () => []).add(doctor); + + final hospitalKey = (doctor.projectName ?? 'Unknown').trim(); + hospitalMap.putIfAbsent(hospitalKey, () => []).add(doctor); + } + + doctorsListByClinic = clinicMap.values.toList(); + doctorsListByHospital = hospitalMap.values.toList(); + doctorsListGrouped = isSortByClinic ? doctorsListByClinic : doctorsListByHospital; + + notifyListeners(); + } + void filterClinics(String? query) { if (query!.isEmpty) { _filteredClinicsList = List.from(clinicsList); @@ -364,9 +400,17 @@ class BookAppointmentsViewModel extends ChangeNotifier { notifyListeners(); } + refreshDoctorsList() { + setIsDoctorsListLoading(true); + if(selectedClinic.clinicID == 17) { + getDentalChiefComplaintDoctorsList(projectID: currentlySelectedHospitalFromRegionFlow != null ? int.parse(currentlySelectedHospitalFromRegionFlow!) : 0); + } else { + getDoctorsList(isNearest: isNearestAppointmentSelected); + } + } + setIsNearestAppointmentSelected(bool isNearestAppointmentSelected) { this.isNearestAppointmentSelected = isNearestAppointmentSelected; - if (isNearestAppointmentSelected) { for (var group in doctorsListGrouped) { group.sort((a, b) { @@ -610,7 +654,7 @@ class BookAppointmentsViewModel extends ChangeNotifier { } //TODO: Make the API dynamic with parameters for ProjectID, isNearest, languageID, doctorId, doctorName - Future getDoctorsList({int projectID = 0, bool isNearest = true, int doctorId = 0, String doctorName = "", Function(dynamic)? onSuccess, Function(String)? onError}) async { + Future getDoctorsList({int projectID = 0, bool isNearest = false, int doctorId = 0, String doctorName = "", Function(dynamic)? onSuccess, Function(String)? onError}) async { doctorsList.clear(); filteredDoctorList.clear(); doctorsListGrouped.clear(); @@ -638,7 +682,7 @@ class BookAppointmentsViewModel extends ChangeNotifier { clearSearchFilters(); getFiltersFromDoctorList(); _groupDoctorsList(); - setIsNearestAppointmentSelected(true); + setIsNearestAppointmentSelected(isNearest); notifyListeners(); if (onSuccess != null) { onSuccess(apiResponse); @@ -1004,7 +1048,8 @@ class BookAppointmentsViewModel extends ChangeNotifier { ); LoaderBottomSheet.showLoader( - loadingText: LocaleKeys.reschedulingAppo.tr(context: navigationService.navigatorKey.currentContext!), + // loadingText: LocaleKeys.reschedulingAppo.tr(context: navigationService.navigatorKey.currentContext!), + loadingText: LocaleKeys.loadingText.tr(context: navigationService.navigatorKey.currentContext!), ); await cancelAppointment(patientAppointmentHistoryResponseModel: patientAppointmentHistoryResponseModel).then((val) async { LoaderBottomSheet.hideLoader(); @@ -1427,8 +1472,10 @@ class BookAppointmentsViewModel extends ChangeNotifier { Future getDentalChiefComplaintDoctorsList({int projectID = 0, Function(dynamic)? onSuccess, Function(String)? onError}) async { doctorsList.clear(); + notifyListeners(); + projectID = currentlySelectedHospitalFromRegionFlow != null ? int.parse(currentlySelectedHospitalFromRegionFlow!) : projectID; - final result = await bookAppointmentsRepo.getDentalChiefComplaintDoctorsList(projectID, selectedChiefComplaintID); + final result = await bookAppointmentsRepo.getDentalChiefComplaintDoctorsList(projectID, selectedChiefComplaintID, isGetNearestAppointments: isNearestAppointmentSelected); result.fold( (failure) async { @@ -1445,6 +1492,7 @@ class BookAppointmentsViewModel extends ChangeNotifier { // clearSearchFilters(); // getFiltersFromDoctorList(); _groupDoctorsList(); + setIsNearestAppointmentSelected(isNearestAppointmentSelected); notifyListeners(); if (onSuccess != null) { onSuccess(apiResponse); diff --git a/lib/features/book_appointments/models/resp_models/doctors_list_response_model.dart b/lib/features/book_appointments/models/resp_models/doctors_list_response_model.dart index 75ed1229..a9aa273e 100644 --- a/lib/features/book_appointments/models/resp_models/doctors_list_response_model.dart +++ b/lib/features/book_appointments/models/resp_models/doctors_list_response_model.dart @@ -193,7 +193,7 @@ class DoctorsListResponseModel { serviceID = json['ServiceID']; setupID = json['SetupID']; speciality = json['Speciality'] != null ? json['Speciality'].cast() : []; - // specialityN = json['SpecialityN'].cast(); + specialityN = json['SpecialityN'] != null ? json['SpecialityN'].cast() : []; transactionType = json['TransactionType']; virtualEmploymentType = json['VirtualEmploymentType']; workingHours = json['WorkingHours']; diff --git a/lib/features/emergency_services/emergency_services_repo.dart b/lib/features/emergency_services/emergency_services_repo.dart index f8eb896f..10c5541a 100644 --- a/lib/features/emergency_services/emergency_services_repo.dart +++ b/lib/features/emergency_services/emergency_services_repo.dart @@ -183,7 +183,7 @@ class EmergencyServicesRepoImp implements EmergencyServicesRepo { @override Future>>> getProjectList() async { - Map request = {'IsOnlineCheckIn': false, 'IsAdvancePayment': false, 'IsForER': true}; + Map request = {'IsOnlineCheckIn': true, 'IsAdvancePayment': false, 'IsForER': true}; try { GenericApiModel>? apiResponse; diff --git a/lib/features/emergency_services/emergency_services_view_model.dart b/lib/features/emergency_services/emergency_services_view_model.dart index bfe226f8..c8a16455 100644 --- a/lib/features/emergency_services/emergency_services_view_model.dart +++ b/lib/features/emergency_services/emergency_services_view_model.dart @@ -713,7 +713,7 @@ class EmergencyServicesViewModel extends ChangeNotifier { appointments = response.data; if (appointments?.isEmpty == true) { haveAnAppointment = false; - dialogService.showErrorBottomSheet(message: "Unable to Fetch Appointment"); + dialogService.showErrorBottomSheet(message: LocaleKeys.NoBookedAppointments.tr()); } notifyListeners(); }); diff --git a/lib/features/hmg_services/hmg_services_view_model.dart b/lib/features/hmg_services/hmg_services_view_model.dart index 7b32cc6e..10b340fd 100644 --- a/lib/features/hmg_services/hmg_services_view_model.dart +++ b/lib/features/hmg_services/hmg_services_view_model.dart @@ -1,4 +1,5 @@ import 'dart:convert'; +import 'dart:io'; import 'package:flutter/material.dart'; import 'package:hmg_patient_app_new/core/common_models/generic_api_model.dart'; @@ -21,6 +22,7 @@ import 'package:hmg_patient_app_new/features/my_appointments/models/resp_models/ import 'package:hmg_patient_app_new/features/symptoms_checker/symptoms_checker_view_model.dart'; import 'package:hmg_patient_app_new/services/error_handler_service.dart'; import 'package:hmg_patient_app_new/services/navigation_service.dart'; +import 'package:sms_otp_auto_verify/sms_otp_auto_verify.dart'; import 'models/req_models/check_activation_e_referral_req_model.dart'; import 'models/resq_models/get_covid_payment_info_resp.dart'; @@ -632,6 +634,9 @@ class HmgServicesViewModel extends ChangeNotifier { Function(GenericApiModel)? onSuccess, Function(String)? onError, }) async { + // Get and set the SMS signature for auto-fill + requestModel.sMSSignature = await getSignature(); + notifyListeners(); final result = await hmgServicesRepo.sendEReferralActivationCode(requestModel); @@ -660,6 +665,14 @@ class HmgServicesViewModel extends ChangeNotifier { ); } + Future getSignature() async { + if (Platform.isAndroid) { + return await SmsVerification.getAppSignature(); + } else { + return null; + } + } + Future checkEReferralActivationCode({ required CheckActivationCodeForEReferralRequestModel requestModel, Function(GenericApiModel)? onSuccess, diff --git a/lib/features/hmg_services/models/req_models/send_activation_code_ereferral_req_model.dart b/lib/features/hmg_services/models/req_models/send_activation_code_ereferral_req_model.dart index 0380de8e..288498d2 100644 --- a/lib/features/hmg_services/models/req_models/send_activation_code_ereferral_req_model.dart +++ b/lib/features/hmg_services/models/req_models/send_activation_code_ereferral_req_model.dart @@ -10,6 +10,7 @@ class SendActivationCodeForEReferralRequestModel { dynamic sessionID; bool? isDentalAllowedBackend; int? deviceTypeID; + String? sMSSignature; SendActivationCodeForEReferralRequestModel( {this.patientMobileNumber, @@ -22,7 +23,8 @@ class SendActivationCodeForEReferralRequestModel { this.patientOutSA, this.sessionID, this.isDentalAllowedBackend, - this.deviceTypeID}); + this.deviceTypeID, + this.sMSSignature}); SendActivationCodeForEReferralRequestModel.fromJson(Map json) { patientMobileNumber = json['PatientMobileNumber']; @@ -36,6 +38,7 @@ class SendActivationCodeForEReferralRequestModel { sessionID = json['SessionID']; isDentalAllowedBackend = json['isDentalAllowedBackend']; deviceTypeID = json['DeviceTypeID']; + sMSSignature = json['SMSSignature']; } Map toJson() { @@ -51,6 +54,7 @@ class SendActivationCodeForEReferralRequestModel { data['SessionID'] = this.sessionID; data['isDentalAllowedBackend'] = this.isDentalAllowedBackend; data['DeviceTypeID'] = this.deviceTypeID; + data['SMSSignature'] = this.sMSSignature; return data; } } diff --git a/lib/features/hmg_services/models/ui_models/vital_sign_ui_model.dart b/lib/features/hmg_services/models/ui_models/vital_sign_ui_model.dart index 66f5d77b..ea0782bd 100644 --- a/lib/features/hmg_services/models/ui_models/vital_sign_ui_model.dart +++ b/lib/features/hmg_services/models/ui_models/vital_sign_ui_model.dart @@ -1,6 +1,18 @@ +import 'package:easy_localization/easy_localization.dart'; import 'package:flutter/material.dart'; +import 'package:hmg_patient_app_new/generated/locale_keys.g.dart'; import 'package:hmg_patient_app_new/theme/colors.dart'; +/// Enum for vital sign status to avoid string-based comparisons +enum VitalSignStatus { + normal, + high, + low, + obese, + overweight, + underweight, +} + /// UI-only helper model for Vital Sign cards. /// /// Keeps presentation logic (chip colors, icon colors, simple status rules) @@ -20,13 +32,12 @@ class VitalSignUiModel { /// Returns a color scheme for a card based on its [status] and [label]. /// - /// Rules (mirrors existing behavior in Medical File page): + /// Rules: /// - Height is always blue. - /// - High => red scheme. - /// - Low => yellow scheme. + /// - High, Obese, Overweight => red scheme. + /// - Low, Underweight => yellow scheme. /// - Otherwise => green scheme (Normal). - static VitalSignUiModel scheme({required String? status, required String label}) { - final s = (status ?? '').toLowerCase(); + static VitalSignUiModel scheme({required VitalSignStatus? status, required String label}) { final l = label.toLowerCase(); // Height should always be blue. @@ -39,7 +50,10 @@ class VitalSignUiModel { ); } - if (s.contains('high')) { + // High, Obese, Overweight => red scheme (health concerns) + if (status == VitalSignStatus.high || + status == VitalSignStatus.obese || + status == VitalSignStatus.overweight) { return VitalSignUiModel( iconBg: AppColors.chipSecondaryLightRedColor, iconFg: AppColors.primaryRedColor, @@ -48,8 +62,8 @@ class VitalSignUiModel { ); } - // Warning for both low and overweight/underweight BMI, since they can indicate potential health issues. - if (s.contains('low') || s.contains('underweight') || s.contains('overweight')) { + // Low, Underweight => yellow scheme (warning) + if (status == VitalSignStatus.low || status == VitalSignStatus.underweight) { final Color yellowBg = AppColors.highAndLow.withValues(alpha: 0.12); return VitalSignUiModel( iconBg: yellowBg, @@ -69,19 +83,37 @@ class VitalSignUiModel { ); } + /// Convert VitalSignStatus enum to localized string + static String statusToString(VitalSignStatus status) { + switch (status) { + case VitalSignStatus.normal: + return LocaleKeys.vitalSignNormal.tr(); + case VitalSignStatus.high: + return LocaleKeys.vitalSignHigh.tr(); + case VitalSignStatus.low: + return LocaleKeys.vitalSignLow.tr(); + case VitalSignStatus.obese: + return LocaleKeys.vitalSignObese.tr(); + case VitalSignStatus.overweight: + return LocaleKeys.vitalSignOverweight.tr(); + case VitalSignStatus.underweight: + return LocaleKeys.vitalSignUnderweight.tr(); + } + } + /// Simple, user-friendly classification: /// - Low: systolic < 90 OR diastolic < 60 /// - High: systolic >= 140 OR diastolic >= 90 /// - Normal: otherwise /// Returns null if values are missing/unparseable. - static String? bloodPressureStatus({dynamic systolic, dynamic diastolic}) { + static VitalSignStatus? bloodPressureStatus({dynamic systolic, dynamic diastolic}) { final int? s = toIntOrNull(systolic); final int? d = toIntOrNull(diastolic); if (s == null || d == null) return null; - if (s < 90 || d < 60) return 'Low'; - if (s >= 140 || d >= 90) return 'High'; - return 'Normal'; + if (s < 90 || d < 60) return VitalSignStatus.low; + if (s >= 140 || d >= 90) return VitalSignStatus.high; + return VitalSignStatus.normal; } static int? toIntOrNull(dynamic v) { @@ -91,28 +123,47 @@ class VitalSignUiModel { return int.tryParse(v.toString()); } - static String bmiStatus(dynamic bmi) { - String bmiStatus = 'Normal'; + static VitalSignStatus? bmiStatus(dynamic bmi) { + // Return null if BMI is not available or is 0 final double bmiResult = double.tryParse(bmi.toString()) ?? 0; - if (bmiResult >= 30) { - bmiStatus = "High"; - } else if (bmiResult < 30 && bmiResult >= 25) { - bmiStatus = "Overweight"; - } else if (bmiResult < 25 && bmiResult >= 18.5) { - bmiStatus = "Normal"; - } else if (bmiResult < 18.5) { - bmiStatus = "Underweight"; + if (bmiResult == 0 || bmi == null) { + return null; + } + + // BMI >= 25 (Overweight or Obese) => High + if (bmiResult >= 25) { + return VitalSignStatus.high; } + // BMI >= 18.5 and < 25 => Normal + else if (bmiResult >= 18.5) { + return VitalSignStatus.normal; + } + // BMI < 18.5 (Underweight) => Low + else { + return VitalSignStatus.low; + } + } + + /// Weight status based on BMI with detailed classification + /// Returns: Obese, Overweight, Normal, Underweight + static VitalSignStatus? weightStatus(dynamic bmi) { + // Return null if BMI is not available or is 0 + final double bmiResult = double.tryParse(bmi.toString()) ?? 0; - // if (bmi == null) return 'N/A'; - // final double bmiValue = double.tryParse(bmi.toString()) ?? 0; - // if (bmiValue < 18.5) return 'Underweight'; - // if (bmiValue < 25) return 'Normal'; - // if (bmiValue < 30) return 'Overweight'; - // return 'High'; + if (bmiResult == 0 || bmi == null) { + return null; + } - return bmiStatus; + if (bmiResult >= 30) { + return VitalSignStatus.obese; + } else if (bmiResult >= 25) { + return VitalSignStatus.overweight; + } else if (bmiResult >= 18.5) { + return VitalSignStatus.normal; + } else { + return VitalSignStatus.underweight; + } } } diff --git a/lib/features/immediate_livecare/immediate_livecare_repo.dart b/lib/features/immediate_livecare/immediate_livecare_repo.dart index cbce4ea2..8775378b 100644 --- a/lib/features/immediate_livecare/immediate_livecare_repo.dart +++ b/lib/features/immediate_livecare/immediate_livecare_repo.dart @@ -5,6 +5,7 @@ import 'package:hmg_patient_app_new/core/api/api_client.dart'; import 'package:hmg_patient_app_new/core/api_consts.dart'; import 'package:hmg_patient_app_new/core/common_models/generic_api_model.dart'; import 'package:hmg_patient_app_new/core/exceptions/api_failure.dart'; +import 'package:hmg_patient_app_new/features/immediate_livecare/models/resp_models/get_livecare_clinic_schedule_response_model.dart'; import 'package:hmg_patient_app_new/features/immediate_livecare/models/resp_models/get_livecare_immediate_clinics_response_model.dart'; import 'package:hmg_patient_app_new/features/immediate_livecare/models/resp_models/get_livecare_immediate_fees_response_model.dart'; import 'package:hmg_patient_app_new/features/immediate_livecare/models/resp_models/get_patient_livecare_history_response_model.dart'; @@ -13,6 +14,9 @@ import 'package:hmg_patient_app_new/services/logger_service.dart'; abstract class ImmediateLiveCareRepo { Future>>> getLiveCareImmediateClinicsList(int age, int genderID, {Function(dynamic)? onSuccess, Function(String)? onError}); + Future>>> getLiveCareImmediateClinicSchedule(int age, int genderID, int serviceID, + {Function(dynamic)? onSuccess, Function(String)? onError}); + Future>> getLiveCareImmediateAppointmentFees(int age, int genderID, int serviceID, {Function(dynamic)? onSuccess, Function(String)? onError}); @@ -203,4 +207,47 @@ class ImmediateLiveCareRepoImp implements ImmediateLiveCareRepo { return Left(UnknownFailure(e.toString())); } } + + @override + Future>>> getLiveCareImmediateClinicSchedule(int age, int genderID, int serviceID, + {Function(dynamic p1)? onSuccess, Function(String p1)? onError}) async { + Map mapDevice = { + "Age": age, + "Gender": genderID, + "ServiceID": serviceID, + }; + + try { + GenericApiModel>? apiResponse; + Failure? failure; + await apiClient.post( + GET_LIVECARE_CLINIC_TIMING, + body: mapDevice, + onFailure: (error, statusCode, {messageStatus, failureType}) { + failure = failureType; + // onError!(error); + }, + onSuccess: (response, statusCode, {messageStatus, errorMessage}) { + try { + final respObject = response['PatientER_GetClinicsServiceTimingsList']; + final liveCareScheduleObj = respObject.map((item) => LiveCareClinicScheduleResponse.fromJson(item as Map)).toList().cast(); + + apiResponse = GenericApiModel>( + messageStatus: messageStatus, + statusCode: statusCode, + errorMessage: null, + data: liveCareScheduleObj, + ); + } catch (e) { + failure = DataParsingFailure(e.toString()); + } + }, + ); + if (failure != null) return Left(failure!); + if (apiResponse == null) return Left(ServerFailure("Unknown error")); + return Right(apiResponse!); + } catch (e) { + return Left(UnknownFailure(e.toString())); + } + } } diff --git a/lib/features/immediate_livecare/immediate_livecare_view_model.dart b/lib/features/immediate_livecare/immediate_livecare_view_model.dart index 46f5a652..6613b481 100644 --- a/lib/features/immediate_livecare/immediate_livecare_view_model.dart +++ b/lib/features/immediate_livecare/immediate_livecare_view_model.dart @@ -11,6 +11,7 @@ import 'package:hmg_patient_app_new/core/enums.dart'; import 'package:hmg_patient_app_new/core/utils/size_utils.dart'; import 'package:hmg_patient_app_new/core/utils/utils.dart'; import 'package:hmg_patient_app_new/features/immediate_livecare/immediate_livecare_repo.dart'; +import 'package:hmg_patient_app_new/features/immediate_livecare/models/resp_models/get_livecare_clinic_schedule_response_model.dart'; import 'package:hmg_patient_app_new/features/immediate_livecare/models/resp_models/get_livecare_immediate_clinics_response_model.dart'; import 'package:hmg_patient_app_new/features/immediate_livecare/models/resp_models/get_livecare_immediate_fees_response_model.dart'; import 'package:hmg_patient_app_new/features/immediate_livecare/models/resp_models/get_patient_livecare_history_response_model.dart'; @@ -46,6 +47,8 @@ class ImmediateLiveCareViewModel extends ChangeNotifier { List patientLiveCareHistoryList = []; bool patientHasPendingLiveCareRequest = false; + List liveCareClinicScheduleList = []; + late AppState _appState; Duration countdownDuration = Duration(minutes: 1, seconds: 0); @@ -265,6 +268,33 @@ class ImmediateLiveCareViewModel extends ChangeNotifier { ); } + Future getLiveCareImmediateClinicSchedule({int? serviceID, Function(dynamic)? onSuccess, Function(String)? onError}) async { + liveCareClinicScheduleList.clear(); + notifyListeners(); + + final result = + await immediateLiveCareRepo.getLiveCareImmediateClinicSchedule(_appState.getAuthenticatedUser()!.age!, _appState.getAuthenticatedUser()!.gender!, serviceID!); + + result.fold( + (failure) async => await errorHandlerService.handleError(failure: failure), + (apiResponse) { + if (apiResponse.messageStatus == 2) { + // dialogService.showErrorDialog(message: apiResponse.errorMessage!, onOkPressed: () {}); + } else if (apiResponse.messageStatus == 1) { + liveCareClinicScheduleList = apiResponse.data!; + + // immediateLiveCareClinicsList.sort((a, b) => b.isOnline!.compareTo(a.isOnline!)); + + // isImmediateLiveCareClinicsLoading = false; + notifyListeners(); + if (onSuccess != null) { + onSuccess(apiResponse); + } + } + }, + ); + } + void startTimer() { timer = Timer.periodic(const Duration(seconds: 1), (_) => addTime()); notifyListeners(); diff --git a/lib/features/immediate_livecare/models/resp_models/get_livecare_clinic_schedule_response_model.dart b/lib/features/immediate_livecare/models/resp_models/get_livecare_clinic_schedule_response_model.dart new file mode 100644 index 00000000..787167de --- /dev/null +++ b/lib/features/immediate_livecare/models/resp_models/get_livecare_clinic_schedule_response_model.dart @@ -0,0 +1,71 @@ +class LiveCareClinicScheduleResponse { + int? iD; + int? serviceID; + int? dayOfWeek; + String? dayOfWeekStr; + bool? isActive; + String? createdOn; + String? createdBy; + bool? projectOutSA; + String? dayOfWeekStrN; + List? shiftTimings; + + LiveCareClinicScheduleResponse({this.iD, this.serviceID, this.dayOfWeek, this.dayOfWeekStr, this.isActive, this.createdOn, this.createdBy, this.projectOutSA, this.dayOfWeekStrN, this.shiftTimings}); + + LiveCareClinicScheduleResponse.fromJson(Map json) { + iD = json['ID']; + serviceID = json['ServiceID']; + dayOfWeek = json['DayOfWeek']; + dayOfWeekStr = json['DayOfWeekStr']; + isActive = json['IsActive']; + createdOn = json['CreatedOn']; + createdBy = json['CreatedBy']; + projectOutSA = json['ProjectOutSA']; + dayOfWeekStrN = json['DayOfWeekStrN']; + if (json['ShiftTimings'] != null) { + shiftTimings = []; + json['ShiftTimings'].forEach((v) { + shiftTimings!.add(new ShiftTimings.fromJson(v)); + }); + } + } + + Map toJson() { + final Map data = new Map(); + data['ID'] = this.iD; + data['ServiceID'] = this.serviceID; + data['DayOfWeek'] = this.dayOfWeek; + data['DayOfWeekStr'] = this.dayOfWeekStr; + data['IsActive'] = this.isActive; + data['CreatedOn'] = this.createdOn; + data['CreatedBy'] = this.createdBy; + data['ProjectOutSA'] = this.projectOutSA; + data['DayOfWeekStrN'] = this.dayOfWeekStrN; + if (this.shiftTimings != null) { + data['ShiftTimings'] = this.shiftTimings!.map((v) => v.toJson()).toList(); + } + return data; + } +} + +class ShiftTimings { + String? endTime; + int? shiftID; + String? startTime; + + ShiftTimings({this.endTime, this.shiftID, this.startTime}); + + ShiftTimings.fromJson(Map json) { + endTime = json['EndTime']; + shiftID = json['ShiftID']; + startTime = json['StartTime']; + } + + Map toJson() { + final Map data = new Map(); + data['EndTime'] = this.endTime; + data['ShiftID'] = this.shiftID; + data['StartTime'] = this.startTime; + return data; + } +} diff --git a/lib/features/lab/lab_repo.dart b/lib/features/lab/lab_repo.dart index 10839b12..82468ada 100644 --- a/lib/features/lab/lab_repo.dart +++ b/lib/features/lab/lab_repo.dart @@ -26,9 +26,9 @@ abstract class LabRepo { Future>> getLabResultsByAppointmentNo({required num appointmentNo, required num projectID, required num clinicID}); - Future>> getPatientAiOverViewLabOrders(var payload); + Future>> getPatientAiOverViewLabOrders({required int langId, required dynamic payload}); - Future>> getPatientAiOverViewLabOrder(var payload); + Future>> getPatientAiOverViewLabOrder(var payloa); } class LabRepoImp implements LabRepo { @@ -319,16 +319,24 @@ class LabRepoImp implements LabRepo { GenericApiModel? apiResponse; Failure? failure; await apiClient.post( - ApiConsts.getAiOverViewLabOrder, + ApiConsts.getAiOverViewLabOrderNew, body: labOrder, + isAllowAny: true, + isExternal: true, + apiHeaders: { + 'Accept-Language': 'en-US,en;q=0.7', + 'Content-Type': 'application/json', + 'X-API-KEY': '9d9d2ac4c1fc302299dbb0fbb1b7fea1', + 'accept': 'application/json', + }, onFailure: (error, statusCode, {messageStatus, failureType}) { failure = failureType; }, onSuccess: (response, statusCode, {messageStatus, errorMessage}) { try { var res; - if (response["analysis_result"] != null) { - res = response["analysis_result"]; + if (response != null) { + res = response; } apiResponse = GenericApiModel( messageStatus: messageStatus, @@ -350,21 +358,29 @@ class LabRepoImp implements LabRepo { } @override - Future>> getPatientAiOverViewLabOrders(var labOrder) async { + Future>> getPatientAiOverViewLabOrders({required int langId, required dynamic payload}) async { try { GenericApiModel? apiResponse; Failure? failure; await apiClient.post( - ApiConsts.getAiOverViewLabOrders, - body: {"lab_results": labOrder}, + ApiConsts.getAiOverViewLabOrdersNew, + isAllowAny: true, + isExternal: true, + apiHeaders: { + 'Accept-Language': 'en-US,en;q=0.7', + 'Content-Type': 'application/json', + 'X-API-KEY': '9d9d2ac4c1fc302299dbb0fbb1b7fea1', + 'accept': 'application/json', + }, + body: {"lab_results": payload, "language_id": langId}, onFailure: (error, statusCode, {messageStatus, failureType}) { failure = failureType; }, onSuccess: (response, statusCode, {messageStatus, errorMessage}) { try { var res; - if (response["lab_analysis_result"] != null) { - res = response["lab_analysis_result"]; + if (response != null) { + res = response; } apiResponse = GenericApiModel( messageStatus: messageStatus, diff --git a/lib/features/lab/lab_view_model.dart b/lib/features/lab/lab_view_model.dart index fb80ca00..09caf578 100644 --- a/lib/features/lab/lab_view_model.dart +++ b/lib/features/lab/lab_view_model.dart @@ -447,20 +447,19 @@ class LabViewModel extends ChangeNotifier { minY = resultValue; } // if (highRefrenceValue < double.parse(element.referenceHigh ?? "0.0")) { - highRefrenceValue = double.parse(element.referenceHigh ?? "0.0"); - flagForHighReferenceRange = element.calculatedResultFlag; + highRefrenceValue = double.parse(element.referenceHigh ?? "0.0"); + flagForHighReferenceRange = element.calculatedResultFlag; // } // if (lowRefenceValue > double.parse(element.referenceLow ?? "0.0")) { - lowRefenceValue = double.parse(element.referenceLow ?? "0.0"); - flagForLowReferenceRange = element.calculatedResultFlag; + lowRefenceValue = double.parse(element.referenceLow ?? "0.0"); + flagForLowReferenceRange = element.calculatedResultFlag; // } - if(double.parse(element.referenceHigh ?? "0.0") == 0 ){ + if (double.parse(element.referenceHigh ?? "0.0") == 0) { highRefrenceValue = double.negativeInfinity; } - - if(double.parse(element.referenceLow ?? "0.0") == 0 ){ + if (double.parse(element.referenceLow ?? "0.0") == 0) { lowRefenceValue = double.negativeInfinity; } @@ -473,18 +472,16 @@ class LabViewModel extends ChangeNotifier { time: DateUtil.convertStringToDate(element.verifiedOnDateTime), referenceValue: element.calculatedResultFlag ?? "", )); - - } catch (e) { - } + } catch (e) {} }); if (flagForLowReferenceRange == null && flagForHighReferenceRange == null) { highRefrenceValue = maxY; lowRefenceValue = minY; } // - if (lowRefenceValue != double.negativeInfinity &&minY > lowRefenceValue) { + if (lowRefenceValue != double.negativeInfinity && minY > lowRefenceValue) { minY = lowRefenceValue - getInterval(); - }else if (highRefrenceValue != double.negativeInfinity && minY > highRefrenceValue) { + } else if (highRefrenceValue != double.negativeInfinity && minY > highRefrenceValue) { minY = highRefrenceValue - getInterval(); } @@ -494,17 +491,16 @@ class LabViewModel extends ChangeNotifier { lowTransformedReferenceValue = double.parse(transformValueInRange(lowRefenceValue, flagForLowReferenceRange ?? "").toStringAsFixed(1)); this.highRefrenceValue = double.parse(highRefrenceValue.toStringAsFixed(2)); this.lowRefenceValue = double.parse(lowRefenceValue.toStringAsFixed(2)); - if(highRefrenceValue != double.negativeInfinity &&maxY=1.0 && maxX < 5.0) return .3; - if(maxX >=5.0 && maxX < 10.0) return 1.5; - if(maxX >=10.0 && maxX < 50.0) return 2.5; - if(maxX >=50.0 && maxX < 100.0) return 5; - if(maxX >=100.0 && maxX < 200.0) return 10; + if (maxX < 1) return .2; + if (maxX >= 1.0 && maxX < 5.0) return .3; + if (maxX >= 5.0 && maxX < 10.0) return 1.5; + if (maxX >= 10.0 && maxX < 50.0) return 2.5; + if (maxX >= 50.0 && maxX < 100.0) return 5; + if (maxX >= 100.0 && maxX < 200.0) return 10; return 15; } @@ -617,7 +614,7 @@ class LabViewModel extends ChangeNotifier { rangeStart = 40.0; rangeEnd = 59.0; break; - // throw ArgumentError('Invalid flag: $flag'); + // throw ArgumentError('Invalid flag: $flag'); } // Clamp input value to 0-100 and map it to the range bounds @@ -845,9 +842,10 @@ class LabViewModel extends ChangeNotifier { notifyListeners(); } - Future getAiOverviewLabOrders({required PatientLabOrdersResponseModel labOrder, required String loadingText}) async { - // LoadingUtils.showFullScreenLoader(loadingText: "Loading and analysing your data,\nPlease be patient and let the AI do the magic. \nPlease be patient, This might take some time."); - // LoadingUtils.showFullScreenLoader(loadingText: loadingText); + Future getAiOverviewLabOrders({required int langId, required PatientLabOrdersResponseModel labOrder, required String loadingText}) async { + isLabAIAnalysisNeedsToBeShown = true; + notifyListeners(); + LoaderBottomSheet.showLoader( loadingText: loadingText, showCloseButton: true, @@ -872,32 +870,36 @@ class LabViewModel extends ChangeNotifier { } orderData["results"] = testResults; results.add(orderData); - final result = await labRepo.getPatientAiOverViewLabOrders(results); + + final result = await labRepo.getPatientAiOverViewLabOrders(payload: results, langId: langId); result.fold( (failure) async { - LoadingUtils.hideFullScreenLoader(); + LoaderBottomSheet.hideLoader(); if (isLabAIAnalysisNeedsToBeShown) { await errorHandlerService.handleError(failure: failure); } }, (apiResponse) { - LoadingUtils.hideFullScreenLoader(); + LoaderBottomSheet.hideLoader(); if (apiResponse.messageStatus == 2) { } else if (apiResponse.messageStatus == 1) { if (isLabAIAnalysisNeedsToBeShown) { labOrdersResponseByAi = apiResponse.data; navigationService.push( - MaterialPageRoute(builder: (_) => LabAiAnalysisDetailedPage()), + CustomPageRoute( + direction: AxisDirection.down, + page: LabAiAnalysisDetailedPage(), + ), ); } } }, ); - LoadingUtils.hideFullScreenLoader(); + // LoadingUtils.hideFullScreenLoader(); } - Future getAiOverviewSingleLabResult({required String langId, required LabResult recentLabResult, required String loadingText}) async { + Future getAiOverviewSingleLabResult({required int langId, required LabResult recentLabResult, required String loadingText}) async { LoaderBottomSheet.showLoader( loadingText: loadingText, showCloseButton: true, diff --git a/lib/features/medical_file/medical_file_view_model.dart b/lib/features/medical_file/medical_file_view_model.dart index b93bb62d..05f82208 100644 --- a/lib/features/medical_file/medical_file_view_model.dart +++ b/lib/features/medical_file/medical_file_view_model.dart @@ -215,27 +215,40 @@ class MedicalFileViewModel extends ChangeNotifier { final result = await medicalFileRepo.getPatientVaccinesList(); result.fold( - // (failure) async => await errorHandlerService.handleError( - // failure: failure, - // onOkPressed: () { - // onError!(failure.message); - // }, - // ), (failure) async { - // onError!(failure.message); + print("❌ Vaccine API Failure: ${failure.message}"); isPatientVaccineListLoading = false; notifyListeners(); + if (onError != null) { + onError(failure.message); + } }, (apiResponse) { + print("📦 Vaccine API Response - Status: ${apiResponse.messageStatus}, Data: ${apiResponse.data}"); + if (apiResponse.messageStatus == 2) { - // dialogService.showErrorDialog(message: apiResponse.errorMessage!, onOkPressed: () {}); + print("⚠️ Vaccine API Error: ${apiResponse.errorMessage}"); + isPatientVaccineListLoading = false; + notifyListeners(); + if (onError != null) { + onError(apiResponse.errorMessage ?? "Unknown error"); + } } else if (apiResponse.messageStatus == 1) { - patientVaccineList = apiResponse.data!; + if (apiResponse.data != null) { + patientVaccineList = apiResponse.data!; + print("✅ Vaccine data loaded: ${patientVaccineList.length} items"); + } else { + print("⚠️ Vaccine API returned success but data is null"); + } isPatientVaccineListLoading = false; notifyListeners(); if (onSuccess != null) { onSuccess(apiResponse); } + } else { + print("⚠️ Unexpected messageStatus: ${apiResponse.messageStatus}"); + isPatientVaccineListLoading = false; + notifyListeners(); } }, ); diff --git a/lib/features/medical_file/models/patient_vaccine_response_model.dart b/lib/features/medical_file/models/patient_vaccine_response_model.dart index ff27b063..255a7198 100644 --- a/lib/features/medical_file/models/patient_vaccine_response_model.dart +++ b/lib/features/medical_file/models/patient_vaccine_response_model.dart @@ -1,160 +1,169 @@ +import 'dart:convert'; + class PatientVaccineResponseModel { - String? setupID; - int? projectID; - int? patientID; + String? setupId; + int? projectId; + int? patientId; int? invoiceNo; - String? procedureID; + String? procedureId; String? vaccineName; - Null? vaccineNameN; + dynamic vaccineNameN; String? invoiceDate; - int? doctorID; - int? clinicID; + int? doctorId; + int? clinicId; String? firstName; String? middleName; String? lastName; - Null? firstNameN; - Null? middleNameN; - Null? lastNameN; + dynamic firstNameN; + dynamic middleNameN; + dynamic lastNameN; String? dateofBirth; int? actualDoctorRate; String? age; String? clinicName; - Null? decimalDoctorRate; - Null? doctorImageURL; + double? decimalDoctorRate; + dynamic doctorImageUrl; String? doctorName; int? doctorRate; - int? doctorStarsRate; + double? doctorStarsRate; String? doctorTitle; int? gender; - Null? genderDescription; - Null? invoiceNoVP; + dynamic genderDescription; + dynamic invoiceNoVp; bool? isActiveDoctorProfile; bool? isDoctorAllowVedioCall; bool? isExecludeDoctor; int? noOfPatientsRate; String? patientName; String? projectName; - String? qR; + String? qr; + List? speciality; String? vaccinationDate; - PatientVaccineResponseModel( - {this.setupID, - this.projectID, - this.patientID, - this.invoiceNo, - this.procedureID, - this.vaccineName, - this.vaccineNameN, - this.invoiceDate, - this.doctorID, - this.clinicID, - this.firstName, - this.middleName, - this.lastName, - this.firstNameN, - this.middleNameN, - this.lastNameN, - this.dateofBirth, - this.actualDoctorRate, - this.age, - this.clinicName, - this.decimalDoctorRate, - this.doctorImageURL, - this.doctorName, - this.doctorRate, - this.doctorStarsRate, - this.doctorTitle, - this.gender, - this.genderDescription, - this.invoiceNoVP, - this.isActiveDoctorProfile, - this.isDoctorAllowVedioCall, - this.isExecludeDoctor, - this.noOfPatientsRate, - this.patientName, - this.projectName, - this.qR, - this.vaccinationDate}); + PatientVaccineResponseModel({ + this.setupId, + this.projectId, + this.patientId, + this.invoiceNo, + this.procedureId, + this.vaccineName, + this.vaccineNameN, + this.invoiceDate, + this.doctorId, + this.clinicId, + this.firstName, + this.middleName, + this.lastName, + this.firstNameN, + this.middleNameN, + this.lastNameN, + this.dateofBirth, + this.actualDoctorRate, + this.age, + this.clinicName, + this.decimalDoctorRate, + this.doctorImageUrl, + this.doctorName, + this.doctorRate, + this.doctorStarsRate, + this.doctorTitle, + this.gender, + this.genderDescription, + this.invoiceNoVp, + this.isActiveDoctorProfile, + this.isDoctorAllowVedioCall, + this.isExecludeDoctor, + this.noOfPatientsRate, + this.patientName, + this.projectName, + this.qr, + this.speciality, + this.vaccinationDate, + }); + + factory PatientVaccineResponseModel.fromRawJson(String str) => PatientVaccineResponseModel.fromJson(json.decode(str)); + + String toRawJson() => json.encode(toJson()); - PatientVaccineResponseModel.fromJson(Map json) { - setupID = json['SetupID']; - projectID = json['ProjectID']; - patientID = json['PatientID']; - invoiceNo = json['InvoiceNo']; - procedureID = json['ProcedureID']; - vaccineName = json['VaccineName']; - vaccineNameN = json['VaccineNameN']; - invoiceDate = json['InvoiceDate']; - doctorID = json['DoctorID']; - clinicID = json['ClinicID']; - firstName = json['FirstName']; - middleName = json['MiddleName']; - lastName = json['LastName']; - firstNameN = json['FirstNameN']; - middleNameN = json['MiddleNameN']; - lastNameN = json['LastNameN']; - dateofBirth = json['DateofBirth']; - actualDoctorRate = json['ActualDoctorRate']; - age = json['Age']; - clinicName = json['ClinicName']; - decimalDoctorRate = json['DecimalDoctorRate']; - doctorImageURL = json['DoctorImageURL']; - doctorName = json['DoctorName']; - doctorRate = json['DoctorRate']; - doctorStarsRate = json['DoctorStarsRate']; - doctorTitle = json['DoctorTitle']; - gender = json['Gender']; - genderDescription = json['GenderDescription']; - invoiceNoVP = json['InvoiceNo_VP']; - isActiveDoctorProfile = json['IsActiveDoctorProfile']; - isDoctorAllowVedioCall = json['IsDoctorAllowVedioCall']; - isExecludeDoctor = json['IsExecludeDoctor']; - noOfPatientsRate = json['NoOfPatientsRate']; - patientName = json['PatientName']; - projectName = json['ProjectName']; - qR = json['QR']; - vaccinationDate = json['VaccinationDate']; - } + factory PatientVaccineResponseModel.fromJson(Map json) => PatientVaccineResponseModel( + setupId: json["SetupID"], + projectId: json["ProjectID"], + patientId: json["PatientID"], + invoiceNo: json["InvoiceNo"], + procedureId: json["ProcedureID"], + vaccineName: json["VaccineName"], + vaccineNameN: json["VaccineNameN"], + invoiceDate: json["InvoiceDate"], + doctorId: json["DoctorID"], + clinicId: json["ClinicID"], + firstName: json["FirstName"], + middleName: json["MiddleName"], + lastName: json["LastName"], + firstNameN: json["FirstNameN"], + middleNameN: json["MiddleNameN"], + lastNameN: json["LastNameN"], + dateofBirth: json["DateofBirth"], + actualDoctorRate: json["ActualDoctorRate"], + age: json["Age"], + clinicName: json["ClinicName"], + decimalDoctorRate: json["DecimalDoctorRate"]?.toDouble(), + doctorImageUrl: json["DoctorImageURL"], + doctorName: json["DoctorName"], + doctorRate: json["DoctorRate"], + doctorStarsRate: json["DoctorStarsRate"]?.toDouble(), + doctorTitle: json["DoctorTitle"], + gender: json["Gender"], + genderDescription: json["GenderDescription"], + invoiceNoVp: json["InvoiceNo_VP"], + isActiveDoctorProfile: json["IsActiveDoctorProfile"], + isDoctorAllowVedioCall: json["IsDoctorAllowVedioCall"], + isExecludeDoctor: json["IsExecludeDoctor"], + noOfPatientsRate: json["NoOfPatientsRate"], + patientName: json["PatientName"], + projectName: json["ProjectName"], + qr: json["QR"], + speciality: json["Speciality"] == null ? [] : List.from(json["Speciality"]!.map((x) => x)), + vaccinationDate: json["VaccinationDate"], + ); - Map toJson() { - final Map data = new Map(); - data['SetupID'] = this.setupID; - data['ProjectID'] = this.projectID; - data['PatientID'] = this.patientID; - data['InvoiceNo'] = this.invoiceNo; - data['ProcedureID'] = this.procedureID; - data['VaccineName'] = this.vaccineName; - data['VaccineNameN'] = this.vaccineNameN; - data['InvoiceDate'] = this.invoiceDate; - data['DoctorID'] = this.doctorID; - data['ClinicID'] = this.clinicID; - data['FirstName'] = this.firstName; - data['MiddleName'] = this.middleName; - data['LastName'] = this.lastName; - data['FirstNameN'] = this.firstNameN; - data['MiddleNameN'] = this.middleNameN; - data['LastNameN'] = this.lastNameN; - data['DateofBirth'] = this.dateofBirth; - data['ActualDoctorRate'] = this.actualDoctorRate; - data['Age'] = this.age; - data['ClinicName'] = this.clinicName; - data['DecimalDoctorRate'] = this.decimalDoctorRate; - data['DoctorImageURL'] = this.doctorImageURL; - data['DoctorName'] = this.doctorName; - data['DoctorRate'] = this.doctorRate; - data['DoctorStarsRate'] = this.doctorStarsRate; - data['DoctorTitle'] = this.doctorTitle; - data['Gender'] = this.gender; - data['GenderDescription'] = this.genderDescription; - data['InvoiceNo_VP'] = this.invoiceNoVP; - data['IsActiveDoctorProfile'] = this.isActiveDoctorProfile; - data['IsDoctorAllowVedioCall'] = this.isDoctorAllowVedioCall; - data['IsExecludeDoctor'] = this.isExecludeDoctor; - data['NoOfPatientsRate'] = this.noOfPatientsRate; - data['PatientName'] = this.patientName; - data['ProjectName'] = this.projectName; - data['QR'] = this.qR; - data['VaccinationDate'] = this.vaccinationDate; - return data; - } + Map toJson() => { + "SetupID": setupId, + "ProjectID": projectId, + "PatientID": patientId, + "InvoiceNo": invoiceNo, + "ProcedureID": procedureId, + "VaccineName": vaccineName, + "VaccineNameN": vaccineNameN, + "InvoiceDate": invoiceDate, + "DoctorID": doctorId, + "ClinicID": clinicId, + "FirstName": firstName, + "MiddleName": middleName, + "LastName": lastName, + "FirstNameN": firstNameN, + "MiddleNameN": middleNameN, + "LastNameN": lastNameN, + "DateofBirth": dateofBirth, + "ActualDoctorRate": actualDoctorRate, + "Age": age, + "ClinicName": clinicName, + "DecimalDoctorRate": decimalDoctorRate, + "DoctorImageURL": doctorImageUrl, + "DoctorName": doctorName, + "DoctorRate": doctorRate, + "DoctorStarsRate": doctorStarsRate, + "DoctorTitle": doctorTitle, + "Gender": gender, + "GenderDescription": genderDescription, + "InvoiceNo_VP": invoiceNoVp, + "IsActiveDoctorProfile": isActiveDoctorProfile, + "IsDoctorAllowVedioCall": isDoctorAllowVedioCall, + "IsExecludeDoctor": isExecludeDoctor, + "NoOfPatientsRate": noOfPatientsRate, + "PatientName": patientName, + "ProjectName": projectName, + "QR": qr, + "Speciality": speciality == null ? [] : List.from(speciality!.map((x) => x)), + "VaccinationDate": vaccinationDate, + }; } diff --git a/lib/features/my_appointments/models/resp_models/appointment_rated_response_model.dart b/lib/features/my_appointments/models/resp_models/appointment_rated_response_model.dart new file mode 100644 index 00000000..2096a1e1 --- /dev/null +++ b/lib/features/my_appointments/models/resp_models/appointment_rated_response_model.dart @@ -0,0 +1,30 @@ +class AppointmentRatedResponseModel { + int? appointmentNo; + int? clinicID; + int? doctorID; + bool? isAllowedToRate; + bool? isAppointmentRated; + int? projectID; + + AppointmentRatedResponseModel({this.appointmentNo, this.clinicID, this.doctorID, this.isAllowedToRate, this.isAppointmentRated, this.projectID}); + + AppointmentRatedResponseModel.fromJson(Map json) { + appointmentNo = json['AppointmentNo']; + clinicID = json['ClinicID']; + doctorID = json['DoctorID']; + isAllowedToRate = json['IsAllowedToRate']; + isAppointmentRated = json['IsAppointmentRated']; + projectID = json['ProjectID']; + } + + Map toJson() { + final Map data = new Map(); + data['AppointmentNo'] = this.appointmentNo; + data['ClinicID'] = this.clinicID; + data['DoctorID'] = this.doctorID; + data['IsAllowedToRate'] = this.isAllowedToRate; + data['IsAppointmentRated'] = this.isAppointmentRated; + data['ProjectID'] = this.projectID; + return data; + } +} diff --git a/lib/features/my_appointments/models/resp_models/hospital_model.dart b/lib/features/my_appointments/models/resp_models/hospital_model.dart index a807b99a..031b3fd4 100644 --- a/lib/features/my_appointments/models/resp_models/hospital_model.dart +++ b/lib/features/my_appointments/models/resp_models/hospital_model.dart @@ -75,9 +75,7 @@ class HospitalsModel { } String? getName(bool isArabic) { - if (isArabic) { - return "$nameN"; - } + return name; } diff --git a/lib/features/my_appointments/models/resp_models/patient_appointment_history_response_model.dart b/lib/features/my_appointments/models/resp_models/patient_appointment_history_response_model.dart index c0da3c47..cea2e64b 100644 --- a/lib/features/my_appointments/models/resp_models/patient_appointment_history_response_model.dart +++ b/lib/features/my_appointments/models/resp_models/patient_appointment_history_response_model.dart @@ -51,6 +51,7 @@ class PatientAppointmentHistoryResponseModel { bool? isExecludeDoctor; dynamic isFollowup; bool? isLiveCareAppointment; + bool? isLiveCareClinic; bool? isInOutPatient; bool? isMedicalReportRequested; bool? isOnlineCheckedIN; @@ -128,6 +129,7 @@ class PatientAppointmentHistoryResponseModel { this.isExecludeDoctor, this.isFollowup, this.isLiveCareAppointment, + this.isLiveCareClinic, this.isMedicalReportRequested, this.isOnlineCheckedIN, this.latitude, @@ -205,6 +207,7 @@ class PatientAppointmentHistoryResponseModel { isExecludeDoctor = json['IsExecludeDoctor']; isFollowup = json['IsFollowup']; isLiveCareAppointment = json['IsLiveCareAppointment']; + isLiveCareClinic = json['IsLiveCareClinic']; isInOutPatient = json['IsInOutPatient']; isMedicalReportRequested = json['IsMedicalReportRequested']; isOnlineCheckedIN = json['IsOnlineCheckedIN']; diff --git a/lib/features/my_appointments/models/resp_models/patient_queue_details_response_model.dart b/lib/features/my_appointments/models/resp_models/patient_queue_details_response_model.dart index c36b9ec5..828bcf0f 100644 --- a/lib/features/my_appointments/models/resp_models/patient_queue_details_response_model.dart +++ b/lib/features/my_appointments/models/resp_models/patient_queue_details_response_model.dart @@ -1,4 +1,48 @@ class PatientQueueDetails { + Data? patientQueueData; + + PatientQueueDetails({this.patientQueueData}); + + PatientQueueDetails.fromJson(Map json) { + patientQueueData = json['data'] != null ? new Data.fromJson(json['data']) : null; + } + + Map toJson() { + final Map data = new Map(); + if (this.patientQueueData != null) { + data['data'] = this.patientQueueData!.toJson(); + } + return data; + } +} + +class Data { + List? patientInQueueList; + int? isClinicConfigured; + + Data({this.patientInQueueList, this.isClinicConfigured}); + + Data.fromJson(Map json) { + if (json['patientInQueueList'] != null) { + patientInQueueList = []; + json['patientInQueueList'].forEach((v) { + patientInQueueList!.add(new PatientInQueueList.fromJson(v)); + }); + } + isClinicConfigured = json['isClinicConfigured']; + } + + Map toJson() { + final Map data = new Map(); + if (this.patientInQueueList != null) { + data['patientInQueueList'] = this.patientInQueueList!.map((v) => v.toJson()).toList(); + } + data['isClinicConfigured'] = this.isClinicConfigured; + return data; + } +} + +class PatientInQueueList { int? patientID; String? patientName; String? queueNo; @@ -7,16 +51,14 @@ class PatientQueueDetails { String? calledOn; bool? servingNow; - PatientQueueDetails( - {this.patientID, + PatientInQueueList({this.patientID, this.patientName, this.queueNo, this.callType, this.roomNo, - this.calledOn, - this.servingNow}); + this.calledOn, this.servingNow}); - PatientQueueDetails.fromJson(Map json) { + PatientInQueueList.fromJson(Map json) { patientID = json['patientID']; patientName = json['patientName']; queueNo = json['queueNo']; @@ -38,3 +80,47 @@ class PatientQueueDetails { return data; } } + +// class PatientQueueDetails { +// int? patientID; +// String? patientName; +// String? queueNo; +// int? callType; +// String? roomNo; +// String? calledOn; +// bool? servingNow; +// int? isClinicConfigured; +// +// PatientQueueDetails( +// {this.patientID, +// this.patientName, +// this.queueNo, +// this.callType, +// this.roomNo, +// this.calledOn, +// this.servingNow, this.isClinicConfigured}); +// +// PatientQueueDetails.fromJson(Map json) { +// patientID = json['patientID']; +// patientName = json['patientName']; +// queueNo = json['queueNo']; +// callType = json['callType']; +// roomNo = json['roomNo']; +// calledOn = json['calledOn']; +// servingNow = json['servingNow']; +// isClinicConfigured = json['isClinicConfigured']; +// } +// +// Map toJson() { +// final Map data = Map(); +// data['patientID'] = this.patientID; +// data['patientName'] = this.patientName; +// data['queueNo'] = this.queueNo; +// data['callType'] = this.callType; +// data['roomNo'] = this.roomNo; +// data['calledOn'] = this.calledOn; +// data['servingNow'] = this.servingNow; +// data['isClinicConfigured'] = this.isClinicConfigured; +// return data; +// } +// } diff --git a/lib/features/my_appointments/my_appointments_repo.dart b/lib/features/my_appointments/my_appointments_repo.dart index 4c7c41e2..d4669767 100644 --- a/lib/features/my_appointments/my_appointments_repo.dart +++ b/lib/features/my_appointments/my_appointments_repo.dart @@ -9,6 +9,7 @@ import 'package:hmg_patient_app_new/core/exceptions/api_failure.dart'; import 'package:hmg_patient_app_new/core/utils/date_util.dart'; import 'package:hmg_patient_app_new/core/utils/utils.dart'; import 'package:hmg_patient_app_new/features/book_appointments/models/resp_models/get_favorite_doctors_list.dart'; +import 'package:hmg_patient_app_new/features/my_appointments/models/resp_models/appointment_rated_response_model.dart'; import 'package:hmg_patient_app_new/features/my_appointments/models/resp_models/rate_appointment_resp_model.dart'; import 'package:hmg_patient_app_new/features/my_appointments/models/resp_models/ask_doctor_request_type_response_model.dart'; import 'package:hmg_patient_app_new/features/my_appointments/models/resp_models/get_tamara_installments_details_response_model.dart'; @@ -76,11 +77,18 @@ abstract class MyAppointmentsRepo { Future>> sendDoctorRate( int rate, int appointmentNo, int projectID, int doctorID, int clinicID, String note, String appoDate, String docName, String projectName, String clinicName); - Future>>> getPatientAppointmentQueueDetails({required int appointmentNo, required int patientID}); + Future>> getPatientAppointmentQueueDetails({required int appointmentNo, required int patientID, required int clinicID}); Future>> convertPatientToCash({required int projectID}); Future>> sendPatientUpdateRequest(); + + Future>> getDoctorsRatingCheck({ + required int doctorID, + required int projectID, + required int clinicID, + required int appointmentNo, + }); } class MyAppointmentsRepoImp implements MyAppointmentsRepo { @@ -825,19 +833,23 @@ class MyAppointmentsRepoImp implements MyAppointmentsRepo { } @override - Future>>> getPatientAppointmentQueueDetails({required int appointmentNo, required int patientID}) async { - Map mapDevice = {"appointmentNo": appointmentNo, "patientID": patientID, "apiKey": "EE17D21C7943485D9780223CCE55DCE5"}; + Future>> getPatientAppointmentQueueDetails({required int appointmentNo, required int patientID, required int clinicID}) async { + Map mapDevice = {"appointmentNo": appointmentNo, "patientID": patientID, "clinicID": clinicID, "apiKey": "EE17D21C7943485D9780223CCE55DCE5"}; try { - GenericApiModel>? apiResponse; + GenericApiModel? apiResponse; Failure? failure; await apiClient.post(ApiConsts.QLINE_URL, onFailure: (error, statusCode, {messageStatus, failureType}) { failure = failureType; }, onSuccess: (response, statusCode, {messageStatus, errorMessage}) { try { - final list = response['data']; - final queueList = list.map((item) => PatientQueueDetails.fromJson(item as Map)).toList().cast(); + // final list = response['data']; + final queueData = response; + // final queueList = list.map((item) => PatientQueueDetails.fromJson(item as Map)).toList().cast(); + final queueList = PatientQueueDetails.fromJson(queueData); - apiResponse = GenericApiModel>( + queueList.patientQueueData!.isClinicConfigured = 1; + + apiResponse = GenericApiModel( messageStatus: messageStatus, statusCode: statusCode, errorMessage: null, @@ -1077,4 +1089,50 @@ class MyAppointmentsRepoImp implements MyAppointmentsRepo { return Left(UnknownFailure(e.toString())); } } + + @override + Future>> getDoctorsRatingCheck({ + required int doctorID, + required int projectID, + required int clinicID, + required int appointmentNo, + }) async { + Map mapDevice = { + "DoctorID": doctorID, + "ProjectID": projectID, + "ClinicID": clinicID, + "AppointmentNo": appointmentNo, + }; + + try { + GenericApiModel? apiResponse; + Failure? failure; + await apiClient.post( + GET_DOCTORS_RATING_CHECK, + body: mapDevice, + onFailure: (error, statusCode, {messageStatus, failureType}) { + failure = failureType; + }, + onSuccess: (response, statusCode, {messageStatus, errorMessage}) { + try { + AppointmentRatedResponseModel appointmentRatedResponseModel = AppointmentRatedResponseModel.fromJson(response['DoctorsRatingResponseModel'][0]); + + apiResponse = GenericApiModel( + messageStatus: messageStatus, + statusCode: statusCode, + errorMessage: errorMessage, + data: appointmentRatedResponseModel, + ); + } catch (e) { + failure = DataParsingFailure(e.toString()); + } + }, + ); + if (failure != null) return Left(failure!); + if (apiResponse == null) return Left(ServerFailure("Unknown error")); + return Right(apiResponse!); + } catch (e) { + return Left(UnknownFailure(e.toString())); + } + } } diff --git a/lib/features/my_appointments/my_appointments_view_model.dart b/lib/features/my_appointments/my_appointments_view_model.dart index a1c48867..73f8d331 100644 --- a/lib/features/my_appointments/my_appointments_view_model.dart +++ b/lib/features/my_appointments/my_appointments_view_model.dart @@ -7,6 +7,7 @@ import 'package:hmg_patient_app_new/core/utils/size_utils.dart'; import 'package:hmg_patient_app_new/core/utils/utils.dart'; import 'package:hmg_patient_app_new/features/book_appointments/models/resp_models/get_favorite_doctors_list.dart'; import 'package:hmg_patient_app_new/features/my_appointments/models/appointemnet_filters.dart'; +import 'package:hmg_patient_app_new/features/my_appointments/models/resp_models/appointment_rated_response_model.dart'; import 'package:hmg_patient_app_new/features/my_appointments/models/resp_models/ask_doctor_request_type_response_model.dart'; import 'package:hmg_patient_app_new/features/my_appointments/models/resp_models/get_tamara_installments_details_response_model.dart'; import 'package:hmg_patient_app_new/features/my_appointments/models/resp_models/patient_appointment_history_response_model.dart'; @@ -54,8 +55,8 @@ class MyAppointmentsViewModel extends ChangeNotifier { bool isAppointmentQueueDetailsLoading = false; bool isPatientHasQueueAppointment = false; int currentQueueStatus = 0; - List patientQueueDetailsList = []; - late PatientQueueDetails currentPatientQueueDetails; + List patientQueueDetailsList = []; + late PatientInQueueList currentPatientQueueDetails; List patientAppointmentsHistoryList = []; List filteredAppointmentList = []; @@ -87,6 +88,11 @@ class MyAppointmentsViewModel extends ChangeNotifier { bool isShowNotEligibleDialog = false; String notEligibleErrorMsg = ""; + bool isAppointmentAlreadyRated = false; + + AppointmentRatedResponseModel? appointmentRatedResponseModel; + bool isAppointmentRatedResponseLoading = false; + MyAppointmentsViewModel({required this.myAppointmentsRepo, required this.errorHandlerService, required this.appState}); void onTabChange(int index) { @@ -308,14 +314,14 @@ class MyAppointmentsViewModel extends ChangeNotifier { // Build grouped list immediately so the UI has data for the default (By Clinic) view. _groupAppointmentsByClinicAndHospital(); - if (patientArrivedAppointmentsHistoryList.isNotEmpty) { - isPatientHasQueueAppointment = false; - notifyListeners(); - if (Utils.isDateToday(DateUtil.convertStringToDate(patientArrivedAppointmentsHistoryList.first.appointmentDate))) { - // getPatientAppointmentQueueDetails(appointmentNo: patientArrivedAppointmentsHistoryList.first.appointmentNo, patientID: patientArrivedAppointmentsHistoryList.first.patientID); - getPatientAppointmentQueueDetails(); - } + // if (patientArrivedAppointmentsHistoryList.isNotEmpty) { + isPatientHasQueueAppointment = false; + notifyListeners(); + if (Utils.isDateToday(DateUtil.convertStringToDate(patientArrivedAppointmentsHistoryList.first.appointmentDate))) { + // getPatientAppointmentQueueDetails(appointmentNo: patientArrivedAppointmentsHistoryList.first.appointmentNo, patientID: patientArrivedAppointmentsHistoryList.first.patientID); + getPatientAppointmentQueueDetails(); } + // } debugPrint('Upcoming Appointments: ${patientUpcomingAppointmentsHistoryList.length}'); debugPrint('Arrived Appointments: ${patientArrivedAppointmentsHistoryList.length}'); @@ -670,9 +676,9 @@ class MyAppointmentsViewModel extends ChangeNotifier { Future getPatientFavouriteDoctors({bool forceRefresh = false, Function(dynamic)? onSuccess, Function(String)? onError}) async { // If data is already fetched and not forcing refresh, skip API call - if (isFavouriteDoctorsDataFetched && !forceRefresh) { - return; - } + // if (isFavouriteDoctorsDataFetched && !forceRefresh) { + // return; + // } isPatientFavouriteDoctorsLoading = true; patientFavouriteDoctorsList.clear(); @@ -923,8 +929,11 @@ class MyAppointmentsViewModel extends ChangeNotifier { //TODO: Discuss With Haroon, Is the User Has no data it return No Element Bad State; isPatientHasQueueAppointment = false; isAppointmentQueueDetailsLoading = true; + patientQueueDetailsList.clear(); notifyListeners(); + final result = await myAppointmentsRepo.getPatientAppointmentQueueDetails( + clinicID: patientArrivedAppointmentsHistoryList.first.clinicID, appointmentNo: patientArrivedAppointmentsHistoryList.first.appointmentNo, patientID: patientArrivedAppointmentsHistoryList.first.patientID); isAppointmentQueueDetailsLoading = false; @@ -940,23 +949,53 @@ class MyAppointmentsViewModel extends ChangeNotifier { if (apiResponse.messageStatus == 2) { onError!(apiResponse.errorMessage!); } else if (apiResponse.messageStatus == 1) { - if (apiResponse.data != null && apiResponse.data!.isNotEmpty) { - isPatientHasQueueAppointment = true; - patientQueueDetailsList = apiResponse.data!; - for (var element in patientQueueDetailsList) { - if (element.patientID == patientArrivedAppointmentsHistoryList.first.patientID) { - currentPatientQueueDetails = element; - currentQueueStatus = element.callType!; - // currentQueueStatus = 2; - break; + if (apiResponse.data != null) { + patientQueueDetailsList.clear(); + patientQueueDetailsList = apiResponse.data!.patientQueueData!.patientInQueueList!; + + if (apiResponse.data!.patientQueueData!.isClinicConfigured == 1) { + currentPatientQueueDetails = PatientInQueueList(); + isPatientHasQueueAppointment = true; + for (var element in patientQueueDetailsList) { + if (element.patientID == patientArrivedAppointmentsHistoryList.first.patientID) { + currentPatientQueueDetails = element; + currentQueueStatus = element.callType!; + // currentQueueStatus = 2; + break; + } } + // patientQueueDetailsList.first.callType = 1; + patientQueueDetailsList.removeWhere((element) => element.patientID == patientArrivedAppointmentsHistoryList.first.patientID); + } else { + patientQueueDetailsList.clear(); + isPatientHasQueueAppointment = false; } - // patientQueueDetailsList.first.callType = 1; - - patientQueueDetailsList.removeWhere((element) => element.patientID == patientArrivedAppointmentsHistoryList.first.patientID); } else { + patientQueueDetailsList.clear(); isPatientHasQueueAppointment = false; } + + // if (apiResponse.data != null && apiResponse.data!.isNotEmpty) { + // patientQueueDetailsList = apiResponse.data!; + // + // // if (patientQueueDetailsList.first.isClinicConfigured == 1) { + // isPatientHasQueueAppointment = true; + // for (var element in patientQueueDetailsList) { + // if (element.patientID == patientArrivedAppointmentsHistoryList.first.patientID) { + // currentPatientQueueDetails = element; + // currentQueueStatus = element.callType!; + // // currentQueueStatus = 2; + // break; + // } + // } + // // patientQueueDetailsList.first.callType = 1; + // patientQueueDetailsList.removeWhere((element) => element.patientID == patientArrivedAppointmentsHistoryList.first.patientID); + // // } else { + // // isPatientHasQueueAppointment = false; + // // } + // } else { + // isPatientHasQueueAppointment = false; + // } notifyListeners(); if (onSuccess != null) { onSuccess(apiResponse.data); @@ -995,4 +1034,79 @@ class MyAppointmentsViewModel extends ChangeNotifier { }, ); } + + Future getDoctorsRatingCheck({ + required int doctorID, + required int projectID, + required int clinicID, + required int appointmentNo, + Function(dynamic)? onSuccess, + Function(String)? onError, + }) async { + isAppointmentRatedResponseLoading = true; + notifyListeners(); + + final result = await myAppointmentsRepo.getDoctorsRatingCheck( + doctorID: doctorID, + projectID: projectID, + clinicID: clinicID, + appointmentNo: appointmentNo, + ); + + result.fold( + (failure) async { + // await errorHandlerService.handleError(failure: failure); + if (onError != null) onError(failure.message); + isAppointmentRatedResponseLoading = false; + notifyListeners(); + }, + (apiResponse) { + if (apiResponse.messageStatus == 2) { + if (onError != null) onError(apiResponse.errorMessage ?? "Unknown error"); + isAppointmentRatedResponseLoading = false; + notifyListeners(); + } else if (apiResponse.messageStatus == 1) { + appointmentRatedResponseModel = apiResponse.data!; + isAppointmentRatedResponseLoading = false; + notifyListeners(); + if (onSuccess != null) { + onSuccess(apiResponse.data); + } + } + }, + ); + } + + Future submitDoctorRating( + {required PatientAppointmentHistoryResponseModel patientAppointmentHistoryResponseModel, + required int docRate, + required String docNote, + Function(dynamic)? onSuccess, + Function(String)? onError}) async { + final result = await myAppointmentsRepo.sendDoctorRate( + docRate, + patientAppointmentHistoryResponseModel.appointmentNo!, + patientAppointmentHistoryResponseModel.projectID!, + patientAppointmentHistoryResponseModel.doctorID!, + patientAppointmentHistoryResponseModel.clinicID!, + docNote, + patientAppointmentHistoryResponseModel.appointmentDate!, + patientAppointmentHistoryResponseModel.doctorNameObj!, + patientAppointmentHistoryResponseModel.projectName!, + patientAppointmentHistoryResponseModel.clinicName!); + + result.fold( + (failure) async => await errorHandlerService.handleError(failure: failure), + (apiResponse) { + if (apiResponse.messageStatus == 2) { + onError?.call(apiResponse.errorMessage ?? 'Unknown error'); + } else if (apiResponse.messageStatus == 1) { + notifyListeners(); + if (onSuccess != null) { + // onSuccess(apiResponse.data); + } + } + }, + ); + } } diff --git a/lib/features/my_invoices/models/get_invoices_list_response_model.dart b/lib/features/my_invoices/models/get_invoices_list_response_model.dart index fe59c045..6e515feb 100644 --- a/lib/features/my_invoices/models/get_invoices_list_response_model.dart +++ b/lib/features/my_invoices/models/get_invoices_list_response_model.dart @@ -1,88 +1,93 @@ +import 'dart:convert'; + class GetInvoicesListResponseModel { String? setupId; - int? projectID; - int? patientID; + int? projectId; + int? patientId; int? appointmentNo; String? appointmentDate; - String? appointmentDateN; - int? clinicID; - int? doctorID; + dynamic appointmentDateN; + int? clinicId; + int? doctorId; int? invoiceNo; int? status; String? arrivedOn; String? doctorName; String? doctorNameN; String? clinicName; - num? decimalDoctorRate; - String? doctorImageURL; - num? doctorRate; - num? patientNumber; + double? decimalDoctorRate; + String? doctorImageUrl; + int? doctorRate; + int? patientNumber; String? projectName; - GetInvoicesListResponseModel( - {this.setupId, - this.projectID, - this.patientID, - this.appointmentNo, - this.appointmentDate, - this.appointmentDateN, - this.clinicID, - this.doctorID, - this.invoiceNo, - this.status, - this.arrivedOn, - this.doctorName, - this.doctorNameN, - this.clinicName, - this.decimalDoctorRate, - this.doctorImageURL, - this.doctorRate, - this.patientNumber, - this.projectName}); + GetInvoicesListResponseModel({ + this.setupId, + this.projectId, + this.patientId, + this.appointmentNo, + this.appointmentDate, + this.appointmentDateN, + this.clinicId, + this.doctorId, + this.invoiceNo, + this.status, + this.arrivedOn, + this.doctorName, + this.doctorNameN, + this.clinicName, + this.decimalDoctorRate, + this.doctorImageUrl, + this.doctorRate, + this.patientNumber, + this.projectName, + }); + + factory GetInvoicesListResponseModel.fromRawJson(String str) => GetInvoicesListResponseModel.fromJson(json.decode(str)); + + String toRawJson() => json.encode(toJson()); - GetInvoicesListResponseModel.fromJson(Map json) { - setupId = json['SetupId']; - projectID = json['ProjectID']; - patientID = json['PatientID']; - appointmentNo = json['AppointmentNo']; - appointmentDate = json['AppointmentDate']; - appointmentDateN = json['AppointmentDateN']; - clinicID = json['ClinicID']; - doctorID = json['DoctorID']; - invoiceNo = json['InvoiceNo']; - status = json['Status']; - arrivedOn = json['ArrivedOn']; - doctorName = json['DoctorName']; - doctorNameN = json['DoctorNameN']; - clinicName = json['ClinicName']; - decimalDoctorRate = json['DecimalDoctorRate']; - doctorImageURL = json['DoctorImageURL']; - doctorRate = json['DoctorRate']; - patientNumber = json['PatientNumber']; - projectName = json['ProjectName']; - } + factory GetInvoicesListResponseModel.fromJson(Map json) => GetInvoicesListResponseModel( + setupId: json["SetupId"], + projectId: json["ProjectID"], + patientId: json["PatientID"], + appointmentNo: json["AppointmentNo"], + appointmentDate: json["AppointmentDate"], + appointmentDateN: json["AppointmentDateN"], + clinicId: json["ClinicID"], + doctorId: json["DoctorID"], + invoiceNo: json["InvoiceNo"], + status: json["Status"], + arrivedOn: json["ArrivedOn"], + doctorName: json["DoctorName"], + doctorNameN: json["DoctorNameN"], + clinicName: json["ClinicName"], + decimalDoctorRate: json["DecimalDoctorRate"]?.toDouble(), + doctorImageUrl: json["DoctorImageURL"], + doctorRate: json["DoctorRate"], + patientNumber: json["PatientNumber"], + projectName: json["ProjectName"], + ); - Map toJson() { - final Map data = {}; - data['SetupId'] = this.setupId; - data['ProjectID'] = this.projectID; - data['PatientID'] = this.patientID; - data['AppointmentNo'] = this.appointmentNo; - data['AppointmentDate'] = this.appointmentDate; - data['AppointmentDateN'] = this.appointmentDateN; - data['ClinicID'] = this.clinicID; - data['DoctorID'] = this.doctorID; - data['InvoiceNo'] = this.invoiceNo; - data['Status'] = this.status; - data['ArrivedOn'] = this.arrivedOn; - data['DoctorName'] = this.doctorName; - data['DoctorNameN'] = this.doctorNameN; - data['ClinicName'] = this.clinicName; - data['DecimalDoctorRate'] = this.decimalDoctorRate; - data['DoctorImageURL'] = this.doctorImageURL; - data['DoctorRate'] = this.doctorRate; - data['PatientNumber'] = this.patientNumber; - data['ProjectName'] = this.projectName; - return data; - } + Map toJson() => { + "SetupId": setupId, + "ProjectID": projectId, + "PatientID": patientId, + "AppointmentNo": appointmentNo, + "AppointmentDate": appointmentDate, + "AppointmentDateN": appointmentDateN, + "ClinicID": clinicId, + "DoctorID": doctorId, + "InvoiceNo": invoiceNo, + "Status": status, + "ArrivedOn": arrivedOn, + "DoctorName": doctorName, + "DoctorNameN": doctorNameN, + "ClinicName": clinicName, + "DecimalDoctorRate": decimalDoctorRate, + "DoctorImageURL": doctorImageUrl, + "DoctorRate": doctorRate, + "PatientNumber": patientNumber, + "ProjectName": projectName, + }; } diff --git a/lib/features/my_invoices/my_invoices_repo.dart b/lib/features/my_invoices/my_invoices_repo.dart index 68eee6e5..486fdc2a 100644 --- a/lib/features/my_invoices/my_invoices_repo.dart +++ b/lib/features/my_invoices/my_invoices_repo.dart @@ -13,6 +13,8 @@ abstract class MyInvoicesRepo { Future>> getInvoiceDetails({required num appointmentNo, required num invoiceNo, required int projectID}); Future>> sendInvoiceEmail({required num appointmentNo, required int projectID}); + + Future>> downloadInvoice({required String setupId, required int invoiceNo, required int projectID}); } class MyInvoicesRepoImp implements MyInvoicesRepo { @@ -36,7 +38,7 @@ class MyInvoicesRepoImp implements MyInvoicesRepo { }, onSuccess: (response, statusCode, {messageStatus, errorMessage}) { try { - final list = response['List_DentalAppointments']; + final list = response['List_AllInvoices']; final invoicesList = list.map((item) => GetInvoicesListResponseModel.fromJson(item as Map)).toList().cast(); @@ -138,4 +140,42 @@ class MyInvoicesRepoImp implements MyInvoicesRepo { return Left(UnknownFailure(e.toString())); } } + + @override + Future> downloadInvoice({required String setupId, required int invoiceNo, required int projectID}) async { + Map mapDevice = { + "SetupID": setupId, + "ProjectID": projectID, + "InvoiceNo": invoiceNo, + }; + + try { + GenericApiModel? apiResponse; + Failure? failure; + await apiClient.post( + DOWNLOAD_INVOICE_PDF, + body: mapDevice, + onFailure: (error, statusCode, {messageStatus, failureType}) { + failure = failureType; + }, + onSuccess: (response, statusCode, {messageStatus, errorMessage}) { + try { + apiResponse = GenericApiModel( + messageStatus: messageStatus, + statusCode: statusCode, + errorMessage: null, + data: response["InvoiceReportPDFContent"], + ); + } catch (e) { + failure = DataParsingFailure(e.toString()); + } + }, + ); + if (failure != null) return Left(failure!); + if (apiResponse == null) return Left(ServerFailure("Unknown error")); + return Right(apiResponse!); + } catch (e) { + return Left(UnknownFailure(e.toString())); + } + } } diff --git a/lib/features/my_invoices/my_invoices_view_model.dart b/lib/features/my_invoices/my_invoices_view_model.dart index d51ea086..cd18026c 100644 --- a/lib/features/my_invoices/my_invoices_view_model.dart +++ b/lib/features/my_invoices/my_invoices_view_model.dart @@ -26,6 +26,8 @@ class MyInvoicesViewModel extends ChangeNotifier { TextEditingController filterSearchController = TextEditingController(); String? selectedFilterItem; + String? downloadInvoicePDFBase64 = ""; + MyInvoicesViewModel({required this.myInvoicesRepo, required this.errorHandlerService, required this.navServices}); setInvoicesListLoading() { @@ -68,7 +70,6 @@ class MyInvoicesViewModel extends ChangeNotifier { Future getInvoiceDetails({required num appointmentNo, required num invoiceNo, required int projectID, Function(dynamic)? onSuccess, Function(String)? onError}) async { final result = await myInvoicesRepo.getInvoiceDetails(appointmentNo: appointmentNo, invoiceNo: invoiceNo, projectID: projectID); - result.fold( (failure) async { isInvoiceDetailsLoading = false; @@ -93,6 +94,32 @@ class MyInvoicesViewModel extends ChangeNotifier { ); } + Future downloadInvoicePDF({required String setupId, required int invoiceNo, required int projectID, Function(dynamic)? onSuccess, Function(String)? onError}) async { + final result = await myInvoicesRepo.downloadInvoice(setupId: setupId, invoiceNo: invoiceNo, projectID: projectID); + + result.fold( + (failure) async { + if (onError != null) { + onError(failure.message); + } + }, + (apiResponse) { + if (apiResponse.messageStatus == 2) { + // dialogService.showErrorDialog(message: apiResponse.errorMessage!, onOkPressed: () {}); + } else if (apiResponse.messageStatus == 1) { + if (apiResponse.data != null && apiResponse.data!.isNotEmpty) { + downloadInvoicePDFBase64 = apiResponse.data!; + } else { + downloadInvoicePDFBase64 = ""; + } + if (onSuccess != null) { + onSuccess(apiResponse); + } + } + }, + ); + } + Future sendInvoiceEmail({required num appointmentNo, required int projectID, Function(dynamic)? onSuccess, Function(String)? onError}) async { final result = await myInvoicesRepo.sendInvoiceEmail(appointmentNo: appointmentNo, projectID: projectID); diff --git a/lib/features/prescriptions/models/resp_models/prescription_detail_response_model.dart b/lib/features/prescriptions/models/resp_models/prescription_detail_response_model.dart index b35cfef7..ef1bc6f7 100644 --- a/lib/features/prescriptions/models/resp_models/prescription_detail_response_model.dart +++ b/lib/features/prescriptions/models/resp_models/prescription_detail_response_model.dart @@ -33,6 +33,7 @@ class PrescriptionDetailResponseModel { num? scaleOffset; String? startDate; bool? hasReminder; + bool? isHomeMedicineDeliverySupported; PrescriptionDetailResponseModel( {this.address, @@ -68,7 +69,7 @@ class PrescriptionDetailResponseModel { this.sKU, this.scaleOffset, this.startDate, - this.hasReminder = false}); + this.hasReminder = false, this.isHomeMedicineDeliverySupported}); PrescriptionDetailResponseModel.fromJson(Map json) { address = json['Address']; diff --git a/lib/features/prescriptions/prescriptions_repo.dart b/lib/features/prescriptions/prescriptions_repo.dart index 3bfcc7fe..387d6410 100644 --- a/lib/features/prescriptions/prescriptions_repo.dart +++ b/lib/features/prescriptions/prescriptions_repo.dart @@ -99,7 +99,11 @@ class PrescriptionsRepoImp implements PrescriptionsRepo { // throw Exception("prescription list is empty"); } - final prescriptionOrders = list.map((item) => PrescriptionDetailResponseModel.fromJson(item as Map)).toList().cast(); + final List prescriptionOrders = + list.map((item) => PrescriptionDetailResponseModel.fromJson(item as Map)).toList().cast(); + if (prescriptionOrders.isNotEmpty) { + prescriptionOrders.first.isHomeMedicineDeliverySupported = response['IsHomeMedicineDeliverySupported'] ?? false; + } apiResponse = GenericApiModel>( messageStatus: messageStatus, diff --git a/lib/features/profile_settings/profile_settings_repo.dart b/lib/features/profile_settings/profile_settings_repo.dart index 9d4c1221..d86bd7fd 100644 --- a/lib/features/profile_settings/profile_settings_repo.dart +++ b/lib/features/profile_settings/profile_settings_repo.dart @@ -16,6 +16,18 @@ abstract class ProfileSettingsRepo { Future>> deactivateAccount(); Future>> getProfileSettings(); + + /// Get patient profile image + Future>> getProfileImage({ + required int patientID, + }); + + /// Upload patient profile image + Future>> uploadProfileImage({ + required int patientID, + required String imageData, + required bool isActive, + }); } class ProfileSettingsRepoImp implements ProfileSettingsRepo { @@ -137,5 +149,89 @@ class ProfileSettingsRepoImp implements ProfileSettingsRepo { return Left(UnknownFailure(e.toString())); } } + + @override + Future>> getProfileImage({ + required int patientID, + }) async { + final Map body = { + 'PatientID': patientID, + }; + + try { + GenericApiModel? apiResponse; + Failure? failure; + + await apiClient.post( + GET_PROFILE_IMAGE, + body: body, + onFailure: (error, statusCode, {messageStatus, failureType}) { + failure = failureType; + }, + onSuccess: (response, statusCode, {messageStatus, errorMessage}) { + try { + apiResponse = GenericApiModel( + messageStatus: messageStatus, + statusCode: statusCode, + errorMessage: errorMessage, + data: response, + ); + } catch (e) { + failure = DataParsingFailure(e.toString()); + } + }, + ); + + if (failure != null) return Left(failure!); + if (apiResponse == null) return Left(ServerFailure("Unknown error")); + return Right(apiResponse!); + } catch (e) { + return Left(UnknownFailure(e.toString())); + } + } + + @override + Future>> uploadProfileImage({ + required int patientID, + required String imageData, + required bool isActive, + }) async { + final Map body = { + 'PatientID': patientID, + 'PatientsProfileImageData': imageData, + 'IsActive': isActive, + }; + + try { + GenericApiModel? apiResponse; + Failure? failure; + + await apiClient.post( + UPLOAD_PROFILE_IMAGE, + body: body, + onFailure: (error, statusCode, {messageStatus, failureType}) { + failure = failureType; + }, + onSuccess: (response, statusCode, {messageStatus, errorMessage}) { + try { + apiResponse = GenericApiModel( + messageStatus: messageStatus, + statusCode: statusCode, + errorMessage: errorMessage, + data: response, + ); + } catch (e) { + failure = DataParsingFailure(e.toString()); + } + }, + ); + + if (failure != null) return Left(failure!); + if (apiResponse == null) return Left(ServerFailure("Unknown error")); + return Right(apiResponse!); + } catch (e) { + return Left(UnknownFailure(e.toString())); + } + } } diff --git a/lib/features/profile_settings/profile_settings_view_model.dart b/lib/features/profile_settings/profile_settings_view_model.dart index 28f28af6..77960569 100644 --- a/lib/features/profile_settings/profile_settings_view_model.dart +++ b/lib/features/profile_settings/profile_settings_view_model.dart @@ -2,6 +2,7 @@ import 'package:easy_localization/easy_localization.dart'; import 'package:flutter/cupertino.dart'; import 'package:flutter/foundation.dart'; import 'package:get_it/get_it.dart'; +import 'package:hmg_patient_app_new/core/app_state.dart'; import 'package:hmg_patient_app_new/features/profile_settings/models/get_patient_info_response_model.dart'; import 'package:hmg_patient_app_new/features/profile_settings/profile_settings_repo.dart'; import 'package:hmg_patient_app_new/generated/locale_keys.g.dart'; @@ -41,6 +42,12 @@ class ProfileSettingsViewModel extends ChangeNotifier { late GetPatientInfoForUpdate getPatientInfoForUpdate; bool isPatientProfileLoading = false; + // ── Profile image state ────────────────────────────────────────────── + bool isProfileImageLoading = false; + bool isUploadingProfileImage = false; + String? profileImageData; + String? profileImageError; + ProfileSettingsViewModel({ required CacheService cacheService, required this.profileSettingsRepo, @@ -172,4 +179,116 @@ class ProfileSettingsViewModel extends ChangeNotifier { isCloseButtonVisible: true, ); } + + // ── Profile image ──────────────────────────────────────────────────── + + Future getProfileImage({ + required int patientID, + Function(dynamic)? onSuccess, + Function(String)? onError, + bool forceRefresh = false, // Add flag to force refresh + }) async { + // Skip API call if image is already loaded and not forcing refresh + if (!forceRefresh && GetIt.instance().getProfileImageData != null) { + // Use cached data + profileImageData = GetIt.instance().getProfileImageData; + if (onSuccess != null) { + onSuccess(profileImageData); + } + return; + } + + isProfileImageLoading = true; + profileImageError = null; + notifyListeners(); + + final result = await profileSettingsRepo.getProfileImage(patientID: patientID); + + result.fold( + (failure) { + isProfileImageLoading = false; + profileImageError = failure.message; + notifyListeners(); + if (onError != null) { + onError(failure.message); + } + }, + (response) { + isProfileImageLoading = false; + // Extract image data from response + if (response.data != null && response.data['Patient_GetProfileImageDataList'] != null) { + var imageList = response.data['Patient_GetProfileImageDataList']; + if (imageList is List && imageList.isNotEmpty) { + profileImageData = imageList[0]['PatientsProfileImageData']; + // Store in AppState for global access + GetIt.instance().setProfileImageData = profileImageData; + } else { + profileImageData = null; + GetIt.instance().setProfileImageData = null; + } + } else { + profileImageData = null; + GetIt.instance().setProfileImageData = null; + } + notifyListeners(); + onSuccess?.call(response.data); + }, + ); + } + + /// Clear cached profile image data + void clearProfileImageCache() { + profileImageData = null; + GetIt.instance().setProfileImageData = null; + notifyListeners(); + } + + /// Sync profile image data from AppState + void syncProfileImageFromAppState() { + final appStateImageData = GetIt.instance().getProfileImageData; + if (appStateImageData != null && appStateImageData.isNotEmpty) { + if (profileImageData != appStateImageData) { + profileImageData = appStateImageData; + notifyListeners(); + } + } + } + + Future uploadProfileImage({ + required int patientID, + required String imageData, + Function(dynamic)? onSuccess, + Function(String)? onError, + }) async { + isUploadingProfileImage = true; + profileImageError = null; + notifyListeners(); + + final result = await profileSettingsRepo.uploadProfileImage( + patientID: patientID, + imageData: imageData, + isActive: true, + ); + + result.fold( + (failure) { + isUploadingProfileImage = false; + profileImageError = failure.message; + notifyListeners(); + if (onError != null) { + onError(failure.message); + } else { + errorHandlerService.handleError(failure: failure); + } + }, + (response) { + isUploadingProfileImage = false; + profileImageData = imageData; + // Store in AppState for global access + GetIt.instance().setProfileImageData = imageData; + notifyListeners(); + onSuccess?.call(response.data); + }, + ); + } } diff --git a/lib/features/radiology/models/resp_models/radiology_ai_analysis_response_model.dart b/lib/features/radiology/models/resp_models/radiology_ai_analysis_response_model.dart new file mode 100644 index 00000000..e838f187 --- /dev/null +++ b/lib/features/radiology/models/resp_models/radiology_ai_analysis_response_model.dart @@ -0,0 +1,22 @@ +import 'dart:convert'; + +class RadiologyAiAnalysisResponse { + String? summary; + + RadiologyAiAnalysisResponse({ + this.summary, + }); + + factory RadiologyAiAnalysisResponse.fromRawJson(String str) => RadiologyAiAnalysisResponse.fromJson(json.decode(str)); + + String toRawJson() => json.encode(toJson()); + + factory RadiologyAiAnalysisResponse.fromJson(Map json) => RadiologyAiAnalysisResponse( + summary: json["summary"], + ); + + Map toJson() => { + "summary": summary, + }; +} + diff --git a/lib/features/radiology/radiology_repo.dart b/lib/features/radiology/radiology_repo.dart index 3ad4e54f..30b078ef 100644 --- a/lib/features/radiology/radiology_repo.dart +++ b/lib/features/radiology/radiology_repo.dart @@ -6,6 +6,7 @@ import 'package:hmg_patient_app_new/core/exceptions/api_failure.dart'; import 'package:hmg_patient_app_new/core/utils/utils.dart'; import 'package:hmg_patient_app_new/features/authentication/models/resp_models/authenticated_user_resp_model.dart'; import 'package:hmg_patient_app_new/features/radiology/models/resp_models/patient_radiology_response_model.dart'; +import 'package:hmg_patient_app_new/features/radiology/models/resp_models/radiology_ai_analysis_response_model.dart'; import 'package:hmg_patient_app_new/services/logger_service.dart'; abstract class RadiologyRepo { @@ -17,6 +18,8 @@ abstract class RadiologyRepo { {required PatientRadiologyResponseModel patientRadiologyResponseModel, required AuthenticatedUser authenticatedUser}); Future>>> getPatientRadiologyOrderByAppointment({required num appointmentNo, required num projectID}); + + Future>> getRadiologyAiAnalysis({required String reportData, required int langId,}); } class RadiologyRepoImp implements RadiologyRepo { @@ -218,4 +221,50 @@ class RadiologyRepoImp implements RadiologyRepo { return Left(UnknownFailure(e.toString())); } } + + @override + Future>> getRadiologyAiAnalysis({required String reportData, required int langId}) async { + Map payload = { + "text": reportData, + "language_id": langId + }; + + try { + GenericApiModel? apiResponse; + Failure? failure; + + await apiClient.post( + ApiConsts.getAiRadiologyAnalysis, + body: payload, + isExternal: true, + isAllowAny: true, + apiHeaders: { + 'Accept-Language': 'en-US,en;q=0.7', + 'Content-Type': 'application/json', + 'X-API-KEY': '9d9d2ac4c1fc302299dbb0fbb1b7fea1', + 'accept': 'application/json', + }, + onFailure: (error, statusCode, {messageStatus, failureType}) { + failure = failureType; + }, + onSuccess: (response, statusCode, {messageStatus, errorMessage}) { + try { + apiResponse = GenericApiModel( + messageStatus: 1, + statusCode: statusCode, + errorMessage: null, + data: RadiologyAiAnalysisResponse.fromJson(response), + ); + } catch (e) { + failure = DataParsingFailure(e.toString()); + } + }, + ); + if (failure != null) return Left(failure!); + if (apiResponse == null) return Left(ServerFailure("Unknown error")); + return Right(apiResponse!); + } catch (e) { + return Left(UnknownFailure(e.toString())); + } + } } diff --git a/lib/features/radiology/radiology_view_model.dart b/lib/features/radiology/radiology_view_model.dart index fa43a96d..7386a5a0 100644 --- a/lib/features/radiology/radiology_view_model.dart +++ b/lib/features/radiology/radiology_view_model.dart @@ -1,12 +1,16 @@ import 'package:easy_localization/easy_localization.dart'; import 'package:flutter/material.dart'; +import 'package:hmg_patient_app_new/core/app_state.dart'; +import 'package:hmg_patient_app_new/core/dependencies.dart'; import 'package:hmg_patient_app_new/extensions/string_extensions.dart'; import 'package:hmg_patient_app_new/features/authentication/models/resp_models/authenticated_user_resp_model.dart'; +import 'package:hmg_patient_app_new/features/radiology/models/resp_models/radiology_ai_analysis_response_model.dart'; import 'package:hmg_patient_app_new/features/radiology/radiology_repo.dart'; import 'package:hmg_patient_app_new/generated/locale_keys.g.dart'; import 'package:hmg_patient_app_new/presentation/radiology/radiology_result_page.dart'; import 'package:hmg_patient_app_new/services/error_handler_service.dart'; import 'package:hmg_patient_app_new/services/navigation_service.dart'; +import 'package:hmg_patient_app_new/widgets/loader/bottomsheet_loader.dart'; import 'package:hmg_patient_app_new/widgets/routes/custom_page_route.dart'; import 'models/resp_models/patient_radiology_response_model.dart'; @@ -14,6 +18,7 @@ import 'models/resp_models/patient_radiology_response_model.dart'; class RadiologyViewModel extends ChangeNotifier { bool isRadiologyOrdersLoading = false; bool isRadiologyPDFReportLoading = false; + bool isRadiologyAIAnalysisNeedsToBeShown = true; RadiologyRepo radiologyRepo; ErrorHandlerService errorHandlerService; @@ -25,6 +30,8 @@ class RadiologyViewModel extends ChangeNotifier { String radiologyImageURL = ""; String patientRadiologyReportPDFBase64 = ""; + RadiologyAiAnalysisResponse? radiologyAiAnalysisResponse; + late List _radiologySuggestionsList = []; List get radiologySuggestions => _radiologySuggestionsList; @@ -120,8 +127,7 @@ class RadiologyViewModel extends ChangeNotifier { ); } - Future getRadiologyImage( - {required PatientRadiologyResponseModel patientRadiologyResponseModel, Function(dynamic)? onSuccess, Function(String)? onError}) async { + Future getRadiologyImage({required PatientRadiologyResponseModel patientRadiologyResponseModel, Function(dynamic)? onSuccess, Function(String)? onError}) async { final result = await radiologyRepo.getRadiologyImage(patientRadiologyResponseModel: patientRadiologyResponseModel); result.fold( @@ -142,12 +148,8 @@ class RadiologyViewModel extends ChangeNotifier { } Future getRadiologyPDF( - {required PatientRadiologyResponseModel patientRadiologyResponseModel, - required AuthenticatedUser authenticatedUser, - Function(dynamic)? onSuccess, - Function(String)? onError}) async { - final result = - await radiologyRepo.getRadiologyReportPDF(patientRadiologyResponseModel: patientRadiologyResponseModel, authenticatedUser: authenticatedUser); + {required PatientRadiologyResponseModel patientRadiologyResponseModel, required AuthenticatedUser authenticatedUser, Function(dynamic)? onSuccess, Function(String)? onError}) async { + final result = await radiologyRepo.getRadiologyReportPDF(patientRadiologyResponseModel: patientRadiologyResponseModel, authenticatedUser: authenticatedUser); result.fold( (failure) async => await errorHandlerService.handleError( @@ -211,4 +213,46 @@ class RadiologyViewModel extends ChangeNotifier { } notifyListeners(); } + + void setIsRadiologyAIAnalysisNeedsToBeShown(bool value) { + isRadiologyAIAnalysisNeedsToBeShown = value; + notifyListeners(); + } + + void closeRadiologyAIAnalysis() { + radiologyAiAnalysisResponse = null; + notifyListeners(); + } + + Future getRadiologyAiAnalysis({required String reportData, required String loadingText, required int langId}) async { + LoaderBottomSheet.showLoader( + loadingText: loadingText, + showCloseButton: true, + onCloseTap: () { + setIsRadiologyAIAnalysisNeedsToBeShown(false); + }, + ); + + final result = await radiologyRepo.getRadiologyAiAnalysis(reportData: reportData, langId: langId); + + result.fold( + (failure) async { + LoaderBottomSheet.hideLoader(); + if (isRadiologyAIAnalysisNeedsToBeShown) { + await errorHandlerService.handleError(failure: failure); + } + }, + (apiResponse) { + LoaderBottomSheet.hideLoader(); + if (apiResponse.messageStatus == 1) { + if (isRadiologyAIAnalysisNeedsToBeShown) { + radiologyAiAnalysisResponse = apiResponse.data; + notifyListeners(); + } + } + }, + ); + + LoaderBottomSheet.hideLoader(); + } } diff --git a/lib/features/todo_section/todo_section_view_model.dart b/lib/features/todo_section/todo_section_view_model.dart index 0f6cc873..b14e4ead 100644 --- a/lib/features/todo_section/todo_section_view_model.dart +++ b/lib/features/todo_section/todo_section_view_model.dart @@ -13,13 +13,13 @@ class TodoSectionViewModel extends ChangeNotifier { String? notificationsCount = "0"; initializeTodoSectionViewModel() async { - if (isAncillaryOrdersNeedReloading) { - patientAncillaryOrdersList.clear(); - isAncillaryOrdersLoading = true; + // if (isAncillaryOrdersNeedReloading) { + patientAncillaryOrdersList.clear(); + isAncillaryOrdersLoading = true; isAncillaryDetailsProceduresLoading = true; notificationsCount = "0"; getPatientOnlineAncillaryOrderList(); - } + // } getPatientDashboard(); } @@ -63,13 +63,14 @@ class TodoSectionViewModel extends ChangeNotifier { } Future getPatientOnlineAncillaryOrderList({Function(dynamic)? onSuccess, Function(String)? onError}) async { - if (!isAncillaryOrdersNeedReloading) { - return; - } + // if (!isAncillaryOrdersNeedReloading) { + // return; + // } patientAncillaryOrdersList.clear(); isAncillaryOrdersLoading = true; notifyListeners(); + final result = await todoSectionRepo.getOnlineAncillaryOrderList(); result.fold( diff --git a/lib/features/translations/custom_asset_loader.dart b/lib/features/translations/custom_asset_loader.dart new file mode 100644 index 00000000..8cf9e7c9 --- /dev/null +++ b/lib/features/translations/custom_asset_loader.dart @@ -0,0 +1,27 @@ +import 'dart:convert'; +import 'dart:io'; +import 'dart:ui'; +import 'package:easy_localization/easy_localization.dart'; +import 'package:flutter/services.dart'; +import 'package:path_provider/path_provider.dart'; + +class RemoteFileLoader extends AssetLoader { + @override + Future?> load(String path, Locale locale) async { + final directory = await getApplicationDocumentsDirectory(); + + // easy_localization uses toLanguageTag() which gives 'en-US' or 'ar-SA' + final fileName = "${locale.toLanguageTag()}.json"; + final file = File("${directory.path}/$fileName"); + + if (await file.exists()) { + final String jsonString = await file.readAsString(); + return jsonDecode(jsonString); + } else { + // If the download failed or hasn't happened yet, load from local assets + // path is the 'path' property from the EasyLocalization widget + final localData = await rootBundle.loadString('$path/$fileName'); + return jsonDecode(localData); + } + } +} diff --git a/lib/features/translations/translation_service.dart b/lib/features/translations/translation_service.dart new file mode 100644 index 00000000..b02a49cd --- /dev/null +++ b/lib/features/translations/translation_service.dart @@ -0,0 +1,36 @@ +import 'dart:io'; +import 'package:flutter/material.dart'; +import 'package:hmg_patient_app_new/core/api_consts.dart'; +import 'package:http/http.dart' as http; +import 'package:path_provider/path_provider.dart'; + +class TranslationService { +// Replace with your actual hosting URL + static final String baseUrl = ApiConsts.googleCloudStorageENTranslationFileBaseURL; + + static Future downloadTranslations() async { + final directory = await getApplicationDocumentsDirectory(); + +// Ensure these match your locale codes exactly (en-US, ar-SA) + List files = ["en-US.json", "ar-SA.json"]; + + for (String fileName in files) { + try { + final String timestamp = DateTime.now().millisecondsSinceEpoch.toString(); + String url = "$baseUrl/$fileName?v=$timestamp"; + debugPrint(url); + // final response = await http.get(Uri.parse("$baseUrl/$fileName")); + final response = await http.get(Uri.parse(url)); + + if (response.statusCode == 200) { + final file = File("${directory.path}/$fileName"); + await file.writeAsBytes(response.bodyBytes); + } else { + print("Failed to download $fileName: Status ${response.statusCode}"); + } + } catch (e) { + print("Network error while downloading $fileName: $e"); + } + } + } +} diff --git a/lib/generated/codegen_loader.g.dart b/lib/generated/codegen_loader.g.dart new file mode 100644 index 00000000..17950a46 --- /dev/null +++ b/lib/generated/codegen_loader.g.dart @@ -0,0 +1,3442 @@ +// DO NOT EDIT. This is code generated via package:easy_localization/generate.dart + +// ignore_for_file: prefer_single_quotes, avoid_renaming_method_parameters, constant_identifier_names + +import 'dart:ui'; + +import 'package:easy_localization/easy_localization.dart' show AssetLoader; + +class CodegenLoader extends AssetLoader{ + const CodegenLoader(); + + @override + Future?> load(String path, Locale locale) { + return Future.value(mapLocales[locale.toString()]); + } + + static const Map _ar_SA = { + "english": "الإنجليزية", + "arabic": "العربية", + "login": "تسجيل الدخول", + "noDataAvailable": "لا توجد بيانات متاحة", + "noRatingAvailable": "لا يوجد تقييم متاح", + "doctorDoesNotHaveRating": "لم يحصل هذا الطبيب على أي تقييمات بعد.", + "confirm": "تأكيد", + "loadingText": "جاري التحميل، يرجى الانتظار...", + "kilometerUnit": "كم", + "dashboardScreenToolbarTitle": "الرئيسية", + "settings": "الإعدادات", + "language": "لغة التطبيق", + "lanEnglish": "الإنجليزية", + "lanArabic": "العربية", + "cancel": "إلغاء", + "done": "تم", + "replay2": "إعادة التشغيل", + "home": "الرئيسية", + "services": "الخدمات", + "mySchedule": "جدولي", + "logout": "تسجيل الخروج", + "respirationRate": "معدل التنفس", + "bookAppo": "حجز موعد", + "searchBy": "البحث بواسطة:", + "clinic": "عيادة", + "byClinic": "حسب العيادة", + "hospital": "مستشفى", + "byHospital": "حسب المستشفى", + "name": "الاسم", + "doctor": "طبيب", + "clinicName": "اسم العيادة", + "hospitalName": "اسم المستشفى", + "noClinicFound": "لم يتم العثور على عيادة", + "doctorName": "اسم الطبيب", + "nearestAppo": "أقرب موعد", + "searchByDocText": "اكتب اسم الطبيب لمساعدتك في العثور عليه", + "enterDocName": "أدخل اسم الطبيب", + "search": "بحث", + "noResultFound": "لم يتم العثور على نتائج", + "noSearchResultFound": "لم يتم العثور على نتائج", + "pleaseEnterProductName": "يرجى إدخال اسم المنتج", + "bookNow": "احجز الآن", + "docInfo": "معلومات الطبيب", + "appoInfo": "معلومات الموعد", + "availableAppo": "المواعيد المتاحة", + "gender": "الجنس", + "nationality": "الجنسية", + "neverWait": "لا تنتظر في الطابور أبدًا", + "reminderTitle": "لديك موعد مع الدكتور.", + "appointmentCalendar": "هل ترغب في تعيين تذكير لهذا الموعد في التقويم الخاص بك؟", + "appointmentSuccess": "تم حجز الموعد بنجاح", + "docQualifications": "مؤهلات الطبيب", + "pressAgain": "اضغط مرة أخرى للخروج من التطبيق", + "laserMaxLimitReach": "الحد الأقصى هو 90 دقيقة", + "confirmAppoHeading": "يرجى مراجعة موعدك", + "patientInfo": "معلومات المريض", + "doctorFilter": "سيتم تصفية الأطباء بناءً على جنسك وعمرك", + "bookSuccess": "تم الحجز بنجاح", + "patientShare": "نسبة المريض", + "patientShareWithTax": "نسبة المريض مع الضريبة", + "confirmAppo": "تأكيد الموعد", + "confirmLiveCare": "تأكيد ال لايف كير", + "waitingForDoctor": "في انتظار الطبيب", + "appointment": "موعد", + "confirmLater": "تأكيد لاحقًا", + "todoList": "قائمة المهام", + "appoActions": "إجراءات الموعد", + "date": "التاريخ", + "time": "الوقت", + "pendingPayment": "معلق للدفع", + "payNow": "ادفع الآن", + "viewQR": "عرض رمز الاستجابة السريعة", + "instruction": "تعليمات", + "livecare": "لايف كير", + "livecareAppo": "موعد لايف كير", + "agreeTo": "أوافق على", + "termsConditoins": "الشروط والأحكام", + "cancelAppoMsg": "هل أنت متأكد أنك تريد إلغاء هذا الموعد؟", + "changePayment": "تغيير طريقة الدفع", + "upcomingNoAction": "لا يتطلب أي إجراء", + "upcomingConfirm": "يرجى تأكيد الموعد لتجنب الإلغاء", + "bookSuccessConfirmMore2412": "ستكون عملية الدفع عبر الإنترنت متاحة قبل 24 ساعة ��ن الموعد.", + "upcomingPaymentPending": "سيتم تفعيل الدفع عبر الإنترنت قبل 24 ساعة من موعد الموعد", + "upcomingPaymentNow": "ادفع عبر الإنترنت الآن لتجنب الانتظار الطويل", + "upcomingQR": "استخدم رمز الاستجابة السريعة لتسجيل الوصول في المستشفى", + "upcomingVirtual": "هذا موعد افتراضي، يرجى الالتزام بالتعليمات لتجربة سلسة.", + "upcomingLivecare": "هذا موعد لايف كير", + "upcomingDetails": "المزيد من التفاصيل", + "reschedule": "إعادة جدولة", + "raise": "رفع", + "complaint": "شكوى", + "add": "إضافة", + "reminder": "تذكير", + "location": "المواقع", + "online": "عبر الإنترنت", + "payment": "الدفع", + "onlinePayment": "عبر الإنترنت", + "onlinePaymentSubtitle": "الدفع", + "cancelNocaps": "إلغاء", + "setReminder": "ضبط تذكير", + "nationalIdNumber": "رقم الهوية الوطنية", + "medicalFileNumber": "رقم الملف الطبي", + "loginregister": "تسجيل الدخول / التسجيل", + "poweredBy": "مدعوم من", + "welcome": "مرحبًا", + "welcomeToDrSulaiman": "مرحباً بكم في مجموعة الدكتور سليمان الحبيب الطبية", + "patientApp": "تطبيق المريض", + "welcomeText": "تطبيق الدكتور سليمان الحبيب للهاتف المحمول", + "drSulaimanText": "الدكتور سليمان الحبيب", + "welcomeText2": "هل زرت مستشفيات أو مراكز الدكتور سليمان الحبيب من قبل؟", + "yes": "نعم", + "no": "لا", + "logintyperadio": "اختر من الخيارات أدناه لتسجيل الدخول إلى ملفك الطبي.", + "registernow": "سجل الآن", + "nationalID": "أدخل رقم الهوية", + "nationalId": "الهوية الوطنية", + "fileNo": "رقم الملف", + "fileno": "رقم الملف", + "forgotFileNo": "نسيت رقم الملف؟", + "forgotFileNoTitle": "نسيت الملف الطبي", + "enterNationalId": "يرجى إدخال رقم الهاتف المحمول ورقم الهوية", + "profileInfo": "يرجى إدخال معلومات الملف الشخصي", + "submit": "إرسال", + "forgotDesc": "أدخل رقم الهاتف المحمول لاستلام رقم الملف الطبي عبر الرسائل القصيرة", + "dob": "تاريخ الميلاد:", + "hijriDate": "الت��ريخ الهجري", + "gregorianDate": "التاريخ الميلادي", + "verifyLoginWith": "يرجى اختيار واحدة من الخيارات التالية للتحقق", + "registerUser": "تسجيل", + "verifyWithFingerprint": "البيومترية", + "verifyWithFaceid": "معرف الوجه", + "verifyWithSms": "رسالة قصيرة", + "verifyWithWhatsapp": "واتساب", + "verifyThrough": "التحقق من خلال", + "lastLogin": "آخر تسجيل دخول في:", + "lastLoginWith": "نوع التحقق:", + "verifyFingerprint": "لتفعيل خدمة تسجيل الدخول ببصمة الإصبع، يرجى التحقق من البيانات باستخدام واحدة من الخيارات التالية.", + "searchMedicine": "ابحث عن دواء", + "pharmaciesList": "قائمة الصيدليات", + "searchMedicineHere": "ابحث عن دواء هنا", + "description": "الوصف", + "howToUse": "كيفية الاستخدام", + "price": "السعر", + "youCanFindItIn": "يمكنك العثور عليه في", + "pleaseEnterMedicineName": "يرجى إدخال اسم الدواء", + "verificationMessage": "يرجى إدخال رمز التحقق المرسل إلى", + "validationMessage": "ينتهي رمز التحقق في", + "notification": "الإشعارات", + "appSettings": "إعدادات التطبيق", + "rateApp": "قيم تطبيقنا", + "before": "قبل", + "minute": "دقائق", + "hour": "ساعة", + "reminderSuccess": "تمت إضافة التذكير بنجاح", + "reminderCancelSuccess": "تم إلغاء التذكير بنجاح", + "patientShareToDo": "المبلغ قبل الضريبة: ", + "patientTaxToDo": "قيمة الضريبة: ", + "patientShareTotalToDo": "إجمالي المبلغ المستحق: ", + "paymentMethod": "طريقة الدفع", + "noNeedToWaitInLine": "لا حاجة للانتظار في الطابور.", + "useQRAppoAttend": "استخدم رمز الاستجابة السريعة لتسجيل حضور الموعد.", + "passQRAppoAttend": "مرر رمز الاستجابة السريعة عبر أجهزة الحضور المتاحة في المستشفى.", + "sitWaitingQR": "اجلس في غرف الانتظار حتى يتم استدعاؤك من قبل الممرضة.", + "attendRegisterCode": "رمز تسجيل الحضور", + "scanQRHospital": "توجّه إلى لوحة تسجيل الوصول الإلكتروني في المستشفى، وقم بالمسح عبر تقنية NFC أو رمز الاستجابة السريعة (QR) لإتمام تسجيل الوصول.", + "scanNFC": "امسح NFC لتسجيل الوصول", + "sendEmail": "إرسال بريد إلكتروني", + "success": "تم بنجاح", + "emailSentSuccessfully": "تم إرسال البريد الإلكتروني بنجاح", + "emailSentError": "خطأ في إرسال البريد الإلكتروني", + "close": "إغلاق", + "closeIt": "أغلق", + "booked": "محجوز", + "confirmed": "مؤكد", + "arrived": "وصل", + "payNowBookSuccess": "ادفع الآن عبر تطبيق الحبيب", + "payNowBookSuccesstext1": "ادفع الآن باستخدام خدمة الدفع عبر الإنترنت من بوابات الدفع الآمنة", + "payNowBookSuccesstext2": "يمكنك أيضًا الدفع لاحقًا عبر الدفع الإلكتروني أو في المستشفى.", + "payLater": "ادفع لاحقًا", + "askDocNotAllowed": "هذه الخدمة ستكون متاحة فقط لزيارة الطبيب قبل 15 يومًا", + "moreVerify": "خيارات تحقق إضافية", + "welcomeBack": "مرحبًا بعودتك!", + "accountInfo": "هل ترغب في تسجيل الدخول باستخدام اسم المستخدم الحالي؟", + "anotherAcc": "استخدم حسابًا آخر", + "next": "التالي", + "firstName": "الاسم الأول", + "middleName": "اسم الأب", + "lastName": "اسم العائلة", + "female": "أنثى", + "male": "ذكر", + "preferredLanguage": "اللغة المفضلة", + "locationsRegister": "أين ترغب في إنشاء هذا الملف؟", + "ksa": "السعودية", + "dubai": "دبي", + "enterEmail": "يرجى إدخال البريد الإلكتروني", + "family": "عائلتي", + "familyTitle": "ملفات عائلتي", + "myFamily": "عائلتي", + "addNewMember": "إضافة فرد جديد للعائلة", + "sentRequests": "الطلبات المرسلة", + "recievedRequests": "الطلبات المستلمة", + "manageFiles": "إدارة ملفات العائلة", + "oxygenation": "تشبع الأكسجين", + "bodyMeasurements": "مؤشر كتلة الجسم", + "temperature": "درجة الحرارة", + "pulse": "النبض", + "respiration": "التنفس", + "bloodPressure": "ضغط الدم", + "painScale": "مقياس الألم", + "weight": "الوزن", + "height": "الطول", + "heart": "معدل ضربات القلب", + "heightUnit": "وحدة الطول", + "weightUnit": "وحدة الوزن", + "unit": "الوحدة", + "request": "طلب", + "memberName": "اسم العضو", + "switchLogin": "تبديل", + "removeMember": "إزالة", + "allowView": "السماح", + "rejectView": "رفض", + "deleteView": "حذف", + "approvals": "الموافقات", + "approvalNo": "رقم الموافقة:", + "companyName": "اسم الشركة:", + "receiptOn": "الإيصال على:", + "expiryDate": "تاريخ الانتهاء:", + "expiryPoints": "منتهي الصلاحية", + "expiryOn": "ينتهي في:", + "procedureName": "اسم الإجراء:", + "procedure": "الإجراء", + "procedureStatus": "حالة الإجراء: ", + "usageStatus": "حالة الاستخدام", + "unusedCount": "عدد غير المستخدم:", + "totalApproval": "إجمالي الموافقة غير المستخدمة", + "category": "الفئة: ", + "expirationDate": "تاريخ انتهاء الصلاحية: ", + "patientCard": "رقم بطاقة المريض: ", + "policyNumber": "رقم الوثيقة: ", + "seeDetails": "عرض التفاصيل", + "insuranceCards": "بطاقات التأمين", + "requestType": "نوع الطلب", + "registerInfoFamily": "كيف تود إضافة عضو جديد؟", + "removeFamilyMember": "هل تريد إزالة هذا العضو؟", + "myMedicalFile": "ملفي الطبي", + "myMedicalFileSubTitle": "جميع سجلاتك الطبية", + "viewMore": "عرض المزيد", + "homeHealthCareService": "خدمة الرعاية الصحية المنزلية", + "onlinePharmacy": "الصيدلية الإلكترونية", + "emergencyService": "خدمة الطوارئ", + "onlinePaymentService": "خدمة الدفع عبر الإنترنت", + "offersAndPackages": "طلب تحويل عبر الإنترنت", + "comprehensiveMedicalCheckup": "فحص طبي شامل", + "hmgService": "خدمة HMG", + "viewAllHabibMedicalService": "عرض جميع خدمات الحبيب الطبية", + "viewAll": "عرض الكل", + "view": "عرض", + "contactUs": "اتصل بنا", + "contactUsLocation": "ص.ب: 91877 - الرياض 11643، طريق الملك فهد - العليا - المملكة العربية السعودية", + "contactUsTime": "السبت - الأربعاء 8:00 صباحًا - 10 مساءً، الخميس 8:00 صباحًا- 8:00 مساءً، الجمعة 2:00 مساءً - 8:00 مساءً", + "viewAllWaysReachUs": "عرض جميع طرق التواصل معنا", + "medicalProfile": "الملف الطبي", + "consultation": "استشارة", + "logs": "السجلات", + "textToSpeech": "كيف يمكنني مساعدتك؟", + "locationDialogMessage": "السماح لتطبيق HMG بالوصول إلى موقعك سيساعدك في عرض المستشفيات الأقرب إليك.", + "userViewRequester": "يريد المستخدم عرض ملفك الطبي", + "userView": "يمكن للمستخدم عرض ملفك الطبي", + "parking": "موقف السيارات", + "alhabiServices": "خدمة HMG", + "parkingTitle": "خدمة السيارة، خدمة السيارة، خدمة لحفظ معلومات الموقف، العودة إليها لاحقًا، 1- من خلال النقر على (قراءة الرمز)، حفظ بيانات الموقف. 2- من خلال النقر على زر (عرض موقف سيارتي)، يظهر لك موقع السيارة في خرائط Google. 3- قراءة موضع آخر بالضغط على زر مسح بيانات الموضع.", + "parkingDescription": "خدمة المواقف هي لك لمسح موقع السيارة حتى تتمكن من العثور عليها بسهولة عند مغادرتك. انقر على زر (مسح الموقف) لحفظ موقع الموقف، ثم سيظهر زر (عرض موقفي) لإظهار الطريق إلى الموقف. إذا كنت ترغب في إعادة مسح موقف السيارة QR فقط انقر على (مسح بياناتي).", + "appointments": "المواعيد", + "all2": "الكل", + "checkinOption": "تسجيل الوصول", + "readBarcode": "قراءة الكود", + "showMyPark": "عرض الموقف", + "clearMyData": "امسح البيانات", + "floor": "الطابق:", + "gate": "البوابة:", + "building": "المبنى:", + "branch": "الفرع:", + "emergencyServices": "خدمات الطوارئ", + "nearester": "أقرب طوارئ", + "locationa": "الموقع", + "callNow": "اتصل الآن", + "ambulancerequest": "طلب إسعاف", + "requestA": "طلب", + "noBookedAppointments": "لا توجد مواعيد محجوزة", + "noConfirmedAppointments": "لا توجد مواعيد مؤكدة", + "noArrivedAppointments": "لا توجد مواعيد تم تسجيل الوصول إليها", + "myAppointmentsList": "المواعيد", + "myAppointments": "مواعيدي", + "radiology": "الأشعة", + "radiologySubtitle": "النتيجة", + "lab": "المختبر", + "labSubtitle": "النتائج", + "medicines": "الأدوية", + "medicinesSubtitle": "الوصفات", + "vitalSigns": "علامات حيوية", + "vitalSignsSubTitle": "التقارير", + "vitalSignNormal": "طبيعي", + "vitalSignLow": "منخفض", + "vitalSignHigh": "مرتفع", + "vitalSignObese": "بدين", + "vitalSignOverweight": "زيادة الوزن", + "vitalSignUnderweight": "نقص الوزن", + "myMedical": "نشط", + "myMedicalSubtitle": "الأدوية", + "myDoctor": "أطبائي", + "myDoctorSubtitle": "القائمة", + "eye": "العين", + "eyeSubtitle": "القياس", + "insurance": "التأمين", + "insuranceSubtitle": "البطاقة", + "updateInsurance": "تحديث", + "updateInsuranceSubtitle": "التأمين", + "insuranceApproval": "التأمين", + "insuranceApprovalSubtitle": "الموافقات", + "allergies": "الحساسية", + "allergiesSubtitle": "القائمة", + "myVaccines": "لقاحاتي", + "myVaccinesSubtitle": "القائمة", + "medical": "طبي", + "medicalSubtitle": "التقرير", + "monthly": "شهري", + "monthlySubtitle": "التقرير", + "sick": "مريض", + "sickSubtitle": "الإجازات", + "myBalance": "رصيدي", + "myBalanceSubtitle": "الرصيد", + "patientCall": "مكالمة المريض", + "patientCallSubtitle": "الخدمة", + "smartWatches": "الساعات الذكية", + "smartWatchesSubtitle": "الاقتران", + "myTrackers": "متعقباتي", + "myTrackersSubtitle": "الخدمة", + "askYour": "اسأل", + "askYourSubtitle": "طبيبك", + "internet": "الإنترنت", + "internetSubtitle": "الاقتران", + "chatbot": "الدرد��ة الآلية", + "chatbotSubtitle": "", + "timeLine": "الخط الزمني", + "labOrders": "طلبات المختبر", + "billNo": "رقم الفاتورة:", + "prescriptions": "الوصفات", + "history": "التاريخ", + "orderNo": "رقم الطلب", + "orderDetails": "تفاصيل الطلب", + "deliveryDriverTrack": "تتبع السائق", + "deliveryLocation": "موقع التسليم", + "driver": "السائق", + "vitalSign": "علامة حيوية", + "monthlyReports": "تقارير شهرية", + "km": "كم:", + "km_": "كم", + "patientHealthSummaryReport": "تقرير ملخص صحة المريض", + "toViewTheTermsAndConditions": "لعرض الشروط والأحكام", + "clickHere": "انقر هنا", + "iAgreeToTheTermsAndConditions": "أوافق على الشروط والأحكام ", + "iAgreeToTheTermsAndConditionsSubtitle": "أوافق على الشروط والأحكام ", + "save": "حفظ", + "userAgreement": "اتفاقية المستخدم", + "updateSuccessfully": "تم التحديث بنجاح", + "checkVaccineAvailability": "تحقق من توفر اللقاح", + "myVaccinesAvailability": "توفر اللقاحات الخاصة بي", + "paymentService": "خدمة الدفع", + "paymentOnline": "الخدمة", + "onlineCheckIn": "تسجيل الوصول عبر الإنترنت", + "myBalances": "رصيدي", + "myWallet": "محفظتي", + "balanceAmount": "مبلغ المحفظة", + "totalBalance": "إجمالي الرصيد", + "createAdvancedPayment": "إعادة شحن المحفظة", + "advancePayment": "دفع مسبق", + "advancePaymentLabel": "يمكنك إنشاء وإضافة دفعة مقدمة لحسابك أو حسابات أخرى.", + "fileNumber": "رقم الملف", + "amount": "المبلغ *", + "depositorEmail": "البريد الإلكتروني للودائع *", + "notes": "ملاحظات", + "selectPatientName": "اختر اسم المريض", + "selectFamilyPatientName": "أفراد العائلة", + "selectHospital": "اختر المستشفى", + "selectCity": "اختر المدينة", + "city": "المدينة", + "distance": "المسافة", + "myAccount": "حسابي", + "otherAccount": "حساب آخر", + "selectBeneficiary": "اختر المستفيد", + "confirmThePayment": "تأكيد الدفع", + "depositorName": "اسم مُقدِّم الإيداع", + "mobileNumber": "رقم الجوال", + "phoneNumber": "رقم الهاتف", + "country": "البلد", + "ok": "حسنا", + "averageWaitingTime": "متوسط وقت الانتظار:", + "waitingTime": "وقت الانتظار المتوقع:", + "waterConsumedInWeek": "الماء المستهلك في أسبوع", + "waterConsumedInMonth": "الماء المستهلك في شهر", + "theVerificationCodeExpiresIn": "ينتهي رمز التحقق في", + "pleaseEnterTheVerificationCode": "يرجى إدخال رمز التحقق المرسل إلى", + "eyeMeasurements": "قياسات العين", + "measurements": "قياسات", + "classes": "نظارات", + "contactLens": "عدسات لاصقة", + "rightEye": "العين اليمنى", + "sphere": "الكرة", + "cylinder": "الأسطوانة", + "axis": "المحور", + "prism": "الهرم", + "va": "VA", + "leftEye": "العين اليسرى", + "brand": "العلامة التجارية", + "power": "القوة", + "diameter": "القطر", + "remarks": "ملاحظات: ", + "activeMedications": "الأدوية النشطة", + "expDate": "تاريخ انتهاء الصلاحية:", + "route": "الطريق", + "frequency": "التكرار", + "dailyQuantity": "��لكمية اليومية:", + "addReminder": "إضافة تذكير", + "cancelReminder": "إلغاء التذكير", + "reminderDes": "يرجى تحديد يوم ووقت بدء العلاج ليتم إعلامك عندما يحين وقت تناول الدواء", + "pleaseSelectAllQuestionToContinue": "يرجى الإجابة على جميع الأسئلة للمتابعة...", + "startDay": "يوم البدء", + "endDay": "يوم الانتهاء", + "days": "أيام ", + "pleaseVerify": "يرجى التحقق ", + "scheduleTime": "وقت الجدولة", + "askDoctor": "اسأل الطبيب", + "doctorResponses": "ردود الطبيب", + "ne_w": "جديد", + "all": "الكل", + "dailyWater": "متابعة الماء اليومية", + "questionHere": "أدخل السؤال هنا...", + "viewDoctorResponses": "عرض ردود الطبيب", + "serviceInformationButton": "تسجيل الدخول / التسجيل", + "serviceInformationTitle": "معلومات الخدمة", + "serviceInformation": "معلومات الخدمة", + "homeHealthCare": "الرعاية الصحية المنزلية", + "noAppointmentAvailable": "لا توجد مواعيد متاحة", + "loginRegister": "تسجيل الدخول / التسجيل", + "orderLog": "سجل الطلب", + "infoLab": "تتيح لك هذه الخدمة عرض نتائج جميع الفحوصات المخبرية التي أجريت في مجموعة الحبيب الطبية بالإضافة إلى إرسال التقرير عبر البريد الإلكتروني.", + "infoRadiology": "تتيح لك هذه الخدمة عرض تقارير وصور الأشعة في مجموعة الحبيب الطبية بالإضافة إلى إرسال التقرير عبر البريد الإلكتروني.", + "noReviewsAvailable": "لا توجد تقييمات متاحة", + "noLocationAvailable": "لا توجد مواقع متاحة", + "orders": "الطلبات", + "lakum": "لكم", + "lakumMsg": "لا توجد تفاصيل عن النقاط", + "lakumPoint": "نقاط لكم", + "wishlist": "قائمة الرغبات", + "products": "المنتجات", + "reviews": "التعليقات", + "brands": "العلامات التجارية", + "productDetails": "تفاصيل المنتج", + "medicationRefill": "إعادة تعبئة الدواء", + "pillReminder": "تذكير الأدوية", + "shippingAddresses": "عناوين الشحن", + "reachUs": "تواصل معنا", + "ourLocations": "مواقعنا", + "edit": "تعديل", + "whatsApp": "واتساب", + "phone": "الهاتف", + "delete": "حذف", + "deleteAddress": "هل أنت متأكد أنك تريد الحذف", + "deletedAddres": "تم حذف العنوان", + "addAddress": "إضافة عنوان جديد", + "addNewAddress": "إضافة عنوان جديد", + "order": "طلبي", + "delivered": "تم التوصيل", + "pending": "قيد الانتظار", + "enterNameHere": "أدخل اسمك هنا", + "processing": "جاري المعالجة", + "cancelled": "ملغى", + "writeReview": "اكتب تقييمًا", + "shareReview": "شارك التقييم", + "review": " التقييمات", + "viewMedicalFile": "عرض التفاصيل", + "viewAllServices": "عرض جميع الخدمات", + "medicalFile": "الملف الطبي", + "verified": "تم التحقق", + "checkup": "فحص", + "hhcHome": "الرئيسية", + "refferal": "إحالة إلكترونية", + "refferalTitle": "إحالة إلكترونية", + "refferalSubTitle": "الخدمة", + "healthCare": "الرعاية الصحية", + "emergency": "حالة طوارئ", + "erservices": "خدمات الطوارئ", + "services2": "الخدمات", + "cantSeeProfile": "لرؤية ملفك الطبي، يرجى تسجيل الدخول أو التسجيل الآن", + "loginRegisterNow": "تسجيل الدخول أو التسجيل الآن", + "hmgPharmacy": "صيدلية الحبيب", + "ecommerceSolution": "حلول التجارة الإلكترونية", + "comprehensive": "شامل", + "onlineConsulting": "استشارات عبر الإنترنت", + "pendingOrder": " قيد الانتظار", + "deliveredOrder": " تم التوصيل", + "processingOrder": " جارٍ المعالجة", + "cancelledOrder": " ملغى", + "compare": " قارن", + "medicationsRefill": " إعادة تعبئة الأدوية", + "recommended": " موصى به لك", + "myPrescription": " وصفتي", + "quantity": " الكمية ", + "reviewAppointment": " مراجعة الموعد ", + "backMyAccount": "العودة إلى حسابي ", + "reviewSuccessful": "تمت مراجعة الطلب بنجاح", + "reviewShared": "تمت مشاركة تقييمك في قسم تقييم المنتج", + "reviewComment": "تساعد تقييماتك الآخرين على اختيار منتج أفضل", + "shippedMethod": "الشحن بواسطة:", + "orderDetail": "تفاصيل الطلب", + "orderSummary": "ملخص الطلب", + "subtotal": "المجموع الفرعي", + "shipping": "الشحن", + "shipBy": "الشحن بواسطة:", + "lakumPoints": "نقاط لكم", + "useLakumPoints": "استخدم نقاط لكم", + "use": "استخدم", + "proceedPay": "المتابعة للدفع", + "vat": "ضريبة القيمة المضافة (15%)", + "inclusiveVat": "(شاملة ضريبة القيمة المضافة)", + "items": "عنصر(عناصر)", + "checkOut": "الدفع", + "sar": "ريال سعودي", + "aed": "درهم إماراتي", + "payOnline": "الدفع عبر الإنترنت", + "cancelOrder": "إلغاء الطلب", + "confirmAddress": "تأكيد العنوان ", + "confirmLocation": "أكد الموقع ", + "conditionsHMG": "الشروط والأحكام ", + "conditions": "الشروط والأحكام لكوم", + "confirmDeleteMsg": "هل أنت متأكد! تريد الحذف ", + "confirmDelete": "حذف", + "confirmCancellation": "هل أنت متأكد! تريد إلغاء هذا الطلب ", + "orderNumber": "رقم الطلب: ", + "orderDate": "التاريخ", + "itemsNo": "عنصر(عناصر)", + "noOrder": "ليس لديك أي طلبات.", + "noResult": "لا توجد نتيجة.", + "nonRecommended": "لا توجد منتجات موصى بها.", + "termsService": "شروط الخدمة", + "beforeusing": "قبل استخدام الفحص، يرجى قراءة شروط الخدمة.", + "accept": "أقرأ وأقبل شروط الخدمة وسياسة الخصوصية", + "dataSafeInfo": "المعلومات التي تقدمها مجهولة الهوية وليست مشتركة مع أي شخص.", + "dataSafe": " بياناتك آمنة.", + "informational": "الفحص لأغراض معلوماتية وليس رأيًا طبيًا مؤهلاً", + "notUseInEmerbency": "لا تستخدم في حالات الطوارئ.", + "notUseInEmerbencyDetails": "في حالة الطوارئ الصحية، ", + "notUseInEmerbencyDetailsCall": "اتصل بأقرب رقم طوارئ على الفور", + "checkDiagnosis": "الفحص ليس تشخيصًا.", + "remeberthat": "تذكر أن", + "loginToUseService": "تحتاج إلى تسجيل الدخول لاستخدام هذه الخدمة", + "offersAndPromotions": "عروض وترويجيات خاصة", + "offers": "عروض", + "myPrescriptions": "وصفاتي", + "searchAndScanMedication": "البحث والمسح الضوئي للدواء", + "shopByBrands": "تسوق حسب العلامات التجارية", + "recentlyViewed": "تمت مشاهدته مؤخرًا", + "bestSellers": "الأكثر مبيعًا", + "deleteAllItems": "حذف جميع العناصر", + "total": "الإجمالي", + "totalWithColonRight": "الإجمالي:", + "selectAddress": "اختر العنوان", + "shippingAddress": "عنوان الشحن", + "changeAddress": "تغيير العنوان", + "selectPaymentOption": "اختر طريقة الدفع", + "selectTamaraPlan": "اختر خطة دفع تمارا", + "changeMethod": "تغيير الطريقة", + "reviewOrder": "مراجعة الطلب", + "active": "نشط", + "inactive": "غير نشط", + "balance": "الرصيد", + "gained": "المكتسب", + "consumed": "المستهلك", + "transferred": "المحول", + "riyal": "ريال", + "membersince": "عضو منذ", + "identification": "رقم الهوية", + "lakumMobile": "رقم الجوال", + "waitinggained": "انتظار المكتسب", + "expired": "منتهي", + "willBeExpired": "سينتهي", + "activateLAKUMAccount": "تفعيل حساب لكم", + "checkBeneficiary": "تحقق من المستفيد", + "beneficiaryName": "اسم المستفيد", + "accountActivation": "تفعيل الحساب", + "lakumTransfer": "تحويل لكم", + "acceptLbl": "قبول", + "declineLbl": "رفض", + "selectGender": "اختر الجنس", + "iAmA": "أنا ...", + "selectAge": "اختر عمرك", + "select": "اختر", + "iAm": "أنا", + "yearsOld": "سنوات", + "dragPoint": "اسحب النقطة لتغيير عمرك", + "refine": "تنقيح", + "subGroup": "مجموعة فرعية", + "max": "الحد الأقصى", + "compeleteOrderMsg": "تم تقديم الطلب بنجاح!!", + "addToCompareMsg": "لقد أضفت منتجًا إلى قائمة المقارنة", + "itInListMsg": "العنصر موجود بالفعل في القائمة", + "compareListFull": "قائمة المقارنة ممتلئة", + "addQuantity": "يجب عليك إضافة الكمية", + "addToCartMsg": "لقد أضفت منتجًا إلى عربة التسوق", + "addToWishlistMsg": "لقد أضفت منتجًا إلى قائمة الرغبات", + "notifyMeMsg": "سوف يتم إبلاغك عند توفر المنتج", + "removeFromWishlistMsg": "لقد أزلت منتجًا من قائمة الرغبات", + "min": "الحد الأدنى", + "reset": "إعادة تعيين", + "apply": "تطبيق", + "viewCategorise": "عرض جميع الفئات", + "viewSubCategorise": "عرض جميع الفئات الفرعية", + "categorise": "الفئات", + "wishList": "قائمة الرغبات", + "cart": "عربة التسوق", + "alhabibapp": "تطبيق الحبيب", + "searchProductHere": "ابحث عن المنتج هنا", + "hhcNotAuthMsg": "تقدم هذه الخدمة مجموعة من خدمات الرعاية الصحية المنزلية، والمتابعة المستمرة والشاملة في أماكن إقامتهم لأولئك الذين لا يمكنهم الوصول إلى المرافق الصحية، مثل (تحليلات المختبر - الأشعة - التطعيمات - العلاج الطبيعي)، إلخ.", + "email": "البريد الإلكتروني *", + "book": "احجز", + "appointmentLabel": "موعد", + "bloodType": "فصيلة الدم", + "maritalStatus": "الحالة الاجتماعية", + "general": "عام", + "profile": "ملفي", + "notifications": "إشعارات", + "notificationDetails": "تفاصيل الإشعار", + "notificationDetailsa": "تفاصيل الإشعار", + "infoMyDoctorPoints": "", + "infoMyDoctor": "تتيح لك هذه الخدمة رؤية جميع الأطباء الذين زرتهم في مجموعة الحبيب الطبية، ومن خلال هذه الخدمة:", + "infoPrescriptions": "تتيح لك هذه الخدمة عرض جميع الوصفات الطبية التي أصدرتها مجموعة الحبيب الطبية، ومن خلال هذه الخدمة، يمكنك:", + "infoMyPrescriptionPoints": "", + "infoInsuranceCards": "تتيح لك هذه الخدمة عرض جميع بطاقات التأمين التي تم تسجيلها خلال زياراتك لمجموعة الحبيب الطبية بالإضافة إلى:", + "infoInsuranceCardsPoints": "", + "infoAllergies": "تتيح لك هذه الخدمة عرض جميع أنواع الحساسية المسجلة خلال زياراتك لمجموعة الحبيب الطبية.", + "infoSickLeaves": "تتيح لك هذه الخدمة عرض جميع الاجازات المرضيه التي تم أخذها في مجموعة الحبيب الطبية بالإضافة إلى:", + "infoSickLeavePoints": "", + "infoApprovals": "تتيح لك هذه الخدمة عرض جميع طلبات الموافقة التي تم إرسالها إلى شركات التأمين بالإضافة إلى:", + "infoApprovalPoints": "", + "infoMonthReport": "عند تفعيل هذه الخدمة، سيقوم النظام بإرسال تقرير شهري تلقائيًا إلى البريد الإلكتروني المسجل يتضمن العلامات الحيوية والنتائج لآخر الزيارات التي تمت في مجموعة الحبيب الطبية.", + "languageSetting": "الرسائل القصيرة، واتساب، لغة مكالمات التأكيد", + "alert": "تنبيهات", + "emailAlert": "تنبيه عبر البريد الإلكتروني", + "smsAlert": "تنبيه عبر الرسائل القصيرة", + "contactInfo": "معلومات الاتصال", + "emrgName": "اسم جهة الاتصال في حالات الطوارئ", + "emrgNo": "رقم جهة الاتصال في حالات الطوارئ", + "modes": "الأوضاع", + "vibration": "اهتزاز عند اللمس", + "blindModes": "أوضاع للمكفوفين جزئيًا", + "invertTheme": "عكس", + "offTheme": "إيقاف", + "dimTheme": "خافت", + "bwTheme": "أسود وأبيض", + "permissions": "إذن", + "cameraPermission": "الكاميرا", + "locationPermission": "الموقع", + "needPrescription": "يتطلب هذا المنتج وصفة طبية", + "outOfStockMsg": "لقد أضفت منتجًا غير متوفر حاليًا، يرجى إزالته!", + "noArabicLetters": "لا يُسمح باستخدام الحروف العربية", + "noOffersAvailable": "لا توجد عروض متاحة!", + "accessibility": "وضع الوصول", + "orderStatus": "حالة الطلب", + "findUs": "اعثر علينا", + "liveChat": "دردشة مباشرة", + "service": "خدمة", + "hmgServiceLabel": "خدمة مجموعة الحبيب الطبية", + "healthWeatherIndicators": "مؤشرات الطقس الصحية", + "healthTipsBasedOnCurrentWeather": "نصائح صحية بناءً على الطقس الحالي", + "moreDetails": "المزيد من التفاصيل", + "resendOrder": "إعادة التعبئة والتوصيل", + "ports": "المنافذ", + "way": "الطريقة", + "dailyDoses": "الجرعات اليومية", + "period": "الفترة", + "duration": "المدة", + "cm": "سم", + "ft": "قدم", + "kg": "كجم", + "lb": "رطل", + "birthDate": "تاريخ الميلاد", + "dateOfBirth": "تاريخ الميلاد", + "mass": "الكتلة", + "tempC": "°م", + "bpm": "نبضة في الدقيقة", + "respirationSigns": "التنفس", + "sysDias": "SBP/DBP", + "body": "مؤشر كتلة الجسم", + "bodyString": "الجسم", + "face": "وجه", + "retouch": "روتوش", + "bikini": "بيكيني", + "totalMinutes": "إجمالي الدقائق", + "feedback": "رأيك يهمنا", + "likeToHear": "نود سماع ملاحظاتك، ومخاوفك بشأن خدمات الرعاية الصحية وتجربة الخدمات الإلكترونية. يرجى استخدام النموذج أدناه", + "emptySubject": "يرجى إدخال الموضوع", + "emptyMessage": "يرجى إدخال الرسالة", + "selectAttachment": "اختر المرفق", + "complainAppo": "شكوى بخصوص الموعد", + "complainAppoAlt": "شكوى بدون موعد", + "messageType": "نوع الرسالة", + "feedbackType": "نوع الرسالة", + "compliment": "تقدير", + "suggestion": "اقتراح", + "yourFeedback": "تم إرسال رأيك", + "selectPart": "يرجى اختيار الجزء الذي تشتكي منه", + "number": "رقم", + "notClassified": "غير مصنف", + "selectClinic": "اختر العيادة", + "selectProject": "اختر المستشفى", + "searchItemError": "يجب أن يكون اسم العنصر أكثر من 3 أحرف", + "youCanFind": "يمكنك العثور على ", + "itemInSearch": " عنصر في البحث", + "bloodDonation": "التبرع بالدم", + "bloodDonationInfo": "من خلال هذه الخدمة، يمكنك تسجيل اسمك كمتبرع بالدم حيث سيتواصل معك بنك الدم في مجموعة الحبيب الطبية في حالة الحاجة إلى فصيلة الدم.", + "bloodInstruction": "أدخل المعلومات المطلوبة، من أجل التسجيل في خدمة التبرع بالدم", + "aboutBloodDonation": "عن التبرع بالدم", + "bloodDonationDescription": "التبرع بالدم هو عمل نبيل ينقذ الأرواح. كل تبرع واحد يمكن أن ينقذ ما يصل إلى ثلاثة أشخاص. من خلال التبرع بالدم، فإنك تساعد المرضى الذين يحتاجون إلى عمليات نقل دم، وحالات الطوارئ، والعلاجات الطبية.", + "bloodDonationBenefit1": "عملية آمنة وسريعة (10-15 دقيقة)", + "bloodDonationBenefit2": "يمكنك التبرع كل 8-12 أسبوع", + "bloodDonationBenefit3": "فحص صحي مجاني لدمك", + "viewTerms": "لعرض الشروط والأحكام", + "wantConnectHmgNetwork": "عزيزي العميل، لا يوجد وصول إلى الإنترنت، هل ترغب في الاتصال بشبكة مجموعة الحبيب الطبية لاستخدام تطبيقنا، تأكد من أنك في نطاق شبكة مجموعة الحبيب الطبية", + "failedToAccessHmgServices": "متصل بشبكة مجموعة الحبيب الطبية،\n\nلكن فشل في الوصول إلى خدمات مجموعة الحبيب الطبية", + "offerAndPackages": "عروض وباقات", + "offerAndPackagesDetails": "تتيح لك هذه الخدمة عرض جميع عروض مجموعة الحبيب الطبية:", + "invoiceNo": "رقم الفاتورة", + "invoiceDate": "تاريخ الفاتورة", + "specialResult": "نتيجة خاصة", + "generalResult": "نتيجة عامة", + "showMoreBtn": "مخطط التدفق", + "value": "القيمة", + "report": "تقرير الأشعة", + "openRad": "فتح الصورة", + "sendCopy": "إرسال التقرير عبر البريد الإلكتروني", + "appoSurvey": "استطلاع", + "appoSurveySubtitle": "استطلاع", + "labResults": "نتائج المختبر", + "doctorRating": "تقييم الطبيب", + "good": "جيد", + "vGood": "جيد جدًا", + "excellent": "ممتاز", + "average": "متوسط", + "infoSigns": "تتيح لك هذه الخدمة عرض جميع العلامات الحيوية التي تم قياسها في مجموعة الحبيب الطبية، مثل (الطول، الوزن، مؤشر كتلة الجسم، معدل ضربات القلب، إلخ.) بالإضافة إلى عرض بعض الرسوم البيانية الإحصائية.", + "infoAdvancePayment": "تم تصميم هذه الخدمة بحيث يمكنك إيداع مبلغ مقدم إما في حسابك أو في حساب شخص آخر، في مجموعة الحبيب الطبية.", + "infoMyBalance": "تتيح لك هذه الخدمة التحقق من رصيدك في جميع الفروع", + "erContant": "تظهر هذه الخدمة أقرب فرع من بين جميع فروع مجموعة الحبيب الطبية بناءً على موقعك الحالي.", + "infoAmbulance": "من خلال هذه الخدمة، يمكنك طلب الإخلاء بواسطة سيارة إسعاف، سواء من المنزل أو إلى المنزل، بالإضافة إلى مجموعة من الخدمات الأخرى", + "infoMyAppointments": "تتيح لك هذه الخدمة رؤية جميع المواعيد التي زرتها في مجموعة الحبيب الطبية، ومن خلال هذه الخدمة:", + "infoTodo": "تم تصميم هذه الخدمة لتمكينك من الحصول على رابط سريع لقائمة المهام التي تحتاج القيام بها", + "familyInfo": "من خلال هذه الخدمة، ستتمكن من ربط ملفات عائلتك الطبية بملفك الطبي حتى تتمكن من إدارة سجلاتهم عن طريق تسجيل الدخول إلى ملفك الطبي.", + "rrtdDetails": "تقدم فريق الاستجابة السريعة خدمة طبية شاملة لجميع أنواع الحالات العاجلة والمستقرة من جميع الأعمار، بما في ذلك البالغين والمرضى الأطفال، وتغطي الخدمة فريق طبي مدرب تدريبًا عاليًا ومجهز تجهيزًا كاملاً قادرًا على تقديم أفضل رعاية طبية في منزل المريض.", + "homeHealthCareText": "تقدم هذه الخدمة مجموعة من خدمات الرعاية الصحية المنزلية، والمتابعة المستمرة والشاملة في أماكن إقامتهم لأولئك الذين لا يمكنهم الوصول إلى المرافق الصحية، مثل (تحليلات المختبر - الأشعة - التطعيمات - العلاج الطبيعي)، إلخ.", + "onlineCheckInAgreement": "تسجيل الوصول عبر الإنترنت مخصص للحالات التي لا تهدد الحياة. اتصل بالهلال الأحمر (997) أو اذهب إلى أقرب قسم طوارئ إذا كان هناك: \n\nعلامات السكتة الدماغية أو النوبة القلبية \nhالة من النوبات أو الإغماء \nهناك إصابة تهدد الطرف أو الحياة \nصورة لإصابات خطيرة", + "infoEreferral": "تتيح لك هذه الخدمة تقديم طلب إحالة من أي مقدم رعاية صحية سواء داخل أو خارج المملكة العربية السعودية إلى أي من مستشفيات HMG، عن طريق ملء بعض بيانات المريض وإرفاق التقارير الطبية، علاوة على ذلك يمكنك تتبع حالة الطلب (قيد المعالجة، مقبول أو مرفوض)", + "erConsultation": "تتيح لك هذه الخدمة إجراء استشارة افتراضية عبر الإنترنت عبر مكالمة فيديو مباشرة مع الطبيب من أي مكان وفي أي وقت.", + "myInvoice": "القائمة", + "invoiceList": "فواتيري", + "allInvoices": "كل الفواتير", + "hospitals": "المستشفيات", + "clinics": "العيادات", + "doctors": "الأطباء", + "selectDoctor": "اختر الطبيب", + "thisItemIsNotAvailable": "هذا العنصر غير متوفر", + "beforeAfterImages": "صور قبل وبعد", + "clinicAcceptLivecare": "لا حاجة للانتظار أو الزيارة يمكنك الآن الحصول على استشارة طبية عبر مكالمة فيديو (لايف كير) في اسم العيادة وسيتصل بك الطبيب على الفور", + "livecareModalTop": "تقبل هذه العيادة خدمات ال لايف كير", + "livecarePoint2": "سوف يتصل بك طبيب متخصص", + "livecarePoint3": "سيكون لدى الطبيب القدرة على رؤية تاريخ ملفك الطبي الكامل", + "livecarePoint4": "تتوفر خدمة توصيل الأدوية المجانية", + "livecarePoint5": "لا حاجة للزيارة، الخدمة في مكانك", + "bookImmediateLivecare": "أوافق على الحصول على الخدمة على الفور", + "bookVideoLivecare2": "لا أحتاج لزيارة العيادة", + "noThankyou": "لا شكرًا", + "visitClinic": "جدولة موعد مع الطبيب", + "generateCovidCertificate": "إصدار شهادة كوفيد-19", + "isReportOutsideKsa": "هل الشهادة مطلوبة خارج المملكة العربية السعودية؟", + "passportNumber": "رقم جواز السفر", + "enterPassportNumber": "يرجى تأكيد أو تحديث رقم جواز السفر الخاص بك:", + "validPassportNumber": "يرجى إدخال رقم جواز سفر صالح", + "continuePlan": "متابعة خطة العلاج؟", + "aboutApp": "حول التطبيق", + "dontHaveAccount": "ليس لديك حساب؟", + "loginOrRegister": "تسجيل الدخول أو التسجيل", + "myFiles": "ملفاتي", + "resultsPending": "النتائج معلقة", + "resultsAvailable": "النتائج متاحة", + "viewReport": "عرض التقرير", + "checkAvailability": "التحقق من التوفر", + "readInstructions": "قراءة التعليمات", + "searchLabReport": "ابحث عن تقرير المختبر", + "prescriptionDeliveryError": "هذه العيادة لا تدعم إعادة التعبئة والتسليم.", + "receiveOtpToast": "أين تود تلقي رمز التحقق OTP؟", + "enterPhoneNumber": "أدخل رقم الهاتف", + "enterEmailDesc": "أدخل عنوان بريدك الإلكتروني لإكمال عملية إنشاء ملف طبي", + "enterPhoneDesc": "أدخل رقم هاتفك لتلقي رمز التحقق ", + "pleaseChooseOption": "الرجاء اختيار من الخيارات أدناه لتلقي رمز التحقق OTP", + "prepareToElevate": "هل أنت مستعد لتحسين صحتك ورفاهيتك؟", + "iAcceptTermsConditions": "أوافق على الشروط والأحكام", + "alreadyHaveAccount": "هل لديك حساب بالفعل؟", + "loginNow": "تسجيل الدخول الآن", + "notice": "إشعار", + "oR": "أو", + "sendOTPWHATSAPP": "أرسل لي OTP عبر واتساب", + "sendOTPSMS": "أرسل لي OTP عبر الرسائل القصيرة", + "fullName": "الاسم الكامل", + "married": "متزوج", + "uae": "الإمارات العربية المتحدة", + "malE": "ذكر", + "loginBy": "تسجيل الدخول بواسطة", + "loginByOTP": "تسجيل الدخول بواسطة OTP", + "guest": "زائر", + "switchAccount": "تبديل الحساب", + "lastLoginBy": "آخر تسجيل دخول بواسطة", + "allSet": "جاهز! الآن يمكنك تسجيل الدخول باستخدام Face ID أو البصمة", + "enableQuickLogin": "تمكين تسجيل الدخول السريع", + "enableMsg": "تمكين تسجيل الدخول السريع سيسمح بالتحقق من خلال Face ID / Biometric الخاص بجهازك الحالي", + "notNow": "ليس الآن", + "pendingActivation": "في انتظار التنشيط", + "awaitingApproval": "انتظر القبول", + "news": "أخبار", + "ready": "جاهز", + "enterValidNationalId": "رقم الهوية أو رقم الملف غير صحيح", + "enterValidPhoneNumber": "الرجاء إدخال رقم هاتف صالح", + "cannotEnterSaudiOrUAENumber": "لا يمكنك إدخال أرقام هواتف السعودية (00966) أو الإمارات (00971) عند اختيار دولة 'أخرى'", + "medicalCentersWithCount": "{count} مراكز طبية", + "medicalCenters": "مراكز طبية", + "hospitalsWithCount": "{count} مستشفيات", + "selectRegion": "اختر المنطقة", + "selectFacility": "اختر المرافق", + "selectFacilitiesSubTitle": "يرجى اختيار المرفق للموعد", + "selectHospitalSubTitle": "يرجى اختيار المستشفى للموعد", + "iAcceptThe": "أوافق على", + "personalDetailsVerification": "التحقق من التفاصيل الشخصية", + "otpVerification": "التحقق من OTP", + "weHaveSendOTP": "لقد أرسلنا OTP إلى", + "via": "عبر", + "forRegistrationVerification": "للتحقق من التسجيل", + "didntReceiveIt": "لم تستلمه؟", + "resendOTP": "إعادة إرسال", + "resendIn": "إعادة الإرسال في", + "pleaseEnterAnationalID": "يرجى إدخال رقم الهوية الوطنية", + "pleaseEnterAFileNumber": "يرجى إدخال رقم الملف", + "pleaseEnterAValidEmail": "يرجى إدخال بريد إلكتروني صالح", + "pleaseEnterFullName": "يرجى إدخال الاسم الكامل", + "pleaseAcceptTermsConditions": "يرجى قبول الشروط والأحكام", + "pleaseEnterAValidIqamaID": "يرجى إدخال رقم إقامة صالح", + "pleaseEnterAValidNationalID": "يرجى إدخال رقم هوية وطنية صالح", + "pleaseEnterAValidDateOfBirth": "يرجى إدخال تاريخ ميلاد صالح", + "pleaseEnterAValidName": "يرجى إدخال اسم صالح", + "pleaseSelectAGender": "يرجى اختيار الجنس", + "pleaseSelectAMaritalStatus": "يرجى اختيار الحالة الاجتماعية", + "pleaseSelectACountry": "يرجى اختيار الدولة", + "pleaseEnterEmail": "يرجى إدخال البريد الإلكتروني", + "pleaseEnterAValidEmailFormat": "يرجى إدخال تنسيق بريد إلكتروني صالح", + "selectCountry": "اختر الدولة", + "forLoginVerification": "للتحقق من تسجيل الدخول", + "searchHospital": "بحث في المستشفى", + "skip": "تخطي", + "getStarted": "ابدأ الآن", + "onboardingHeading1": "حجز المواعيد لم يكن أسهل من الآن.", + "onboardingBody1": "ببضع نقرات فقط يمكنك استشارة الطبيب الذي تختاره.", + "onboardingHeading2": "الوصول إلى السجل الطبي بين يديك", + "onboardingBody2": "تتبع تاريخك الطبي بما في ذلك الفحوصات المخبرية، الوصفات الطبية، التأمين، وغيرها.", + "hmgHospitals": "مستشفيات الحبيب", + "hmcMedicalClinic": "مراكز مجموعة الحبيب الطبية", + "applyFilter": "تطبيق الفلتر", + "facilityAndLocation": "المرفق والموقع", + "regionAndLocation": "المنطقة والمواقع", + "clearAllFilters": "مسح جميع الفلاتر", + "filters": "فلاتر", + "searchClinic": "بحث عن عيادة", + "normal": "عادي", + "attention": "انتباه", + "monitor": "مراقبة", + "noSpecialResult": "لا توجد نتائج خاصة", + "setTheDateRange": "تعيين النطاق الزمني", + "historyFlowchart": "مخطط تدفق التاريخ", + "to": "إلى", + "startDate": "تاريخ البدء", + "endDate": "تاريخ الانتهاء", + "walkin": "زيارة بدون موعد", + "laserClinic": "عيادة الليزر", + "continueString": "يكمل", + "covid_info": "تجري مستشفيات د. سليمان الحبيب فحص فيروس كورونا المستجد وتصدر شهادات السفر على مدار الساعة، طوال أيام الأسبوع، وبسرعة ودقة عالية. يمكن للراغبين في الاستفادة من هذه الخدمة زيارة أحد فروع مستشفيات د. سليمان الحبيب وإجراء فحص كورونا خلال بضع دقائق والحصول على النتائج خلال عدة ساعات خدمة فحص فيروس كورونا Covid 19 بتقنية PCR للكشف عن الفيروس وفقاً لأعلى المعايير العالمية وبأحدث أجهزة RT-PCR عالية الدقة (GeneXpert الأمريكي وغيره)، وهي طرق معتمدة من قبل هيئة الغذاء والدواء وكذلك من قبل المركز السعودي للوقاية من الأمراض المُعدية", + "appointmentDetails": "تفاصيل الموعد", + "checkingDoctorAvailability": "جاري التحقق من توفر الطبيب...", + "cancellingAppointmentPleaseWait": "جاري إلغاء الموعد، يرجى الانتظار...", + "appointmentCancelledSuccessfully": "تم إلغاء الموعد بنجاح", + "notConfirmed": "غير مؤكد", + "appointmentStatus": "حالة الموعد", + "doctorWillCallYou": "سيتصل بك الطبيب عندما يقترب موعدك.", + "getDirections": "الحصول على الاتجاهات", + "notifyMeBeforeAppointment": "أبلغني قبل الموعد", + "fetchingLabResults": "جاري جلب نتائج المختبر...", + "fetchingRadiologyResults": "جاري جلب نتائج الأشعة...", + "fetchingAppointmentPrescriptions": "جاري جلب وصفات الموعد...", + "noPrescriptionsForAppointment": "ليس لديك أي وصفات طبية لهذا الموعد.", + "amountBeforeTax": "المبلغ قبل الضريبة", + "rebookAppointment": "إعادة حجز الموعد", + "fetchingDoctorSchedulePleaseWait": "جاري جلب جدول الطبيب، يرجى الانتظار...", + "pickADate": "اختر تاريخاً", + "confirmingAppointmentPleaseWait": "جاري تأكيد الموعد، يرجى الانتظار...", + "appointmentConfirmedSuccessfully": "تم تأكيد الموعد بنجاح", + "appointmentPayment": "دفع الموعد", + "checkingPaymentStatusPleaseWait": "جاري التحقق من حالة الدفع، يرجى الانتظار...", + "paymentFailedPleaseTryAgain": "فشل الدفع! يرجى المحاولة مرة أخرى.", + "appointmentCheckIn": "تسجيل حضور الموعد", + "insuranceExpiredOrInactive": "التأمين منتهي الصلاحية أو غير نشط", + "totalAmountToPay": "المبلغ الإجمالي المستحق", + "vat15": "ضريبة القيمة المضافة 15%", + "liveCare": "لايف كير", + "recentVisits": "الزيارات الأخيرة", + "favouriteDoctors": "الأطباء المفضلون", + "searchByClinic": "البحث حسب العيادة", + "tapToSelectClinic": "انقر لاختيار العيادة", + "searchByDoctor": "البحث حسب الطبيب", + "tapToSelect": "انقر للاختيار", + "searchByRegion": "البحث حسب المنطقة", + "centralRegion": "المنطقة الوسطى", + "immediateConsultation": "استشارة فورية", + "scheduledConsultation": "استشارة مجدولة", + "pharmaLiveCare": "لايف كير الصيدلية", + "notSureHelpMeChooseClinic": "غير متأكد؟ ساعدني في اختيار عيادة!", + "mentionYourSymptomsAndFindDoctors": "اذكر أعراضك واعثر على قائمة الأطباء وفقاً لذلك", + "immediateService": "خدمة فورية", + "noNeedToWaitGetMedicalConsultation": "لا حاجة للانتظار، ستحصل على استشارة طبية فورية عبر مكالمة فيديو", + "noVisitRequired": "لا حاجة للزيارة", + "doctorWillContact": "سيتصل بك الطبيب", + "specialisedDoctorWillContactYou": "سيتصل بك طبيب متخصص وسيكون قادراً على الاطلاع على تاريخك الطبي", + "freeMedicineDelivery": "توصيل مجاني للأدوية", + "offersFreeMedicineDelivery": "يوفر توصيل مجاني للأدوية لموعد لايف كير", + "dentalChiefComplaints": "الشكاوى الرئيسة للأسنان", + "viewAvailableAppointments": "عرض المواعيد المتاحة", + "doctorProfile": " ملف الطبيب", + "waitingAppointment": "موعد الانتظار", + "hospitalInformation": "معلومات المستشفى", + "fetchingAppointmentShare": "جاري جلب تفاصيل الموعد...", + "bookingYourAppointment": "جاري حجز موعدك...", + "selectLiveCareClinic": "عيادة لايف كير", + "checkingForExistingDentalPlan": "جاري التحقق من وجود خطة أسنان حالية، يرجى الانتظار...", + "dentalTreatmentPlan": "خطة علاج الأسنان", + "youHaveExistingTreatmentPlan": "لديك خطة علاج حالية: ", + "mins": "دقيقة", + "totalTimeRequired": "إجمالي الوقت المطلوب", + "wouldYouLikeToContinue": "هل تريد متابعتها؟", + "chooseDoctor": "اختر الطبيب", + "viewNearestAppos": "عرض أقرب المواعيد المتاحة", + "noDoctorFound": "لم يتم العثور على طبيب مطابق للمعايير المحددة...", + "yesPleasImInAHurry": "نعم من فضلك، أنا في عجلة من أمري", + "fetchingFeesPleaseWait": "جاري جلب الرسوم، يرجى الانتظار...", + "noThanksPhysicalVisit": "لا، شكراً. أفضل الزيارة الشخصية", + "offline": "غير متصل", + "videoCall": "مكالمة فيديو", + "liveVideoCallWithHMGDoctors": "مكالمة فيديو مباشرة مع أطباء مجموعة الحبيب الطبية", + "liveAudioCallWithHMGDoctors": "مكالمة صوتية مباشرة مع أطباء مجموعة الحبيب الطبية", + "audioCall": "مكالمة صوتية", + "phoneCall": "مكالمة هاتفية", + "livePhoneCallWithHMGDoctors": "مكالمة هاتفية مباشرة مع أطباء مجموعة الحبيب الطبية", + "reviewLiveCareRequest": "مراجعة طلب لايف كير", + "selectedLiveCareType": "نوع لايف كير المحدد", + "selectLiveCareCallType": "اختر نوع مكالمة لايف كير", + "confirmingLiveCareRequest": "جاري تأكيد طلب لايف كير، يرجى الانتظار...", + "unknownErrorOccurred": "حدث خطأ غير معروف...", + "liveCarePermissionsMessage": "يتطلب لايف كير أذونات الكاميرا والميكروفون والموقع والإشعارات لتمكين الاستشارة الافتراضية بين المريض والطبيب، يرجى السماح بهذه الأذونات للمتابعة.", + "liveCarePayment": "دفع لايف كير", + "mada": "مدى", + "visaOrMastercard": "فيزا أو ماستركارد", + "tamara": "تمارا", + "fetchingApplePayDetails": "جاري جلب تفاصيل Apple Pay، يرجى الانتظار...", + "liveCarePendingRequest": "طلب لايف كير قيد الانتظار", + "callLiveCareSupport": "اتصل بدعم لايف كير", + "whatIsWaitingAppointment": "ما هو موعد الانتظار؟", + "waitingAppointmentsFeature": "تتيح لك ميزة مواعيد الانتظار حجز موعد أثناء تواجدك داخل مبنى المستشفى، وفي حال عدم توفر مواعيد متاحة في جدول الطبيب.", + "appointmentWithDoctorConfirmed": "الموعد مع الطبيب مؤكد، ولكن وقت الدخول غير محدد.", + "paymentWithinTenMinutes": "ملاحظة: يجب عليك الدفع خلال 10 دقائق من الحجز، وإلا سيتم إلغاء موعدك تلقائياً", + "liveLocation": "الموقع المباشر", + "verifyYourLocationAtHospital": "تحقق من موقعك في المستشفى لتسجيل الحضور", + "error": "خطأ", + "ensureWithinHospitalLocation": "يرجى التأكد من أنك داخل موقع المستشفى لإجراء تسجيل الحضور عبر الإنترنت.", + "nfcNearFieldCommunication": "NFC (الاتصال قريب المدى)", + "scanPhoneViaNFC": "امسح هاتفك عبر لوحة NFC لتسجيل الحضور", + "qrCode": "رمز QR", + "scanQRCodeToCheckIn": "امسح رمز QR بالكاميرا لتسجيل الحضور", + "processingCheckIn": "جاري معالجة تسجيل الحضور...", + "bookingWaitingAppointment": "جاري حجز موعد الانتظار، يرجى الانتظار...", + "enterValidIDorIqama": "يرجى إدخال رقم هوية وطنية أو رقم ملف صالح", + "selectAppointment": "حدد الموعد", + "rebookSameDoctor": "أعد الحجز مع نفس الطبيب", + "queueing": "قائمة الانتظار", + "inQueue": "في قائمة الانتظار", + "yourTurn": "دورك", + "halaFirstName": "هلا {firstName}!!!", + "thankYouForPatience": "شكراً لصبرك، هذا هو رقم قائمة الانتظار الخاص بك.", + "servingNow": "يُخدم الآن", + "callForVitalSigns": "نداء للعلامات الحيوية", + "callForDoctor": "نداء للطبيب", + "thingsToAskDoctor": "أشياء تسأل طبيبك عنها اليوم", + "improveOverallHealth": "ماذا يمكنني أن أفعل لتحسين صحتي العامة؟", + "routineScreenings": "هل هناك أي فحوصات روتينية يجب أن أجريها؟", + "whatIsThisMedicationFor": "لماذا هذا الدواء؟", + "sideEffectsToKnow": "هل هناك أي آثار جانبية يجب أن أعرفها؟", + "whenFollowUp": "متى يجب أن أعود للمتابعة؟", + "goToHomepage": "الذهاب إلى الصفحة الرئيسية", + "appointmentsList": "قائمة المواعيد", + "allAppt": "جميع المواعيد", + "upcoming": "القادمة", + "completed": "المكتملة", + "noAppointmentsYet": "ليس لديك أي مواعيد بعد.", + "viewProfile": "عرض الملف الشخصي", + "choosePreferredHospitalForService": "اختر المستشفى المفضل لديك للخدمة", + "noHospitalsFound": "لم يتم العثور على مستشفيات", + "cancelOrderConfirmation": "هل أنت متأكد أنك تريد إلغاء هذا الطلب؟", + "orderCancelledSuccessfully": "تم إلغاء الطلب بنجاح", + "requestID": "معرف الطلب:", + "noCMCOrdersYet": "ليس لديك أي طلبات فحص شامل بعد.", + "cmcOrders": "طلبات الفحص الشامل", + "summary": "الملخص", + "selectedService": "الخدمة المحددة", + "requestSubmittedSuccessfully": "تم إرسال طلبك بنجاح.", + "hereIsYourRequestNumber": "هذا هو رقم طلبك #: ", + "pleaseSelectHospitalToContinue": "يرجى اختيار مستشفى للمتابعة", + "confirmSubmitRequest": "هل أنت متأكد أنك تريد إرسال هذا الطلب؟", + "pendingOrderWait": "لديك طلب معلق. يرجى الانتظار حتى تتم معالجته.", + "noServicesAvailable": "لا توجد خدمات متاحة", + "selectAService": "اختر خدمة", + "comprehensiveCheckup": "الفحص الشامل", + "viewNearestHMGLocations": "عرض أقرب مواقع مجموعة الحبيب الطبية", + "provideFeedbackOnServices": "قدم ملاحظاتك على خدماتنا", + "liveChatWithHMG": "خيار الدردشة المباشرة مع مجموعة الحبيب الطبية", + "send": "إرسال", + "status": "الحالة", + "sendingFeedback": "جاري إرسال الملاحظات...", + "selectFeedbackType": "اختر نوع الملاحظات", + "loadingAppointmentsList": "جاري تحميل قائمة المواعيد...", + "noAppointmentsForFeedback": "ليس لديك أي مواعيد لتقديم ملاحظات عنها.", + "selectedAppointment": "الموعد المحدد:", + "subject": "الموضوع", + "enterSubjectHere": "أدخل الموضوع هنا", + "message": "الرسالة", + "enterMessageHere": "أدخل الرسالة هنا", + "filesSelected": "تم تحديد {count} ملف(ات)", + "otherDetails": "تفاصيل أخرى", + "medicalReport": "التقرير الطبي", + "medicalReportNumber": "التقرير الطبي {number}", + "patientIsInsured": "المريض مؤمن عليه", + "insuranceDocument": "وثيقة التأمين", + "selectBranch": "اختر الفرع", + "patientInformation": "معلومات المريض", + "patientLocation": "أين يتواجد المريض", + "identificationNumber": "رقم الهوية", + "enterIdentificationNumber": "أدخل رقم الهوية*", + "patientName": "اسم المريض*", + "referralRequesterInformation": "معلومات مقدم طلب الإحالة", + "enterReferralRequesterName": "أدخل اسم مقدم طلب الإحالة*", + "requesterName": "اسم مقدم الطلب", + "relationship": "العلاقة", + "selectRelation": "اختر العلاقة", + "otherName": "اسم آخر", + "otherNameHint": "اسم آخر*", + "requesterNameRequired": "اسم مقدم طلب الإحالة مطلوب", + "selectRelationshipRequired": "يرجى اختيار العلاقة", + "otherRelationshipNameRequired": "اسم العلاقة الأخرى مطلوب", + "identificationNumberRequired": "رقم الهوية مطلوب", + "patientNameRequired": "اسم المريض مطلوب", + "enterPatientPhoneRequired": "يرجى إدخال رقم هاتف المريض", + "selectPatientCityRequired": "يرجى اختيار مدينة المريض", + "medicalReportRequired": "مطلوب تقرير طبي واحد على الأقل", + "selectBranchRequired": "يرجى اختيار الفرع", + "insuranceDocumentRequired": "وثيقة التأمين مطلوبة للمرضى المؤمن عليهم", + "searchResult": "نتيجة البحث", + "referralNo": "رقم الإحالة {number}", + "eReferral": "الإحالة الإلكترونية", + "referralCreatedSuccessfully": "تم إنشاء إحالتك بنجاح.", + "hereIsYourReferralNumber": "هذا هو رقم الإحالة الخاص بك #: ", + "searchEReferral": "البحث عن إحالة إلكترونية", + "enterRequiredInfoToSearch": "يرجى إدخال المعلومات المطلوبة للبحث عن إحالة إلكترونية", + "selectPickupDirection": "اختر اتجاه الاستلام", + "selectDirection": "اختر الاتجاه", + "toHospital": "إلى المستشفى", + "fromHospital": "من المستشفى", + "selectWay": "اختر الطريقة", + "oneWay": "اتجاه واحد", + "twoWay": "اتجاهين", + "selectPickupDetails": "اختر تفاصيل الاستلام", + "pleaseSelectDetailsOfPickup": " يرجى تحديد تفاصيل الاستلام", + "selectDetails": "اختر التفاصيل", + "work": "العمل", + "pick": "استلام", + "insideTheHome": "داخل المنزل", + "haveAnyAppointment": "هل لديك أي موعد", + "amountPaidAtHospital": "سيتم دفع المبلغ في المستشفى", + "submitRequest": "إرسال الطلب", + "enterPickupLocationManually": "أدخل موقع الاستلام يدوياً", + "enterPickupLocation": "أدخل موقع الاستلام", + "trackingDetails": "تفاصيل التتبع", + "cancelRequest": "إلغاء الطلب", + "shareLocationWhatsapp": "مشاركة موقعك المباشر على واتساب", + "pleaseWaitForCall": "يرجى انتظار المكالمة", + "toHospitalLower": "إلى المستشفى", + "contact": "اتصال", + "failed": "فشل", + "confirmationCall": "مكالمة التأكيد", + "pickupFromHome": "الاستلام من المنزل", + "onTheWayToHospital": " في الطريق إلى المستشفى", + "arrivedAtHospital": "وصل إلى المستشفى", + "orderCancel": "إلغاء الطلب", + "emergencyCheckIn": "تسجيل الطوارئ الإلكتروني", + "erOnlineCheckInDescription": "تتيح هذه الخدمة للمرضى تسجيل موعد الطوارئ قبل الوصول.", + "erOnlineCheckInSuccess": "تم تسجيل وصول الطوارئ بنجاح. الرجاء التوجه إلى منطقة الانتظار.", + "erOnlineCheckInError": "حدث خطأ غير متوقع أثناء عملية التسجيل. يرجى التواصل مع الدعم.", + "fetchingHospitalsList": "جاري جلب قائمة المستشفيات...", + "fetchingPaymentInformation": "جاري جلب معلومات الدفع...", + "erVisitDetails": "تفاصيل زيارة الطوارئ", + "erClinic": "عيادة الطوارئ", + "vatWithAmount": "الضريبة 15% ({amount})", + "erAppointmentBookedSuccess": "تم حجز موعدك بنجاح. يرجى إتمام إجراءات تسجيل الوصول عند وصولك إلى المستشفى.", + "underProcessing": "قيد المعالجة", + "canceledByPatient": "ملغى بواسطة المريض", + "rapidResponseTeam": "فريق الاستجابة السريع", + "allFacilities": "جميع المرافق", + "selectLocation": "اختر الموقع", + "pleaseSelectTheLocation": "يرجى اختيار الموقع", + "viewLocationGoogleMaps": "عرض الموقع على خرائط جوجل", + "callAmbulance": "استدعاء سيارة إسعاف", + "requestAmbulanceInEmergency": "طلب سيارة إسعاف في حالة الطوارئ من المنزل أو المستشفى", + "confirmation": "تأكيد", + "areYouSureYouWantToCallAmbulance": "هل أنت متأكد أنك تريد استدعاء سيارة إسعاف؟", + "getDetailsOfNearestBranch": "احصل على تفاصيل أقرب فرع بما في ذلك الاتجاهات", + "areYouSureYouWantToCallRRT": "هل أنت متأكد أنك تريد استدعاء فريق الاستجابة السريعة (RRT)؟", + "priorERCheckInToSkipLine": "تسجيل الوصول المسبق في الطوارئ لتجاوز الطابور والدفع في الاستقبال.", + "areYouSureYouWantToMakeERCheckIn": "هل أنت متأكد أنك تريد إجراء تسجيل وصول الطوارئ؟", + "checkingYourERAppointmentStatus": "جاري التحقق من حالة موعد الطوارئ الخاص بك...", + "transportOptions": "خيارات النقل", + "selectHospitalForAdvancePayment": "يرجى اختيار المستشفى الذي ترغب في دفع مبلغ مقدم له.", + "recharge": "إعادة الشحن", + "activityLevel": "مستوى النشاط", + "selectActivityLevel": "حدد مستوى النشاط", + "caloriesPerDay": "السعرات الحرارية في اليوم الواحد", + "dietType": "نوع النظام الغذائي", + "selectDietType": "حدد نوع النظام الغذائي", + "bodyFrameSize": "حجم إطار الجسم", + "selectBodyFrameSize": "حدد حجم إطار الجسم", + "averageCycleLength": "متوسط طول الدورة الشهرية (عادةً 28 يومًا)", + "averageLutealPhase": "متوسط طول المرحلة الأصفرية (عادةً 14 يومًا)", + "convert": "يتحول", + "calculate": "احسب", + "healthCalculators": "حاسبات الصحة", + "healthConverters": "محولات الصحة", + "generalHealth": "الصحة العامة", + "relatedToBMICalories": "متعلق بمؤشر كتلة الجسم والسعرات الحرارية ودهون الجسم وما إلى ذلك للبقاء على اطلاع دائم بصحتك.", + "selectCalculator": "اختر الآلة الحاسبة", + "womensHealth": "صحة المرأة", + "relatedToPeriodsOvulation": "متعلق بالدورة الشهرية والإباضة والحمل ومواضيع أخرى.", + "bloodSugar": "سكر الدم", + "trackYourGlucoseLevels": "تتبع مستويات الجلوكوز لديك، وفهم الاتجاهات، واحصل على رؤى مخصصة لصحة أفضل.", + "bloodCholesterol": "كوليسترول الدم", + "monitorCholesterolLevels": "راقب مستويات الكوليسترول، وقيّم مخاطر صحة القلب، واتخذ خطوات استباقية للرفاهية.", + "triglyceridesFatBlood": "الدهون الثلاثية في الدم", + "understandTriglyceridesImpact": "افهم تأثير الدهون الثلاثية على صحة القلب مع رؤى مخصصة وتوصيات الخبراء.", + "bmiCalculator": "حاسبة\nمؤشر كتلة الجسم", + "caloriesCalculator": "حاسبة\nالسعرات الحرارية", + "bmrCalculator": "حاسبة\nمعدل الأيض الأساسي", + "idealBodyWeight": "الوزن المثالي\nللجسم", + "bodyFatCalculator": "حاسبة\nدهون الجسم", + "carbsProteinFat": "الكربوهيدرات\nالبروتين والدهون", + "ovulationPeriod": "فترة\nالإباضة", + "deliveryDueDate": "تاريخ الولادة\nالمتوقع", + "low": "منخفض", + "preDiabetic": "ما قبل السكري", + "high": "مرتفع", + "elevated": "مرتفع قليلاً", + "recorded": "مسجل", + "noRecordsYet": "لا توجد سجلات بعد", + "lastRecord": "آخر سجل", + "addBloodSugar": "إضافة سكر الدم", + "addBloodPressure": "إضافة ضغط الدم", + "addWeight": "إضافة الوزن", + "bloodSugarDataSavedSuccessfully": "تم حفظ بيانات سكر الدم بنجاح", + "bloodPressureDataSavedSuccessfully": "تم حفظ بيانات ضغط الدم بنجاح", + "weightDataSavedSuccessfully": "تم حفظ بيانات الوزن بنجاح", + "pleaseWait": "يرجى الانتظار", + "selectUnit": "اختر الوحدة", + "selectMeasureTime": "اختر وقت القياس", + "selectArm": "اختر الذراع", + "enterBloodSugar": "أدخل سكر الدم", + "enterSystolicValue": "أدخل القيمة الانقباضية", + "enterDiastolicValue": "أدخل القيمة الانبساطية", + "enterWeight": "أدخل الوزن", + "selectDuration": "اختر المدة", + "systolic": "الانقباضي", + "diastolic": "الانبساطي", + "sendReportByEmail": "إرسال التقرير عبر البريد الإلكتروني", + "enterYourEmailToReceiveReport": "أدخل عنوان بريدك الإلكتروني لاستلام التقرير", + "addNewRecord": "إضافة سجل جديد", + "healthTrackers": "متتبعات الصحة", + "monitorBloodPressureLevels": "راقب مستويات ضغط الدم لديك، وتتبع القراءات الانقباضية والانبساطية، وحافظ على صحة قلبك.", + "trackWeightProgress": "تتبع تقدم وزنك، وضع الأهداف، وحافظ على كتلة جسم صحية من أجل العافية الشاملة.", + "bookAppointment": "احجز موعد", + "completeCheckup": "الفحص الشامل", + "indoorNavigation": "الملاحة الداخلية", + "eReferralServices": "خدمات الإحالة الإلكترونية", + "dailyWaterMonitor": "مراقب الماء اليومي", + "fetchingYourWaterIntakeDetails": "جاري جلب تفاصيل استهلاك الماء الخاص بك.", + "healthCalculatorsServices": "حاسبات\nالصحة", + "healthConvertersServices": "محولات\nالصحة", + "smartWatchesServices": "الساعات\nالذكية", + "exploreServices": "استكشف الخدمات", + "medicalAndCareServices": "الخدمات الطبية والرعاية", + "hmgServices": "خدمات مجموعة الحبيب الطبية", + "personalServices": "الخدمات الشخصية", + "habibWallet": "محفظة الحبيب", + "loginToViewWalletBalance": "سجل الدخول لعرض رصيد محفظتك", + "loginToViewMedicalFile": "سجل الدخول لعرض ملفك الطبي", + "addMember": "إضافة عضو", + "addFamilyMember": "إضافة فرد من العائلة", + "pleaseFillBelowFieldToAddNewFamilyMember": "يرجى ملء الحقل أدناه لإضافة فرد جديد من العائلة إلى ملفك الشخصي", + "healthTools": "أدوات الصحة", + "supportServices": "خدمات الدعم", + "virtualTour": "جولة افتراضية", + "carParking": "موقف السيارات", + "latestNews": "آخر الأخبار", + "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": "طلب لايف كير فوري", + "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": "نتيجة {date}", + "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": "الأعراض الشائعة", + "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": "تفاصيل العلامات الحيوية", + "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": "معدل الأيض الأساسي لديك أعلى من المتوسط ​​بالنسبة لحالتك. قد يعكس هذا زيادة في كتلة العضلات، أو صغر السن، أو ارتفاع معدل الأيض لديك بشكل طبيعي، مما يعني أنك تحرق طاقة أكبر أثناء الراحة. استخدم هذه المعلومات لتحديد احتياجاتك من السعرات الحرارية: احتياجات أعلى للحفاظ على الوزن أو بناء العضلات، ونقص معتدل في السعرات الحرارية لإنقاص الوزن. استشر طبيبك إذا لاحظت أي تغيرات سريعة غير مبررة.", + "ibwAtTargetDescription": "وزنك في النطاق المثالي. حافظ على عاداتك الحالية — نظام غذائي متوازن ونشاط بدني منتظم — للاستمرار على هذا المستوى.", + "ibwBelowTargetDescription": "أنتِ أقل من الوزن المثالي بمقدار {kg} كجم. يُنصح بزيادة الوزن تدريجيًا من خلال فائضٍ بسيط في السعرات الحرارية، وتناول أطعمة غنية بالعناصر الغذائية، مع ممارسة تمارين المقاومة.", + "ibwAboveTargetDescription": "أنتِ أعلى من الوزن المثالي بمقدار {kg} كجم. يُنصح باتباع خفضٍ تدريجي ومستدام للوزن من خلال عجزٍ معتدل في السعرات الحرارية، وزيادة مستوى النشاط البدني، واتباع تغذية متوازنة.", + "ibwCloseToRangeDescription": "وزنك قريب من النطاق المثالي. حافظ على نظام غذائي متوازن ونشاط بدني منتظم، مع اتباع إرشادات شخصية من مختص صحي عند الحاجة.", + "ibwUnderweightDescription": "أنت أقل من الوزن المثالي بمقدار {kg} كجم. ركّز على زيادة السعرات الحرارية من خلال أطعمة غنية بالعناصر الغذائية، وأعط الأولوية للبروتين وتمارين المقاومة لبناء كتلة صحية، مع اتباع إرشادات شخصية من مختص صحي.", + "ibwUnderweightGenericDescription": "يبدو أنكِ أقل من الوزن المثالي. يُنصح بزيادة السعرات الحرارية من خلال أطعمة غنية بالعناصر الغذائية وممارسة تمارين المقاومة؛ مع الحصول على استشارة مختص صحي عند الحاجة.", + "ibwOverweightDescription": "أنت أعلى من الوزن المثالي بمقدار {kg} كجم. يُنصح باتباع استراتيجية تدريجية ومستدامة لفقدان الوزن — من خلال عجزٍ معتدل في السعرات الحرارية، وممارسة نشاط بدني منتظم، وتغذية متوازنة. يُفضّل استشارة مختص صحي قبل إجراء تغييرات كبيرة.", + "ibwOverweightGenericDescription": "يبدو أنك أعلى من الوزن المثالي. يُنصح باتباع عجزٍ تدريجي ومستدام في السعرات الحرارية مع ممارسة النشاط البدني واتباع تغذية متوازنة؛ مع الحصول على استشارة مختص صحي عند الحاجة.", + "dietVeryLowCarbsDescription": "نهج منخفض الكربوهيدرات جدًا (الكيتوني): تقليل الكربوهيدرات إلى الحد الأدنى مع زيادة الدهون وبروتين معتدل. قد يساعد على فقدان الوزن بسرعة وتحسين التحكم في سكر الدم لدى بعض الأشخاص، لكنه قد يكون صعب الاستمرار عليه وقد يسبب نقصًا في بعض العناصر الغذائية. يجب مراقبة الترطيب ، والحصول على استشارة مختص صحي عند استخدامه لفترة طويلة.", + "dietLowCarbsDescription": "خطة منخفضة الكربوهيدرات وعالية البروتين: تقليل الكربوهيدرات مع زيادة البروتين لدعم الشعور بالشبع والحفاظ على الكتلة العضلية. مفيدة لإدارة الوزن وضبط سكر الدم عند موازنتها مع الخضروات والدهون الصحية. احرص على الحصول على كمية كافية من الألياف والعناصر الدقيقة (الفيتامينات والمعادن).", + "dietModerateCarbsDescription": "تناول معتدل للكربوهيدرات مع توازن في البروتين والدهون: يوفر طاقة مستقرة للأنشطة اليومية والتمارين مع قدر من المرونة. مناسب لكثير من الأشخاص الذين يسعون لإدارة وزن مستدامة وتحسين الأداء.", + "dietUSDAGuidelinesDescription": "نمط متوازن مستند إلى (USDA): يركز على الفواكه والخضروات والحبوب الكاملة والبروتينات قليلة الدهن، مع الحد من السكريات المضافة والدهون المشبعة. يُعد إطارًا قائمًا على الأدلة لدعم الصحة العامة، وكفاية العناصر الغذائية، والوقاية من الأمراض المزمنة.", + "dietZoneDietDescription": "حمية الزون (~40٪ كربوهيدرات / 30٪ بروتين / 30٪ دهون): تركز على التوازن الهرموني وضبط الحصص الغذائية. قد تُحسّن تركيب الجسم ومستوى الطاقة لدى بعض الأشخاص، لكنها تتطلب تخطيطًا جيدًا؛ ويُفضّل تخصيصها بالتعاون مع مختص تغذية.", + "contextDependent": "يعتمد السياق", + "calculatedValue": "القيمة المحسوبة", + "calculationFormula": "صيغة الحساب", + "confirmYourLocation": "قم بتأكيد موقعك", + "confirmYourLocationDesc": "يرجى تأكيد موقعك الحالي لعرض الاتجاهات المناسبة.", + "insideHospital": "أنا داخل المستشفى", + "outsideHospital": "أنا خارج المستشفى", + "servicePriceList": "قائمة أسعار الخدمات", + "servicePriceListDesc": "توضح قائمة أسعار الخدمات أدناه رسوم الخدمات الصحية المقدمة للمرضى بنظام الدفع النقدي. أما فيما يتعلق بالخدمات المشمولة بالتأمين، فسيتم تطبيق التغطية التأمينية والتحقق من الأهلية واحتساب نسب التحمل وفقًا لشروط وثيقة التأمين وجدول المنافع المعتمد لكل شركة تأمين.", + "servicePriceListRights": "يحق للمريض الحصول على متابعة مجانية في غضون 14 يومًا من الزيارة الأولى", + "serviceName": "اسم الخدمة", + "noFreeSlot": "لا توجد مواعيد متاحة في التاريخ المحدد. يرجى اختيار تاريخ آخر أو إعادة المحاولة لاحقاً.", + "noThanksIKnowTheClinic": "لا، شكراً. أنا أعرف العيادة.", + "unableToSendOTP": "تعذر إرسال رمز التحقق لمرة واحدة", + "loadingAIAnalysis": "نقوم بتحليل نتائجك، يرجى التحلي بالصبر والسماح للذكاء الاصطناعي بالقيام بالمهمة، قد يستغرق هذا بعض الوقت.", + "symptoms": "أعراض", + "insuranceInActive": "التأمين غير نشط", + "insuranceInActiveContactSupport": "لديك تأمين ساري المفعول، ولكنه غير مُفعّل حاليًا في مجموعة حبيب الطبية. يُرجى التواصل مع خدمة عملاء مجموعة حبيب الطبية.", + "updateAppNow": "تحديث التطبيق الآن", + "Dr": "الدكتور.", + "reschedulingAppo": "إعادة جدولة الموعد، يرجى الانتظار...", + "invalidEligibility": "لا يمكنك إجراء الدفع عبر الإنترنت لأنك غير مؤهل لاستخدام الخدمة المقدمة.", + "invalidInsurance": "لا يمكنك إجراء الدفع عبر الإنترنت لأنه ليس لديك تأمين صالح.", + "continueCash": "متابعة الدفع نقدًا", + "applewatch": "ساعة آبل", + "applehealthapplicationshouldbeinstalledinyourphone": "يجب تثبيت تطبيق Apple Health على هاتفك", + "unabletodetectapplicationinstalledpleasecomebackonceinstalled": "لا يمكننا اكتشاف التطبيق المثبت على جهازك. يرجى العودة إلى هنا بمجرد تثبيت هذا التطبيق.", + "applewatchshouldbeconnected": "يجب توصيل ساعة آبل", + "samsungwatch": "ساعة سامسونج", + "samsunghealthapplicationshouldbeinstalledinyourphone": "يجب تثبيت تطبيق Samsung Health على هاتفك", + "samsungwatchshouldbeconnected": "يجب توصيل ساعة سامسونج", + "huaweiwatch": "ساعة هواوي", + "huaweihealthapplicationshouldbeinstalledinyourphone": "يجب تثبيت تطبيق Huawei Health على هاتفك", + "huaweiwatchshouldbeconnected": "يجب توصيل ساعة هواوي", + "whoopwatch": "ساعة Whoop", + "whoophealthapplicationshouldbeinstalledinyourphone": "يجب تثبيت تطبيق Whoop Health على هاتفك", + "whoopwatchshouldbeconnected": "يجب توصيل ساعة Whoop", + "updatetheinformation": "سيتيح ذلك جمع أحدث المعلومات من ساعة آبل الخاصة بك", + "timeFor": "الوقت", + "hmgPolicies": "سياسات مجموعة الحبيب الطبية", + "darkMode": "المظهر الداكن", + "featureComingSoonDescription": "هذه الميزة ستتوفر قريباً. نحن نعمل جاهدين لإضافة ميزات أكثر تميزاً إلى التطبيق. انتظرونا لمتابعة التحديثات.", + "generateAiAnalysisResult": "قم بإجراء تحليل لهذا المختبر AI", + "ratings": "التقييمات", + "hmgPharmacyText": "صيدلية الحبيب، المتجر الصيدلاني الإلكتروني المتكامل الذي تقدمه لكم مجموعة خدمات الدكتور سليمان الحبيب الطبية.", + "insuranceRequestSubmittedSuccessfully": "تم إرسال طلب تحديث بيانات التأمين بنجاح. سيتم إعلامك بمجرد الانتهاء.", + "updatingEmailAddress": "جارٍ تحديث عنوان البريد الإلكتروني، يرجى الانتظار...", + "verifyInsurance": "التحقق من التأمين", + "tests": "تحليل", + "calendarPermissionAlert": "يرجى منح إذن الوصول إلى التقويم من إعدادات التطبيق لضبط تذكير تناول الدواء.", + "sortByNearestLocation": "فرز حسب الأقرب إلى موقعك", + "giveLocationPermissionForNearestList": "يرجى منح إذن الوصول إلى الموقع من إعدادات التطبيق لعرض أقرب المواقع.", + "sortByLocation": "الترتيب حسب الموقع", + "timeForFirstReminder": "وقت التذكير الأول", + "reminderRemovalNote": "يمكنك إزالتها من التقويم الخاص بك لاحقاً عن طريق إيقاف تشغيل التذكير", + "communicationLanguage": "لغة التواصل", + "cmcServiceHeader": "فحص صحي شامل: تشخيص متقدم، معلومات صحية مفصلة", + "cmcServiceDescription": "احصل على معلومات تفصيلية عن صحتك من خلال خدمات التشخيص المتقدمة لدينا. افهم جسمك بشكل أفضل لمستقبل صحي.", + "eReferralServiceHeader": "نظام الإحالة الإلكترونية في مستشفى حبيب: تبسيط عملية إحالة المرضى", + "eReferralServiceDescription": "نُسهّل عملية نقل المرضى بسلاسة إلى مستشفى حبيب من خلال نظام الإحالة الإلكترونية الآمن لدينا. نضمن استمرارية الرعاية لكل مريض.", + "bloodDonationServiceHeader": "تبرع بالدم، أنقذ الأرواح. تبرعك يُحدث فرقاً.", + "bloodDonationServiceDescription": "تبرّع بالدم، وأنقذ الأرواح. تبرعك يبعث الأمل. انضم لحملة التبرع بالدم وكن شريان حياة للمحتاجين. كل قطرة تُحدث فرقًا!", + "healthTrackersServiceHeader": "تتبّع مؤشراتك الحيوية بسهولة ويسر", + "healthTrackersServiceDescription": "أدخل بياناتك لمراقبة معدل ضربات القلب وضغط الدم بشكل مستمر، بالإضافة إلى ملخصات دقيقة لأنشطتك اليومية. ابقَ على اطلاع وحسّن صحتك بسهولة.", + "waterConsumptionServiceHeader": "حافظ على رطوبتك، حافظ على صحتك. تتبع كمية الماء التي تشربها يومياً بكل سهولة.", + "waterConsumptionServiceDescription": "أروِ عطشك، وتابع صحتك. راقب كمية الماء التي تتناولها يومياً بكل سهولة باستخدام تطبيقنا سهل الاستخدام، مما يضمن لك الترطيب الأمثل والصحة الجيدة.", + "smartWatchServiceHeader": "قم بمزامنة ساعتك الذكية مع تطبيقات الصحة", + "smartWatchServiceDescription": "قم بتوصيل ساعتك الذكية بسلاسة بتطبيقنا الصحي لتتبع البيانات بسهولة والحصول على رؤى شخصية.", + "liveChatServiceHeader": "مساعدة الخبراء على مدار الساعة طوال أيام الأسبوع\n\nمساعدة", + "liveChatServiceDescription": "هل تحتاج إلى مساعدة؟ تتيح لك خدمة الدردشة المباشرة لدينا التواصل مع فريق دعم الخبراء للإجابة على أي أسئلة لديك حول الميزات أو الإعدادات أو استكشاف الأخطاء وإصلاحها.", + "emergencyServiceHeader": "تسجيل الوصول إلى قسم الطوارئ، أسرع من أي وقت مضى. اتصل بالإسعاف / فريق الاستجابة السريعة على الفور", + "emergencyServiceDescription": "هل تواجه حالة طبية طارئة؟ سيارات الإسعاف وفرق الاستجابة السريعة لدينا جاهزة على مدار الساعة. بالإضافة إلى ذلك، يمكنك تسجيل دخولك إلى قسم الطوارئ بسرعة لتلقي رعاية أسرع.", + "homeHealthCareServiceHeader": "صحتك، في أبهى صورها. رعاية فائقة الجودة، تصلك إلى عتبة دارك.", + "homeHealthCareServiceDescription": "نقدم لكم رعاية صحية عالية الجودة تصلكم إلى عتبة منزلكم. ممرضات ذوات خبرة يقدمون رعاية حانية في راحة منزلكم.", + "profileOnlyText": "الملف الشخصي", + "information": "معلومة", + "noFavouriteDoctors": "ليس لديك أي قائمة مفضلة حتى الآن", + "addDoctors": "إضافة الأطباء", + "favouriteList": "قائمة المفضلة", + "later": "لاحقاً", + "cancelAppointmentConfirmMessage": "هل أنت متأكد من رغبتك في إلغاء هذا الموعد؟", + "acknowledged": "معترف به", + "searchLabResults": "بحث نتائج المختبر", + "callForAssistance": "اتصل للحصول على المساعدة الفورية", + "oneWaySubtitle": "نقل من الموقع إلى المستشفى", + "twoWaySubtitle": "نقل من الموقع إلى المستشفى والعودة مرة أخرى", + "toHospitalSubtitle": "نقل من موقعك الحالي إلى المستشفى", + "fromHospitalSubtitle": "نقل من المستشفى إلى منزلك", + "clinicSchedule": "جدول العيادة", + "doctorAddedToFavourite": "تمت إضافة إلى قائمة المفضلة", + "doctorRemovedFromFavourite": "تمت إزالة من قائمة الأطباء المفضلين", + "addNewFavorite": "إضافة مفضلة جديدة", + "favourites": "المفضلة", + "pendingAncillaryOrders": "مدفوعات طلبات الاضافية", + "liveCarePermissions": "يتطلب لايف كير أذونات الكاميرا والميكروفون وإشعارات، يرجى السماح لها بالمتابعة.", + "containsImage": "يحتوي على صورة", + "servicesSelected": "الخدمة (الخدمات) المختارة", + "selectMedicalFileFromFamily": "اختر ملف طبي من عائلتك", + "anyFileFromHMG": "أي ملف طبي نشط من مجموعة حبيب الطبية", + "noDistance": "المسافة غير متوفرة", + "enterAmount": "يرجى إدخال المبلغ للمتابعة.", + "selectHospitalContinue": "يرجى اختيار المستشفى للمتابعة.", + "year": "سنة", + "notifyPrescription": "أبلغني قبل وقت الاستهلاك", + "doctorRatingAppointment": "طبيب\nتصنيف", + "ratingSubmitted": "تم إرسال التقييم", + "rateTheDoctor": "يرجى تقييم الطبيب", + "downloadInvoice": "تحميل الفاتورة", + "approvalDetails": "تفاصيل الموافقة", + "obese": "سمين", + "overWeight": "وزن زائد", + "underWeight": "وزن ناقص", + "nearestGate": "أقرب بوابة", + "RRTDDetails": "فريق الاستجابة السريعة يقوم بتقديم رعاية صحية متكاملة لجميع الحالات الطارئة و المستقرة، الخدمة تشمل جميع الأعمار بما في ذلك حالات الأطفال.", + "normalMapType": "افتراضي", + "satelliteMapType": "قمر صناعي", + "terrainMapType": "تضاريس", + "hybridMapType": "هجين", + "openCamera": "افتح الكاميرا", + "openGallery": "افتح المعرض", + "openFiles": "فتح الملفات", + "generateAiAnalysisRadResult": "بإنشاء تحليل الذكاء الاصطناعي لهذا التقرير", + "grantLocationPermission": "يرجى منح إذن الوصول إلى الموقع من إعدادات التطبيق للحصول على نتائج أفضل.", + "NoBookedAppointments": "لا توجد مواعيد محجوزة", + "aiDisclaimerRad": "سيتم مشاركة بيانات نتائج الأشعة الخاصة بك بشكل آمن مع محلل الذكاء الاصطناعي لدينا لتحليلها. يساعد هذا في توفير رؤى صحية مخصصة. هل ترغب في المتابعة؟", + "imageSizeTooLarge": "حجم الصورة كبير جداً. الحد الأقصى للحجم هو 1 ميجابايت", + "profileImageUpdatedSuccessfully": "تم تحديث صورة الملف الشخصي بنجاح", + "failedToProcessImage": "فشل في معالجة الصورة. يرجى المحاولة مرة أخرى", + "cameraGalleryPermissionMessage": "مطلوب إذن الكاميرا والمعرض لتحميل صورة الملف الشخصي. يرجى منح الأذونات من إعدادات التطبيق.", + "cameraGalleryPermissionRequired": "مطلوب أذونات الكاميرا والمعرض", + "cameraPermissionMessage": "مطلوب إذن الكاميرا لالتقاط الصور. يرجى منح الإذن من إعدادات التطبيق.", + "cameraPermissionRequired": "مطلوب إذن الكاميرا", + "galleryPermissionMessage": "مطلوب إذن المعرض لتحديد الصور. يرجى منح الإذن من إعدادات التطبيق.", + "galleryPermissionRequired": "مطلوب إذن المعرض", + "failedToCheckPermissions": "فشل في التحقق من الأذونات. يرجى المحاولة مرة أخرى", + "takePhotoWithCamera": "التقط صورة بالكاميرا", + "selectFromPhotoLibrary": "اختر من مكتبة الصور", + "selectDocumentFiles": "اختر ملفات المستندات", + "profilePicture": "صورة الملف الشخصي", + "sendingAskDrRequest": "جارٍ إرسال الطلب...", + "askDrRequestSentSuccess": "تم إرسال الطلب بنجاح، وسيتواصل معك الطبيب قريباً.", + "onlineCheckIn24HoursOnly": "لا تتوفر خدمة تسجيل الوصول عبر الإنترنت إلا خلال 4 ساعات من موعد الحجز.لا تتوفر خدمة تسجيل الوصول عبر الإنترنت إلا خلال 4 ساعات من موعد الحجز." +}; +static const Map _en_US = { + "english": "English", + "arabic": "Arabic", + "login": "Login", + "noDataAvailable": "No Data Available", + "noRatingAvailable": "No Rating Available", + "doctorDoesNotHaveRating": "This doctor does not have any ratings yet.", + "confirm": "Confirm", + "loadingText": "Loading, please wait...", + "kilometerUnit": "KM", + "dashboardScreenToolbarTitle": "Home", + "settings": "Settings", + "language": "App Language", + "lanEnglish": "English", + "lanArabic": "العربية", + "cancel": "Cancel", + "done": "Done", + "replay2": "Replay", + "home": "Home", + "services": "SERVICES", + "mySchedule": "My Schedule", + "logout": "Logout", + "respirationRate": "Respiration Rate", + "bookAppo": "Book Appointment", + "searchBy": "Search By:", + "clinic": "Clinic", + "byClinic": "By Clinic", + "hospital": "Hospital", + "byHospital": "By Hospital", + "name": "Name", + "doctor": "Doctor", + "clinicName": "Clinic Name", + "hospitalName": "Hospital Name", + "noClinicFound": "No Clinic Found", + "doctorName": "Doctor Name", + "nearestAppo": "Nearest Appointment", + "searchByDocText": "Type the name of the doctor to help you find him", + "enterDocName": "Enter Doctor name", + "search": "Search", + "noResultFound": "No Result Found", + "noSearchResultFound": "No Result Found", + "pleaseEnterProductName": "Please Enter Product Name", + "bookNow": "Book Now", + "docInfo": "Doctor Information", + "appoInfo": "Appointment Information", + "availableAppo": "Available Appointments", + "gender": "Gender", + "nationality": "Nationality", + "neverWait": "Never wait in queue", + "reminderTitle": "You have appointment with Dr. ", + "appointmentCalendar": "Would you like to set a reminder for this appointment in your calendar?", + "appointmentSuccess": "Appointment is successfully booked", + "docQualifications": "Doctor Qualifications", + "pressAgain": "Press again to exit the app", + "laserMaxLimitReach": "Maximum limit is 90 minutes", + "confirmAppoHeading": "Kindly review your Appointment", + "patientInfo": "Patient Information", + "doctorFilter": "Doctors will be filtered based on your gender and age", + "bookSuccess": "Book Success", + "patientShare": "Patient Share", + "patientShareWithTax": "Patient Share with Tax", + "confirmAppo": "Confirm Appointment", + "confirmLiveCare": "Confirm LiveCare", + "waitingForDoctor": "Waiting for doctor", + "appointment": "Appointment", + "confirmLater": "Confirm Later", + "todoList": "Todo List", + "appoActions": "Appointment Actions", + "date": "Date", + "time": "Time", + "pendingPayment": "Pending for payment", + "payNow": "Pay Now", + "viewQR": "View QR Code", + "instruction": "Instructions", + "livecare": "LiveCare", + "livecareAppo": "LiveCare Appointment", + "agreeTo": "I agree to the", + "termsConditoins": "Terms and Conditions", + "cancelAppoMsg": "Are you sure you want to cancel this appointment?", + "changePayment": "Change Payment Method", + "upcomingNoAction": "No Action Required", + "upcomingConfirm": "Please confirm the appointment to avoid cancellation", + "bookSuccessConfirmMore2412": "The online payment process will be available 24 hours before the appointment.", + "upcomingPaymentPending": "Online Payment will be Activated before 24 Hours of Appointment Time", + "upcomingPaymentNow": "Pay Online now to avoid long waiting queue", + "upcomingQR": "Use the QR Code to Check-In in hospital", + "upcomingVirtual": "This is a virtual appointment, Please adhere to the instructions for a seamless experience.", + "upcomingLivecare": "This is a LiveCare appointment", + "upcomingDetails": "More Details", + "reschedule": "Reschedule", + "raise": "Raise", + "complaint": "Complaint", + "add": "Add", + "reminder": "Reminder", + "location": "Locations", + "online": "Online", + "payment": "Payment", + "onlinePayment": "Online", + "onlinePaymentSubtitle": "Payment", + "cancelNocaps": "Cancel", + "setReminder": "Set Reminder", + "nationalIdNumber": "National ID Number", + "medicalFileNumber": "Medical File Number", + "loginregister": "Login / Register", + "poweredBy": "Powered by", + "welcome": "Welcome", + "welcomeToDrSulaiman": "Welcome to Dr. Sulaiman Al Habib Medical Group", + "patientApp": "Patient App", + "welcomeText": "Dr. Sulaiman Al Habib Mobile Application", + "drSulaimanText": "Dr. Sulaiman Al Habib", + "welcomeText2": "Have you previously visited the hospitals or medical centers of Dr. Sulaiman Al Habib?", + "yes": "Yes", + "no": "No", + "logintyperadio": " Choose from the below options to login to your medical file.", + "registernow": "Register Now", + "nationalID": "Enter the Identification Number", + "nationalId": "National ID", + "fileNo": "File Number", + "fileno": "File No", + "forgotFileNo": "Forgot file Number?", + "forgotFileNoTitle": "Forgot medical file", + "enterNationalId": "Please enter mobile number and identification number", + "profileInfo": "Please enter profile information", + "submit": "Submit", + "forgotDesc": "Enter the mobile number to receive the Medical File Number via SMS", + "dob": "Birth Date:", + "hijriDate": "Hijri Date", + "gregorianDate": "Gregorian Date", + "verifyLoginWith": "Please choose one of the following options to verify", + "registerUser": "Register", + "verifyWithFingerprint": "Biometric", + "verifyWithFaceid": "Face ID", + "verifyWithSms": "SMS", + "verifyWithWhatsapp": "Whatsapp", + "verifyThrough": "Verify Through", + "lastLogin": "LAST LOGIN AT:", + "lastLoginWith": "VERIFICATION TYPE:", + "verifyFingerprint": "To activate the fingerprint login service, please verify data by using one of the following options.", + "searchMedicine": "Search Medicine", + "pharmaciesList": "Pharmacies List", + "searchMedicineHere": "Search Medicine Here", + "description": "Description", + "howToUse": "How to Use", + "price": "Price", + "youCanFindItIn": "You can find it in", + "pleaseEnterMedicineName": "Please Enter Medicine Name", + "verificationMessage": "Please enter the Verification Code sent to", + "validationMessage": "The verification code expires in", + "notification": "Notifications", + "appSettings": "App Settings", + "rateApp": "Rate Our App", + "before": "Before", + "minute": "Minutes", + "hour": "Hour", + "reminderSuccess": "The reminder has been added successfully", + "reminderCancelSuccess": "The reminder has been cancelled successfully", + "patientShareToDo": "Amount before tax: ", + "patientTaxToDo": "Tax amount: ", + "patientShareTotalToDo": "Total amount due: ", + "paymentMethod": "Payment Method", + "noNeedToWaitInLine": "No need to stand in line.", + "useQRAppoAttend": "Use the QR code to register the appointment attendance.", + "passQRAppoAttend": "Pass the QR code through the attendance devices available in the Hospital.", + "sitWaitingQR": "Sit in the waiting rooms until called by the nurse.", + "attendRegisterCode": "Attendance registration code", + "scanQRHospital": "Approach the Online Check-In board in the hospital & scan via NFC or QR Code to Check-In", + "scanNFC": "Scan NFC to Check-In", + "sendEmail": "Send Email", + "success": "Done successfully", + "emailSentSuccessfully": "Email Sent Successfully", + "emailSentError": "Error Sending Email", + "close": "Close", + "closeIt": "Close", + "booked": "Booked", + "confirmed": "Confirmed", + "arrived": "Arrived", + "payNowBookSuccess": "Pay now via Al Habib App", + "payNowBookSuccesstext1": "Pay Now using online payment service from secure payment gateways", + "payNowBookSuccesstext2": "You can also Pay Later via online payment or in Hospital", + "payLater": "Pay Later", + "askDocNotAllowed": "This service will be available for last 15 days’ doctor Visit only", + "moreVerify": "More Verification Options", + "welcomeBack": "Welcome back!", + "accountInfo": "Would you like to login with current username?", + "anotherAcc": "Use Another Account", + "next": "Next", + "firstName": "First Name", + "middleName": "Middle Name", + "lastName": "Last Name", + "female": "Female", + "male": "Male", + "preferredLanguage": "Preferred Language", + "locationsRegister": "Where do you want to create this file?", + "ksa": "KSA", + "dubai": "Dubai", + "enterEmail": "Please Enter Email", + "family": "My Family", + "familyTitle": "My Family Files", + "addNewMember": "Add Family Member", + "sentRequests": "Sent Requests", + "recievedRequests": "Recieved Requests", + "manageFiles": "Manage Family Files", + "oxygenation": "Oxygenation", + "bodyMeasurements": "body Mass Index", + "temperature": "Temperature", + "pulse": "Pulse", + "respiration": "Respiration", + "bloodPressure": "Blood Pressure", + "painScale": "Pain Scale", + "weight": "Weight", + "height": "Height", + "heart": "Heart Rate", + "heightUnit": "height unit", + "weightUnit": "Weight Unit", + "unit": "Unit", + "request": "Request", + "memberName": "Member Name", + "switchLogin": "Switch", + "removeMember": "Remove", + "allowView": "Allow", + "rejectView": "Reject", + "deleteView": "Delete", + "myFamily": "MY FAMILY", + "approvals": "Approvals", + "approvalNo": "Approval No:", + "companyName": "Company Name:", + "receiptOn": "Receipt on:", + "expiryDate": "Expiry Date:", + "expiryPoints": "Expired", + "expiryOn": "Expiry on:", + "procedureName": "Procedure Name:", + "procedure": "Procedure", + "procedureStatus": "Procedure Status: ", + "usageStatus": "Usage Status", + "unusedCount": "Unused Count:", + "totalApproval": "Total approval unused", + "category": "Category: ", + "expirationDate": "Expiration Date: ", + "patientCard": "Patient Card ID: ", + "policyNumber": "Policy Number: ", + "seeDetails": "SEE DETAILS", + "insuranceCards": "Insurance Cards", + "requestType": "Request Type", + "registerInfoFamily": "How would like to add the new member?", + "removeFamilyMember": "Remove this member?", + "myMedicalFile": "My Medical File", + "myMedicalFileSubTitle": "All your medical records", + "viewMore": "View More", + "homeHealthCareService": "Home Health Care Service", + "onlinePharmacy": "Online Pharmacy", + "emergencyService": "Emergency Service", + "onlinePaymentService": "Online Payment Service", + "offersAndPackages": "Online transfer request", + "comprehensiveMedicalCheckup": "Comprehensive Medical Check-up", + "hmgService": "HMG Service", + "viewAllHabibMedicalService": "View All Habib Medical Service", + "viewAll": "View All", + "view": "View", + "contactUs": "Contact Us", + "contactUsLocation": "P.O.Box: 91877 - Riyadh 11643, King Fahad Road - Olaya - Kingdom of Saudi Arabia", + "contactUsTime": "Saturday - Wednesday 8:00 AM - 10 PM, Thursday 8:00 AM- 8:00 PM, Friday 2:00 PM - 8:00 PM", + "viewAllWaysReachUs": "View All Ways Reach Us", + "medicalProfile": "Medical Profile", + "consultation": "Consultation", + "logs": "Logs", + "textToSpeech": "How May I Help You?", + "locationDialogMessage": "Allow the HMG app to access your location will assist you in showing the hospitals according to the nearest to you.", + "userViewRequester": "User Wants to View Your Medical File", + "userView": "User Can View Your Medical File", + "parking": "Parking", + "alhabiServices": "HMG Service", + "parkingTitle": "Car service, car service, service to save parking information, return to it later, 1- By clicking on (Read the code), save the parking data. 2- By clicking on the button (view my car park), it shows you the car’s location in Google Maps. 3- Read another position by pressing the Clear Position Data button.", + "parkingDescription": "Parking service is for you to scan the car location so you can find it easy on you way out. click on (scan parking) button to save the parking location, then (show my park) button will appear to show you the way for the parking. if you want to rescan parking QR just click on (Clear My Data).", + "appointments": "Appointments", + "all2": "All", + "checkinOption": "Check-In", + "readBarcode": "Read Barcode", + "showMyPark": "Show My Park", + "clearMyData": "Clear My Data", + "floor": "Floor:", + "gate": "Gate:", + "building": "Building:", + "branch": "Branch:", + "emergencyServices": "Emergency Services", + "nearester": "Nearest ER", + "locationa": "Location", + "callNow": "Call now", + "ambulancerequest": "Ambulance", + "requestA": "Request", + "noBookedAppointments": "No Booked Appointments", + "noConfirmedAppointments": "No Confirmed Appointments", + "noArrivedAppointments": "No Arrived Appointments", + "myAppointmentsList": "Appointments", + "myAppointments": "My", + "radiology": "Radiology", + "radiologySubtitle": "Result", + "lab": "Lab", + "labSubtitle": "Results", + "medicines": "Medicines", + "medicinesSubtitle": "Prescriptions", + "vitalSigns": "Vital Signs", + "vitalSignsSubTitle": "Reports", + "vitalSignNormal": "Normal", + "vitalSignLow": "Low", + "vitalSignHigh": "High", + "vitalSignObese": "Obese", + "vitalSignOverweight": "Overweight", + "vitalSignUnderweight": "Underweight", + "myMedical": "Active", + "myMedicalSubtitle": "Medications", + "myDoctor": "My Doctors", + "myDoctorSubtitle": "List", + "eye": "Eye", + "eyeSubtitle": "Measurement", + "insurance": "Insurance", + "insuranceSubtitle": "Card", + "updateInsurance": "Update", + "updateInsuranceSubtitle": "Insurance", + "insuranceApproval": "Insurance", + "insuranceApprovalSubtitle": "Approvals", + "allergies": "Allergies", + "allergiesSubtitle": "List", + "myVaccines": "My Vaccines", + "myVaccinesSubtitle": "List", + "medical": "Medical", + "medicalSubtitle": "Report", + "monthly": "Monthly", + "monthlySubtitle": "Report", + "sick": "Sick", + "sickSubtitle": "Leaves", + "myBalance": "My Balance", + "myBalanceSubtitle": "Credit", + "patientCall": "Patient Call", + "patientCallSubtitle": "Service", + "smartWatches": "Smart Watches", + "smartWatchesSubtitle": "Pairing", + "myTrackers": "My Trackers", + "myTrackersSubtitle": "Service", + "askYour": "Ask Your", + "askYourSubtitle": "Doctor", + "internet": "Internet", + "internetSubtitle": "Pairing", + "chatbot": "Chatbot", + "chatbotSubtitle": "", + "timeLine": "Timeline", + "labOrders": "Lab Orders", + "billNo": "Bill No:", + "prescriptions": "Prescriptions", + "history": "History", + "orderNo": "Order No", + "orderDetails": "Order Details", + "deliveryDriverTrack": "Driver Tracking", + "deliveryLocation": "Delivery Location", + "driver": "Driver", + "vitalSign": "Vital Sign", + "monthlyReports": "Monthly Reports", + "km": "KMs:", + "km_": "KM", + "patientHealthSummaryReport": "Patient Health Summary Report", + "toViewTheTermsAndConditions": "To View the Terms and Conditions Report", + "clickHere": "Click here", + "iAgreeToTheTermsAndConditions": "I agree to the terms and conditions ", + "iAgreeToTheTermsAndConditionsSubtitle": "I agree to the terms and conditions ", + "save": "Save", + "userAgreement": "User Agreement", + "updateSuccessfully": "Update Successfully", + "checkVaccineAvailability": "CHECK VACCINE AVAILABILITY", + "myVaccinesAvailability": "My Vaccines Availability", + "paymentService": "Payment Service", + "paymentOnline": "Service", + "onlineCheckIn": "Online Check-In", + "myBalances": "My Balances", + "myWallet": "My Wallet", + "balanceAmount": "Wallet Amount", + "totalBalance": "Total Balance", + "createAdvancedPayment": "Recharge Wallet", + "advancePayment": "Advance Payment", + "advancePaymentLabel": "You can create and add an Advanced Payment for your account or other accounts.", + "fileNumber": "File Number", + "amount": "Amount *", + "depositorEmail": "Depositor Email *", + "notes": "Notes", + "selectPatientName": "Select Patient Name", + "selectFamilyPatientName": "Family Members", + "selectHospital": "Select Hospital", + "selectCity": "Select City", + "city": "City", + "distance": "Distance", + "otherAccount": "Other Account", + "selectBeneficiary": "Select Beneficiary", + "confirmThePayment": "Confirm The Payment", + "depositorName": "Depositor Name", + "mobileNumber": "Mobile Number", + "phoneNumber": "Phone Number", + "country": "Country", + "ok": "Ok", + "averageWaitingTime": "Average Waiting Time:", + "waitingTime": "Expected waiting time:", + "waterConsumedInWeek": "Water consumed in a week", + "waterConsumedInMonth": "Water consumed in a month", + "theVerificationCodeExpiresIn": "The Verification Code Expires in", + "pleaseEnterTheVerificationCode": "Please enter the verification code send to", + "eyeMeasurements": "Eye Measurements", + "measurements": "Measurements", + "classes": "Glasses", + "contactLens": "Contact Lens", + "rightEye": "Right Eye", + "sphere": "Sphere", + "cylinder": "Cylinder", + "axis": "Axis", + "prism": "Prism", + "va": "VA", + "leftEye": "Left Eye", + "brand": "Brand", + "power": "Power", + "diameter": "Diameter", + "activeMedications": "Active Medications", + "expDate": "Active Exp Date :", + "dailyQuantity": "Daily Quantity :", + "addReminder": "Add Reminder", + "cancelReminder": "Cancel Reminder", + "reminderDes": "Please select treatment start day and time to be notified when it's time to take the medicine", + "pleaseSelectAllQuestionToContinue": "Please answer all questions to continue...", + "startDay": "Start Day", + "endDay": "End Day", + "days": "Days ", + "pleaseVerify": "Please Verify ", + "scheduleTime": "Schedule time", + "askDoctor": "Ask Doctor", + "doctorResponses": "Doctor Responses", + "ne_w": "New", + "all": "All", + "dailyWater": "Daily Water Check", + "questionHere": "Enter the question here...", + "viewDoctorResponses": "View Doctor Responses", + "serviceInformationButton": "LOGIN / REGISTER", + "serviceInformationTitle": "Service Information", + "serviceInformation": "Service Information", + "homeHealthCare": "Home Health Care", + "noAppointmentAvailable": "No Available Appointments", + "homeHealthCareText": "This service provides a set of home health care services, continuous and comprehensive follow-up in their places of residence for those who cannot access health facilities, such as (laboratory analyzes - radiology - vaccinations - physical therapy), etc.", + "loginRegister": "Login/Register", + "orderLog": "Order Log", + "infoLab": "This service allows you to view the results of all laboratory tests performed in Al Habib Medical Group as well as sending the report via e-mail.", + "infoRadiology": "This service allows you to view the reports and photos of radiology in Al Habib Medical Group as well as send the report by e-mail.", + "noReviewsAvailable": "No Reviews Available", + "noLocationAvailable": "No Location Available", + "orders": "Orders", + "wishlist": "Wishlist", + "products": "Products", + "reviews": "Reviews", + "brands": "Brands", + "productDetails": "Product Details", + "medicationRefill": "Medication Refill", + "pillReminder": "Pill Reminder", + "shippingAddresses": "Shipping Addresses", + "reachUs": "Reach Us", + "ourLocations": "Our Locations", + "edit": "Edit", + "whatsApp": "Whats App", + "phone": "Phone", + "delete": "Delete", + "deleteAddress": "Are you sure want to delete", + "deletedAddres": "Address has been deleted", + "addAddress": "ADD A NEW ADDRESS", + "addNewAddress": "Add New Address", + "order": "My Order", + "delivered": "Delivered", + "pending": "Pending", + "enterNameHere": "Enter Your Name", + "processing": "Processing", + "cancelled": "Cancelled", + "writeReview": "Write Review", + "shareReview": "SHARE REVIEW", + "review": " reviews", + "viewMedicalFile": "View Details", + "viewAllServices": "View All Services", + "medicalFile": "Medical File", + "verified": "Verified", + "checkup": "Checkup", + "hhcHome": "Home", + "refferal": "E-Refferal", + "refferalTitle": "E-Refferal", + "refferalSubTitle": "Service", + "healthCare": "Health Care", + "emergency": "Emergency", + "erservices": "Emergency", + "services2": "Services", + "cantSeeProfile": "To view your medical profile, please log in or register now", + "loginRegisterNow": "Login or Register Now", + "hmgPharmacy": "HMG Pharmacy", + "ecommerceSolution": "Ecommerce Solution", + "comprehensive": "Comprehensive", + "onlineConsulting": "Online Consulting", + "pendingOrder": " PENDING", + "deliveredOrder": " DELIVERED", + "processingOrder": " PROCESSING", + "cancelledOrder": " CANCELLED", + "compare": " Compare", + "medicationsRefill": " Medication Refill", + "recommended": " Recommended for You", + "myPrescription": " My Prescriptions", + "quantity": " QTY ", + "reviewAppointment": " Review Appointment ", + "backMyAccount": "BACK TO MY ACCOUNT ", + "reviewSuccessful": "Review Successful", + "reviewShared": "Your review has been shared on product review section", + "reviewComment": "Your reviews help other to choose better product", + "shippedMethod": "SHIP BY:", + "orderDetail": "Order Details", + "orderSummary": "Order Summary", + "subtotal": "Subtotal", + "shipping": "Shipping", + "shipBy": "SHIP BY:", + "lakumPoints": "Lakum Points", + "useLakumPoints": "Use Lakum points", + "use": "USE", + "proceedPay": "PROCEED TO PAY", + "vat": "VAT (15%)", + "inclusiveVat": "(inclusive VAT)", + "items": "item(s)", + "checkOut": "CHECK OUT", + "sar": "SAR", + "aed": "AED", + "payOnline": "PAY ONLINE", + "cancelOrder": "CANCEL ORDER", + "confirmAddress": "CONFIRM ADDRESS ", + "confirmLocation": "CONFIRM LOCATION ", + "conditionsHMG": "Terms & Conditions ", + "conditions": "Terms & Conditions of Lakum", + "confirmDeleteMsg": "Are you sure! want to delete ", + "confirmDelete": "DELETE", + "confirmCancellation": "Are you sure! want to cancel this order ", + "orderNumber": "Order#: ", + "orderDate": "Date", + "itemsNo": "items(s)", + "noOrder": "You Don't have any orders.", + "noResult": "No Result.", + "nonRecommended": "No Recommended Products.", + "termsService": "Terms of Service", + "beforeusing": "Before using the checkup, please read Terms of Service.", + "accept": "I read and accept Terms of Service and Privacy Policy", + "dataSafeInfo": "Information that you provide is anonymous and not shared with anyone.", + "dataSafe": " Your data is safe.", + "informational": "Checkup is for informational purposes and is not a qualified medical opinion", + "notUseInEmerbency": "Do not use in emergencies.", + "notUseInEmerbencyDetails": "In case of health emergency, ", + "notUseInEmerbencyDetailsCall": "call the nearest emergency number immediately", + "checkDiagnosis": "Checkup is not a diagnosis.", + "remeberthat": "Remember that", + "loginToUseService": "You need to login to use this service", + "offersAndPromotions": "OFFERS & SPECIAL PROMOTIONS", + "offers": "OFFERS", + "myPrescriptions": "MY PRESCRIPTIONS", + "searchAndScanMedication": "SEARCH & SCAN FOR MEDICATION", + "shopByBrands": "Shop by Brands", + "recentlyViewed": "Recently Viewed", + "bestSellers": "Best Seller", + "deleteAllItems": "Delete All Items", + "total": "Total", + "totalWithColonRight": "Total:", + "selectAddress": "Select Address", + "shippingAddress": "SHIPPING ADDRESS", + "changeAddress": "Change Address", + "selectPaymentOption": "Select Payment Option", + "selectTamaraPlan": "Select Tamara Payment Plan", + "changeMethod": "Change Method", + "reviewOrder": "Review Order", + "active": "Active", + "inactive": "InActive", + "balance": "Balance", + "gained": "GAINED", + "consumed": "Consumed", + "transferred": "Transferred", + "riyal": "RIYAL", + "membersince": "MEMBER SINCE", + "identification": "Identification Number", + "lakumMobile": "Mobile Number", + "waitinggained": "Waiting gained", + "expired": "Expired", + "willBeExpired": "Will Be Expired", + "lakumPoint": "Lakum Points", + "activateLAKUMAccount": "Activate LAKUM Account", + "checkBeneficiary": "CHECK BENEFICIARY", + "beneficiaryName": "Beneficiary Name", + "accountActivation": "Account Activation", + "lakumTransfer": "Lakum Transfer", + "acceptLbl": "Accept", + "declineLbl": "Decline", + "selectGender": "Select Gender", + "iAmA": "I am a ...", + "selectAge": "Select Your Age", + "select": "Select", + "iAm": "I am", + "yearsOld": "years old", + "dragPoint": "Drag point to change your age", + "refine": "Refine", + "subGroup": "Subgroup", + "max": "Max", + "compeleteOrderMsg": "Order has been placed successfully!!", + "addToCompareMsg": "You have added a product to the Compare list", + "itInListMsg": "Item is already in the list", + "compareListFull": "Your compare list is full", + "addQuantity": "You should add quantity", + "addToCartMsg": "You have added a product to the cart", + "addToWishlistMsg": "You have added a product to the Wishlist", + "notifyMeMsg": "You will be notified when product available", + "removeFromWishlistMsg": "You have removed a product from the Wishlist", + "min": "Min", + "reset": "Reset", + "apply": "Apply", + "viewCategorise": "View All Categories", + "viewSubCategorise": "View All Sub Categories", + "categorise": "Categories", + "wishList": "WishList", + "myAccount": "My Account", + "cart": "Cart", + "alhabibapp": "Al Habib app", + "searchProductHere": "Search Product here", + "hhcNotAuthMsg": "This service provides a set of home health care services, continuous and comprehensive follow-up in their places of residence for those who cannot access health facilities, such as (laboratory analyzes - radiology - vaccinations - physical therapy), etc.", + "email": "Email *", + "book": "Book", + "appointmentLabel": "Appointment", + "bloodType": "Blood Type", + "maritalStatus": "Marital status", + "general": "General", + "profile": "My Profile", + "notifications": "Notifications", + "notificationDetails": "Notification Details", + "notificationDetailsa": "Notification Details", + "infoMyDoctorPoints": "", + "infoMyDoctor": "This service allows you to see all the doctors you have visited in Al Habib Medical Group, and through this service:", + "infoPrescriptions": "This service allows you to view all the medical prescriptions issued by Al Habib Medical Group, and through this service, you can:", + "infoMyPrescriptionPoints": "", + "infoInsuranceCards": "This service allows you to view all the insurance cards that recorded during your visits to Al Habib Medical Group in addition to:", + "infoInsuranceCardsPoints": "", + "infoAllergies": "This service allows you to view all types of allergies recorded during your visits to Al Habib Medical Group.", + "infoSickLeaves": "This service allows you to view all sick leaves that were taken in Al Habib Medical Group in addition to:", + "infoSickLeavePoints": "", + "infoApprovals": "This service allows you to view all approvals requests that have been sent to the insurance companies in addition to:", + "infoApprovalPoints": "", + "infoMonthReport": "Upon activation of this service, the system will send a monthly report automatically to the registered email which lists the vital signs and the results for the last visits made in AlHabib Medical Group.", + "languageSetting": "SMS, WhatsApp and Confirmation Calls Language", + "alert": "Alerts", + "emailAlert": "Alert by Email", + "smsAlert": "Alert by SMS", + "contactInfo": "Contact Information", + "emrgName": "Emergency Contact Name", + "emrgNo": "Emergency Contact Number", + "modes": "Modes", + "vibration": "Vibration Touch Feedback", + "blindModes": "Modes for Partially Blind", + "invertTheme": "Invert", + "offTheme": "Off", + "dimTheme": "Dim", + "bwTheme": "Black and White", + "permissions": "Permission", + "cameraPermission": "Camera", + "locationPermission": "Location", + "needPrescription": "This product requires a prescription", + "outOfStockMsg": "You have added product which is out of stock now, Please remove that!", + "noArabicLetters": "It Is Not Allow to Use Arabic Letters", + "noOffersAvailable": "No Offers Available!", + "accessibility": "Accessibility Mode", + "orderStatus": "Order Status", + "findUs": "Find Us", + "liveChat": "Live Chat", + "service": "Service", + "hmgServiceLabel": "HMG Service", + "healthWeatherIndicators": "Health Weather Indicators", + "healthTipsBasedOnCurrentWeather": "Health Tips Based On Current Weather", + "moreDetails": "More details", + "resendOrder": "Refill and Delivery", + "ports": "Ports", + "way": "Way", + "dailyDoses": "Daily Doses", + "period": "Period", + "duration": "المدة", + "cm": "CM", + "ft": "ft", + "kg": "kg", + "lb": "lb", + "birthDate": "Birth Date", + "dateOfBirth": "Date of Birth", + "mass": "Mass", + "tempC": "°C", + "bpm": "bpm", + "respirationSigns": "Respiration", + "sysDias": "SBP/DBP", + "body": "Body Mass", + "bodyString": "Body", + "face": "Face", + "retouch": "Re-touch", + "bikini": "Bikini", + "totalMinutes": "Total Minutes", + "feedback": "Feedback", + "likeToHear": "We would love to hear the feedback, concerns on healthcare services and eServices experience. Please use the below form", + "emptySubject": "Please enter the subject", + "emptyMessage": "Please enter message", + "selectAttachment": "Select Attachment", + "complainAppo": "Complaint for appointment", + "complainAppoAlt": "Complaint without appointment", + "messageType": "Message Type", + "feedbackType": "Feedback Type", + "compliment": "Appreciation", + "suggestion": "Suggestion", + "yourFeedback": "Your feedback was sent", + "selectPart": "Please select the part that complain about", + "number": "Number", + "notClassified": "Not classified", + "selectClinic": "Select Clinic", + "selectProject": "Select Hospital", + "searchItemError": "Item name should be more than 3 character", + "youCanFind": "You Can Find ", + "itemInSearch": " Item in Search", + "bloodDonation": "Blood Donation", + "bloodDonationInfo": "Through this service, you can register your name as a blood donor where the blood bank in the Habib Medical Group will communicate you in case of need for blood type.", + "bloodInstruction": "Enter the required information, in order to register for Blood Donation Service", + "aboutBloodDonation": "About Blood Donation", + "bloodDonationDescription": "Blood donation is a noble act that saves lives. Every single donation can save up to three people. By donating blood, you help patients in need of transfusions, emergencies, and medical treatments.", + "bloodDonationBenefit1": "Safe and quick process (10-15 minutes)", + "bloodDonationBenefit2": "You can donate every 8-12 weeks", + "bloodDonationBenefit3": "Free health check of your blood", + "viewTerms": "To view the terms and conditions", + "wantConnectHmgNetwork": "Dear customer there is no internet access, do you want to connect with HMG network to use our app, make sure you are in range of HMG network", + "failedToAccessHmgServices": "Connected with HMG Network,\n\nBut failed to access HMG services", + "offerAndPackages": "Offers and Packages", + "offerAndPackagesDetails": "This service allows you to view all HMG Offers:", + "invoiceNo": "Invoice No", + "invoiceDate": "Invoice Date", + "specialResult": "Special Result", + "generalResult": "General Result", + "showMoreBtn": "Flow Chart", + "value": "Value", + "report": "Radiology Report", + "openRad": "Open Image", + "sendCopy": "Email the Report", + "appoSurvey": "Survey", + "appoSurveySubtitle": "Survey", + "labResults": "Lab Results", + "doctorRating": "Doctor Rating", + "good": "Good", + "vGood": "Very Good", + "excellent": "Excellent", + "average": "Average", + "infoSigns": "This service allows you to view all vital signs were performed in the Habib Medical Group, e.x (height, weight, body mass index, heart rate, etc.) as well shows some statistics charts.", + "infoAdvancePayment": "This service designed so that you can deposit an amount in advance either in your account or in someone else's account with Al Habib Medical Group.", + "infoMyBalance": "This service allows you to check your balance in all branchs", + "erContant": "This service displays nearest branch among all the branches of Al Habib Medical Group based on your current location.", + "infoAmbulance": "Through this service, you can request evacuation by ambulance, whether from home or to home, in addition to a set of other services", + "infoMyAppointments": "This service allows you to see all the appointment you have visited in Al Habib Medical Group, and through this service:", + "infoTodo": "This service is designed to enable you to have a quick link to the list of tasks that need to be done", + "familyInfo": "Through this service, you will be able to link your family medical files to your medical file so that you can manage their records by login to your medical file.", + "rrtdDetails": "The Rapid response team provides a comprehensive medical service for all sorts of urgent and stable cases of all ages, including adult and paediatric patients, the service is covered by a fully equipped and highly trained medical team capable of providing the best medical care at the patient’s home.", + "onlineCheckInAgreement": "The online check-in is for non-life threatening situation. Call the red crescent (997) or go to the nearest emergency department if there are: \n\nsigns of stroke or heart attack \nhistory of seizure or syncope \nthere is limb or life threatening injury \npicture of severe injuries", + "infoEreferral": "This service allows you to submit a Referral request from any health care providers either inside or outside the kingdom of Saudi Arabia to any of HMG Hospitals, by filling some of the patient's data and attaching the medical reports, moreover you can track the request status (Under process, Accepted or Rejected)", + "erConsultation": "This service allows you to make an online virtual consultation via video call directly with the doctor from anywhere at any time.", + "myInvoice": "List", + "invoiceList": "My Invoice", + "allInvoices": "All Invoices", + "hospitals": "Hospitals", + "clinics": "Clinics", + "doctors": "Doctors", + "selectDoctor": "Select Doctor", + "thisItemIsNotAvailable": "This item is not available", + "beforeAfterImages": "Before & After Images", + "clinicAcceptLivecare": "No need to wait or visit You can now get medical consultation via Video call (LiveCare service) in The name of the clinic clinic and the doctor will contact you immediately", + "livecareModalTop": "This Clinic is Accepting LiveCare services", + "livecarePoint2": "A Specialized doctor will contact you", + "livecarePoint3": "A Doctor will be able to see your full medical file history", + "livecarePoint4": "Free Medicine Delivery Available", + "livecarePoint5": "No need to visit the service is in your place", + "bookImmediateLivecare": "I accept to get the service immediately", + "bookVideoLivecare2": "I don't need to visit the clinic", + "noThankyou": "No Thanks", + "visitClinic": "Schedule appointment with the doctor", + "generateCovidCertificate": "Generate Covid-19 Certificate", + "isReportOutsideKsa": "Is the certificate needed for outside KSA?", + "passportNumber": "Passport Number", + "enterPassportNumber": "Please confirm or update your passport number:", + "validPassportNumber": "Please enter valid passport number", + "continuePlan": "Continue with the treatment plan?", + "aboutApp": "About the app", + "aboutPoints": "Online Appointment Booking & rescheduling, Insurance approval status, Find A doctor, Ask your doctor, Medical prescriptions, Lab results, Hospitals contact numbers, Doctor profiles, Hospitals locations, Pharmacies Locations, Hospital's Virtual Tour, Official Social Media, Vaccines Schedule, Health Calculators, Other Services", + "termsConditions": "These Online Services Terms of Use (Service Terms) govern certain online services provided by Dr Sulaiman Al Habib Medical Services Group Company (HMG, we, us, our)...", + "receiveOtpToast": "Where would you like to receive OTP?", + "enterPhoneNumber": "Enter Phone Number", + "enterEmailDesc": "Enter your email address to complete the process of creating a medical file", + "enterPhoneDesc": "Enter your phone number to receive OTP verification code", + "pleaseChooseOption": "Please select from the below options to receive OTP", + "dontHaveAccount": "Don't have an account?", + "loginOrRegister": "Login or Register", + "myFiles": "My Files", + "resultsPending": "Results Pending", + "resultsAvailable": "Results Available", + "viewReport": "View Report", + "checkAvailability": "Check Availability", + "readInstructions": "Read Instructions", + "searchLabReport": "Search Lab Report", + "prescriptionDeliveryError": "This clinic doesn't support refill", + "prepareToElevate": "Prepared to elevate your health and well-being?", + "iAcceptTermsConditions": "I Accept the Terms and Conditions", + "alreadyHaveAccount": "Already have an account?", + "loginNow": "Login Now", + "notice": "Notice", + "oR": "OR", + "sendOTPWHATSAPP": "Send me OTP on Whatsapp", + "sendOTPSMS": "Send me OTP on SMS", + "fullName": "Full Name", + "married": "Married", + "uae": "United Arab Emirates", + "malE": "Male", + "loginBy": "Login By", + "loginByOTP": "Login By OTP", + "guest": "Guest", + "switchAccount": "Switch Account", + "allSet": "All Set! Now you can login with Face ID or Biometric", + "enableQuickLogin": "Enable Quick Login", + "enableMsg": "Enabling the quick login will verify through your existing device Face ID / Biometric", + "notNow": "Not Now", + "pendingActivation": "Pending Activation", + "awaitingApproval": "Awaiting Approval", + "enterValidNationalId": "Invalid Identification or file number", + "enterValidPhoneNumber": "Please enter a valid phone number", + "cannotEnterSaudiOrUAENumber": "You cannot enter Saudi Arabia (00966) or UAE (00971) phone numbers when 'Others' country is selected", + "ready": "Ready", + "medicalCentersWithCount": "{count} Medical Centers", + "medicalCenters": " Medical Centers", + "hospitalsWithCount": "{count} Hospitals", + "selectRegion": "Select Region", + "selectFacility": "Select Facilities", + "selectFacilitiesSubTitle": "Please select the facility for the appointment", + "selectHospitalSubTitle": "Please select the hospital for the appointment", + "news": "News", + "iAcceptThe": "I Accept the", + "personalDetailsVerification": "Personal Details Verification", + "otpVerification": "OTP Verification", + "weHaveSendOTP": "We have sent you the OTP code on", + "via": "via", + "forRegistrationVerification": "for registration verification", + "didntReceiveIt": "Didn't receive it?", + "resendOTP": "Resend", + "resendIn": "resend in", + "pleaseEnterAnationalID": "Please enter a national ID", + "pleaseEnterAFileNumber": "Please enter a file number", + "pleaseEnterAValidEmail": "Please enter a valid email", + "pleaseEnterFullName": "Please enter full name", + "pleaseAcceptTermsConditions": "Please accept the terms and conditions", + "pleaseEnterAValidIqamaID": "Please enter a valid Iqama ID", + "pleaseEnterAValidNationalID": "Please enter a valid national ID", + "pleaseEnterAValidDateOfBirth": "Please enter a valid date of birth", + "pleaseEnterAValidName": "Please enter a valid name", + "pleaseSelectAGender": "Please select a gender", + "pleaseSelectAMaritalStatus": "Please select a marital status", + "pleaseSelectACountry": "Please select a country", + "pleaseEnterEmail": "Please enter email", + "pleaseEnterAValidEmailFormat": "Please enter a valid email format", + "selectCountry": "Select Country", + "forLoginVerification": "for login verification", + "lastLoginBy": "Last login by", + "searchHospital": "Search Hospital", + "skip": "Skip", + "getStarted": "Get Started", + "onboardingHeading1": "Booking appointment has never been easy", + "onboardingBody1": "In few clicks find yourself having consultation with the doctor of your choice.", + "onboardingHeading2": "Access the medical history on finger tips", + "onboardingBody2": "Keep track on your medical history including labs, prescription, insurance, etc", + "normal": "Normal", + "attention": "Attention", + "monitor": "Monitor", + "noSpecialResult": "No Special Results", + "setTheDateRange": "Set The Date Range", + "historyFlowchart": "History FlowChart", + "to": "to", + "startDate": "Start Date", + "endDate": "End Date", + "hmgHospitals": "HMG Hospitals", + "hmcMedicalClinic": "HMC Medical Centers", + "applyFilter": "Apply Filter", + "facilityAndLocation": "Facility and Location", + "regionAndLocation": "Region And Locations", + "clearAllFilters": "Clear all filters", + "filters": "Filters", + "searchClinic": "Search Clinic", + "walkin": "Walk In", + "continueString": "Continue", + "laserClinic": "Laser Clinic", + "covid_info": "Dr. Sulaiman Al Habib hospitals are conducting a test for the emerging corona virus and issuing travel certificates 24/7 in a short time and with high accuracy. Those wishing to benefit from this service can visit one of Dr. Sulaiman Al Habib branches to conduct a corona test within few minutes, and obtain the result within several hours. Corona Virus Covid 19 testing service with PCR technology to detect the virus according to the highest international standards and with the latest high-precision RT-PCR devices (American GeneXpert and others), That is approved by the Food and Drug Authority as well as by the Saudi Center for Infectious Diseases Prevention.", + "appointmentDetails": "Appointment Details", + "checkingDoctorAvailability": "Checking doctor availability...", + "cancellingAppointmentPleaseWait": "Cancelling Appointment, Please Wait...", + "appointmentCancelledSuccessfully": "Appointment Cancelled Successfully", + "notConfirmed": "Not Confirmed", + "appointmentStatus": "Appointment Status", + "doctorWillCallYou": "The doctor will call you once the appointment time approaches.", + "getDirections": "Get Directions", + "notifyMeBeforeAppointment": "Notify me before the appointment", + "fetchingLabResults": "Fetching Lab Results...", + "fetchingRadiologyResults": "Fetching Radiology Results...", + "fetchingAppointmentPrescriptions": "Fetching Appointment Prescriptions...", + "noPrescriptionsForAppointment": "You don't have any prescriptions for this appointment.", + "amountBeforeTax": "Amount before tax", + "rebookAppointment": "Re-book Appointment", + "fetchingDoctorSchedulePleaseWait": "Fetching Doctor Schedule, Please Wait...", + "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.", + "appointmentCheckIn": "Appointment check in", + "insuranceExpiredOrInactive": "Insurance expired or inactive", + "totalAmountToPay": "Total amount to pay", + "vat15": "VAT 15%", + "liveCare": "LiveCare", + "recentVisits": "Recent Visits", + "favouriteDoctors": "Favourite Doctors", + "searchByClinic": "Search By Clinic", + "tapToSelectClinic": "Tap to select clinic", + "searchByDoctor": "Search By Doctor", + "tapToSelect": "Tap to select", + "searchByRegion": "Search By Region", + "centralRegion": "Central Region", + "immediateConsultation": "Immediate Consultation", + "scheduledConsultation": "Scheduled Consultation", + "pharmaLiveCare": "Pharma LiveCare", + "notSureHelpMeChooseClinic": "Not sure? help me choose a clinic!", + "mentionYourSymptomsAndFindDoctors": "Mention your symptoms and find the list of doctors accordingly", + "immediateService": "Immediate service", + "noNeedToWaitGetMedicalConsultation": "No need to wait, you will get medical consultation immediately via video call", + "noVisitRequired": "No visit required", + "doctorWillContact": "Doctor will contact", + "specialisedDoctorWillContactYou": "A specialised doctor will contact you and will be able to view your medical history", + "freeMedicineDelivery": "Free medicine delivery", + "offersFreeMedicineDelivery": "Offers free medicine delivery for the LiveCare appointment", + "dentalChiefComplaints": "Dental Chief Complaints", + "viewAvailableAppointments": "View available appointments", + "doctorProfile": "Doctor Profile", + "waitingAppointment": "Waiting Appointment", + "hospitalInformation": "Hospital Information", + "fetchingAppointmentShare": "Fetching Appointment Share...", + "bookingYourAppointment": "Booking your appointment...", + "selectLiveCareClinic": "LiveCare Clinic", + "checkingForExistingDentalPlan": "Checking for an existing dental plan, Please wait...", + "dentalTreatmentPlan": "Dental treatment plan", + "youHaveExistingTreatmentPlan": "You have an existing treatment plan: ", + "mins": "Mins", + "totalTimeRequired": "Total time required", + "wouldYouLikeToContinue": "Would you like to continue it?", + "chooseDoctor": "Choose Doctor", + "viewNearestAppos": "View nearest available appointments", + "noDoctorFound": "No Doctor found for selected criteria...", + "yesPleasImInAHurry": "Yes please, I am in a hurry", + "fetchingFeesPleaseWait": "Fetching fees, Please wait...", + "noThanksPhysicalVisit": "No, Thanks. I would like a physical visit", + "offline": "Offline", + "videoCall": "Video Call", + "liveVideoCallWithHMGDoctors": "Live Video Call with HMG Doctors", + "liveAudioCallWithHMGDoctors": "Live Audio Call with HMG Doctors", + "audioCall": "Audio Call", + "phoneCall": "Phone Call", + "livePhoneCallWithHMGDoctors": "Live Phone Call with HMG Doctors", + "reviewLiveCareRequest": "Review LiveCare Request", + "selectedLiveCareType": "Selected LiveCare Type", + "selectLiveCareCallType": "Select LiveCare call type", + "confirmingLiveCareRequest": "Confirming LiveCare request, Please wait...", + "unknownErrorOccurred": "Unknown error occurred...", + "liveCarePermissionsMessage": "LiveCare requires Camera, Microphone, Location & Notifications permissions to enable virtual consultation between patient & doctor, Please allow these to proceed.", + "liveCarePayment": "LiveCare Payment", + "mada": "Mada", + "visaOrMastercard": "Visa or Mastercard", + "tamara": "Tamara", + "fetchingApplePayDetails": "Fetching Apple Pay details, Please wait...", + "liveCarePendingRequest": "LiveCare Pending Request", + "callLiveCareSupport": "Call LiveCare Support", + "whatIsWaitingAppointment": "What is Waiting Appointment?", + "waitingAppointmentsFeature": "The waiting appointments feature allows you to book an appointment while you are inside the hospital building, and in case there is no available slot in the doctor's schedule.", + "appointmentWithDoctorConfirmed": "The appointment with the doctor is confirmed, but the time of entry is uncertain.", + "paymentWithinTenMinutes": "Note: You must have to pay within 10 minutes of booking, otherwise your appointment will be cancelled automatically", + "liveLocation": "Live Location", + "verifyYourLocationAtHospital": "Verify your location to be at hospital to check in", + "error": "Error", + "ensureWithinHospitalLocation": "Please ensure you're within the hospital location to perform online check-in.", + "nfcNearFieldCommunication": "NFC (Near Field Communication)", + "scanPhoneViaNFC": "Scan your phone via NFC board to check in", + "qrCode": "QR Code", + "scanQRCodeToCheckIn": "Scan QR code with your camera to check in", + "processingCheckIn": "Processing Check-In...", + "bookingWaitingAppointment": "Booking Waiting Appointment, Please wait...", + "enterValidIDorIqama": "Please enter a valid national ID or file number", + "selectAppointment": "Select Appointment", + "rebookSameDoctor": "Rebook with same doctor", + "queueing": "Queueing", + "inQueue": "In Queue", + "yourTurn": "Your Turn", + "halaFirstName": "Hala {firstName}!!!", + "thankYouForPatience": "Thank you for your patience, here is your queue number.", + "servingNow": "Serving Now", + "callForVitalSigns": "Call for vital signs", + "callForDoctor": "Call for Doctor", + "thingsToAskDoctor": "Things to ask your doctor today", + "improveOverallHealth": "What can I do to improve my overall health?", + "routineScreenings": "Are there any routine screenings I should get?", + "whatIsThisMedicationFor": "What is this medication for?", + "sideEffectsToKnow": "Are there any side effects I should know about?", + "whenFollowUp": "When should I come back for a follow-up?", + "goToHomepage": "Go to homepage", + "appointmentsList": "Appointments List", + "allAppt": "All Appt.", + "upcoming": "Upcoming", + "completed": "Completed", + "noAppointmentsYet": "You don't have any appointments yet.", + "viewProfile": "View Profile", + "choosePreferredHospitalForService": "Choose your preferred hospital for the service", + "noHospitalsFound": "No hospitals Found", + "cancelOrderConfirmation": "Are you sure you want to cancel this order?", + "orderCancelledSuccessfully": "Order has been cancelled successfully", + "requestID": "Request ID:", + "noCMCOrdersYet": "You don't have any CMC orders yet.", + "cmcOrders": "CMC Orders", + "summary": "Summary", + "selectedService": "Selected Service", + "requestSubmittedSuccessfully": "Your request has been successfully submitted.", + "hereIsYourRequestNumber": "Here is your request #: ", + "pleaseSelectHospitalToContinue": "Please select a hospital to continue", + "confirmSubmitRequest": "Are you sure you want to submit this request?", + "pendingOrderWait": "You have a pending order. Please wait for it to be processed.", + "noServicesAvailable": "No services available", + "selectAService": "Select a Service", + "comprehensiveCheckup": "Comprehensive Checkup", + "viewNearestHMGLocations": "View your nearest HMG locations", + "provideFeedbackOnServices": "Provide your feedback on our services", + "liveChatWithHMG": "Live chat option with HMG", + "send": "Send", + "status": "Status", + "sendingFeedback": "Sending Feedback...", + "selectFeedbackType": "Select Feedback Type", + "loadingAppointmentsList": "Loading appointments list...", + "noAppointmentsForFeedback": "You do not have any appointments to submit a feedback.", + "selectedAppointment": "Selected Appointment:", + "subject": "Subject", + "enterSubjectHere": "Enter subject here", + "message": "Message", + "enterMessageHere": "Enter message here", + "filesSelected": "{count} file(s) selected", + "otherDetails": "Other Details", + "medicalReport": "Medical Report", + "medicalReportNumber": "Medical Report {number}", + "patientIsInsured": "Patient is Insured", + "insuranceDocument": "Insurance Document", + "selectBranch": "Select Branch", + "patientInformation": "Patient information", + "patientLocation": "Where the patient located", + "identificationNumber": "Identification Number", + "enterIdentificationNumber": "Enter Identification Number*", + "patientName": "Patient Name*", + "referralRequesterInformation": "Referral requester information", + "enterReferralRequesterName": "Enter Referral Requester Name*", + "requesterName": "Requester Name", + "relationship": "Relationship", + "selectRelation": "Select Relation", + "otherName": "Other Name", + "otherNameHint": "Other Name*", + "requesterNameRequired": "Referral requester name is required", + "selectRelationshipRequired": "Please select a relationship", + "otherRelationshipNameRequired": "Other relationship name is required", + "identificationNumberRequired": "Identification number is required", + "patientNameRequired": "Patient name is required", + "enterPatientPhoneRequired": "Please Enter patient phone number", + "selectPatientCityRequired": "Please select patient city", + "medicalReportRequired": "At least one medical report is required", + "selectBranchRequired": "Please select a branch", + "insuranceDocumentRequired": "Insurance document is required for insured patients", + "searchResult": "Search Result", + "referralNo": "Referral No {number}", + "eReferral": "E Referral", + "referralCreatedSuccessfully": "Your Referral has been created Successfully.", + "hereIsYourReferralNumber": "Here is your Referral #: ", + "searchEReferral": "Search E-Referral", + "enterRequiredInfoToSearch": "Please enter the required information to search for an e-referral", + "selectPickupDirection": "Select Pickup Direction", + "selectDirection": "Select Direction", + "toHospital": "To Hospital", + "fromHospital": "From Hospital", + "selectWay": "Select Way", + "oneWay": "One Way", + "twoWay": "Two Way", + "selectPickupDetails": "Select Pickup Details", + "pleaseSelectDetailsOfPickup": " Please select the details of pickup", + "selectDetails": "Select Details", + "work": "Work", + "pick": "Pick", + "insideTheHome": "Inside the home", + "haveAnyAppointment": "Have any appointment", + "amountPaidAtHospital": "Amount will be paid at the hospital", + "submitRequest": "Submit Request", + "enterPickupLocationManually": "Enter Pickup Location Manually", + "enterPickupLocation": "Enter Pickup Location", + "trackingDetails": "Tracking Details", + "cancelRequest": "Cancel Request", + "shareLocationWhatsapp": "Share Your Live Location on Whatsapp", + "pleaseWaitForCall": "Please wait for the call", + "toHospitalLower": "to hospital", + "contact": "Contact", + "failed": "Failed", + "confirmationCall": "Confirmation Call", + "pickupFromHome": "Pickup Up from Home", + "onTheWayToHospital": " On The Way To Hospital", + "arrivedAtHospital": "Arrived at Hospital", + "orderCancel": "Order Cancel", + "emergencyCheckIn": "Emergency Check-In", + "erOnlineCheckInDescription": "This service lets patients register their ER appointment prior to arrival.", + "erOnlineCheckInSuccess": "Your ER Online Check-In has been successfully done. Please proceed to the waiting area.", + "erOnlineCheckInError": "Unexpected error occurred during check-in. Please contact support.", + "fetchingHospitalsList": "Fetching hospitals list...", + "fetchingPaymentInformation": "Fetching payment information...", + "erVisitDetails": "ER Visit Details", + "erClinic": "ER Clinic", + "vatWithAmount": "VAT 15% ({amount})", + "erAppointmentBookedSuccess": "Your appointment has been booked successfully. Please perform Check-In once you arrive at the hospital.", + "underProcessing": "Under processing", + "canceledByPatient": "Cancelled by patient", + "rapidResponseTeam": "Rapid Response Team", + "allFacilities": "All Facilities", + "selectLocation": "Select Location", + "pleaseSelectTheLocation": "Please select the location", + "viewLocationGoogleMaps": "View Location on Google Maps", + "callAmbulance": "Call Ambulance", + "requestAmbulanceInEmergency": "Request an ambulance in emergency from home or hospital", + "confirmation": "Confirmation", + "areYouSureYouWantToCallAmbulance": "Are you sure you want to call an ambulance?", + "getDetailsOfNearestBranch": "Get the details of nearest branch including directions", + "areYouSureYouWantToCallRRT": "Are you sure you want to call Rapid Response Team (RRT)?", + "priorERCheckInToSkipLine": "Prior ER Check-In to skip the line & payment at the reception.", + "areYouSureYouWantToMakeERCheckIn": "Are you sure you want to make ER Check-In?", + "checkingYourERAppointmentStatus": "Checking your ER Appointment status...", + "transportOptions": "Transport Options", + "selectHospitalForAdvancePayment": "Please select the hospital you want to make an advance payment for.", + "recharge": "Recharge", + "activityLevel": "Activity Level", + "selectActivityLevel": "Select Activity Level", + "caloriesPerDay": "Calories Per Day", + "dietType": "Diet Type", + "selectDietType": "Select Diet Type", + "bodyFrameSize": "Body Frame Size", + "selectBodyFrameSize": "Select Body Frame Size", + "averageCycleLength": "Average Cycle Length (Usually 28 days)", + "averageLutealPhase": "Average Luteal Phase Length(Usually 14 days)", + "convert": "Convert", + "calculate": "Calculate", + "healthCalculators": "Health Calculators", + "healthConverters": "Health Converters", + "generalHealth": "General Health", + "relatedToBMICalories": "Related To BMI, calories, body fat, etc to stay updated with your health.", + "selectCalculator": "Select Calculator", + "womensHealth": "Women's Health", + "relatedToPeriodsOvulation": "Related To periods, ovulation, pregnancy, and other topics.", + "bloodSugar": "Blood Sugar", + "trackYourGlucoseLevels": "Track your glucose levels, understand trends, and get personalized insights for better health.", + "bloodCholesterol": "Blood Cholesterol", + "monitorCholesterolLevels": "Monitor cholesterol levels, assess heart health risks, and take proactive steps for well-being.", + "triglyceridesFatBlood": "Triglycerides Fat Blood", + "understandTriglyceridesImpact": "Understand triglycerides' impact on heart health with personalized insights and expert recommendations.", + "bmiCalculator": "BMI\nCalculator", + "caloriesCalculator": "Calories\nCalculator", + "bmrCalculator": "BMR\nCalculator", + "idealBodyWeight": "Ideal Body\nWeight", + "bodyFatCalculator": "Body Fat\nCalculator", + "carbsProteinFat": "Carbs\nProtein & Fat", + "ovulationPeriod": "Ovulation\nPeriod", + "deliveryDueDate": "Delivery\nDue Date", + "low": "Low", + "preDiabetic": "Pre-diabetic", + "high": "High", + "elevated": "Elevated", + "recorded": "Recorded", + "noRecordsYet": "No records yet", + "lastRecord": "Last Record", + "addBloodSugar": "Add Blood Sugar", + "addBloodPressure": "Add Blood Pressure", + "addWeight": "Add Weight", + "bloodSugarDataSavedSuccessfully": "Blood Sugar Data saved successfully", + "bloodPressureDataSavedSuccessfully": "Blood Pressure Data saved successfully", + "weightDataSavedSuccessfully": "Weight Data saved successfully", + "pleaseWait": "Please wait", + "selectUnit": "Select Unit", + "selectMeasureTime": "Select Measure Time", + "selectArm": "Select Arm", + "enterBloodSugar": "Enter Blood Sugar", + "enterSystolicValue": "Enter Systolic Value", + "enterDiastolicValue": "Enter Diastolic Value", + "enterWeight": "Enter Weight", + "selectDuration": "Select Duration", + "systolic": "Systolic", + "diastolic": "Diastolic", + "sendReportByEmail": "Send Report by Email", + "enterYourEmailToReceiveReport": "Enter your email address to receive the report", + "addNewRecord": "Add new Record", + "healthTrackers": "Health Trackers", + "monitorBloodPressureLevels": "Monitor your blood pressure levels, track systolic and diastolic readings, and maintain a healthy heart.", + "trackWeightProgress": "Track your weight progress, set goals, and maintain a healthy body mass for overall wellness.", + "bookAppointment": "Book Appointment", + "completeCheckup": "Complete Checkup", + "indoorNavigation": "Indoor Navigation", + "eReferralServices": "E-Referral Services", + "dailyWaterMonitor": "Daily Water Monitor", + "fetchingYourWaterIntakeDetails": "Fetching your water intake details.", + "healthCalculatorsServices": "Health\nCalculators", + "healthConvertersServices": "Health\nConverters", + "smartWatchesServices": "Smart\nWatches", + "exploreServices": "Explore Services", + "medicalAndCareServices": "Medical & Care Services", + "hmgServices": "HMG Services", + "personalServices": "Personal Services", + "habibWallet": "Habib Wallet", + "loginToViewWalletBalance": "Login to view your wallet balance", + "loginToViewMedicalFile": "Login to view your medical file", + "addMember": "Add Member", + "addFamilyMember": "Add Family Member", + "pleaseFillBelowFieldToAddNewFamilyMember": "Please fill the below field to add a new family member to your profile", + "healthTools": "Health Tools", + "supportServices": "Support Services", + "virtualTour": "Virtual Tour", + "carParking": "Car Parking", + "latestNews": "Latest News", + "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. Please wait for your turn, The doctor will call you shortly.", + "dontHaveHHCOrders": "You don't have any Home Health Care orders yet.", + "hhcOrders": "HHC Orders", + "requestedServices": "Requested Services", + "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": "Recent 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 files", + "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", + "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": "Analysing your results, Please be patient and let the AI do the magic, This might take some time.", + "symptoms": "Symptoms", + "insuranceInActive": "Insurance Inactive", + "insuranceInActiveContactSupport": "You have a valid insurance but it is currently inactive in HMG. Please contact HMG customer support to activate it.", + "updateAppNow": "Update App Now", + "Dr": "Dr.", + "reschedulingAppo": "Rescheduling Appointment, Please wait...", + "invalidEligibility": "You cannot make online payment because you are not eligible to use the provided service.", + "invalidInsurance": "You cannot make online payment because you do not have a valid insurance.", + "continueCash": "Continue as cash", + "applewatch": "Apple Watch", + "applehealthapplicationshouldbeinstalledinyourphone": "Apple Health application should be installed in your phone", + "unabletodetectapplicationinstalledpleasecomebackonceinstalled": "We are unable to detect the application installed in your device. Please come back here once you have installed this application.", + "applewatchshouldbeconnected": "Apple Watch should be connected", + "samsungwatch": "Samsung Watch", + "samsunghealthapplicationshouldbeinstalledinyourphone": "Samsung Health application should be installed in your phone", + "samsungwatchshouldbeconnected": "Samsung Watch should be connected", + "huaweiwatch": "Huawei Watch", + "huaweihealthapplicationshouldbeinstalledinyourphone": "Huawei Health application should be installed in your phone", + "huaweiwatchshouldbeconnected": "Huawei Watch should be connected", + "whoopwatch": "Whoop Watch", + "whoophealthapplicationshouldbeinstalledinyourphone": "Whoop Health application should be installed in your phone", + "whoopwatchshouldbeconnected": "Whoop Watch should be connected", + "updatetheinformation": "This will allow to gather the most up to date information from your apple watch", + "timeFor": "Time For", + "hmgPolicies": "HMG Policies", + "featureComingSoonDescription": "Feature is coming soon. We are actively working to bring more exciting features into the app. Stay tuned for updates.", + "darkMode": "Dark Mode", + "generateAiAnalysisResult": "Generate AI analysis for this result", + "ratings": "Ratings", + "hmgPharmacyText": "Al Habib Pharmacy, the complete online Pharmaceutical store brought to you by Dr. Sulaiman Al Habib Medical Services Group.", + "insuranceRequestSubmittedSuccessfully": "Your insurance update request has been successfully submitted. You will be notified once completed.", + "updatingEmailAddress": "Updating email address, Please wait...", + "verifyInsurance": "Verify Insurance", + "tests": "tests", + "calendarPermissionAlert": "Please grant calendar access permission from app settings to set medication reminder.", + "timeForFirstReminder": "Time for 1st reminder", + "reminderRemovalNote": "You can remove it from your calendar later by switching off the reminder", + "sortByLocation": "Sort by location", + "sortByNearestLocation": "Sort by nearest to your location", + "giveLocationPermissionForNearestList": "Please grant location permission from app settings to see the nearest locations.", + "communicationLanguage": "Communication Language", + "cmcServiceHeader": "Complete Health Checkup: Advanced diagnostics, Detailed Health insights", + "cmcServiceDescription": "Get detailed insights into your health with our advanced diagnostics. Understand your body better for a healthier future.", + "eReferralServiceHeader": "HMG Hospital E-Referral: Streamlined patient referrals", + "eReferralServiceDescription": "Facilitate seamless patient transfers to HMG with our secure e-referral system. Ensure continuity of care for every patient.", + "bloodDonationServiceHeader": "Give Blood, Save Lives. Your donation makes a difference.", + "bloodDonationServiceDescription": "Donate blood, empower lives. Your contribution creates hope. Join our blood drive and be a lifeline for those in need. Every drop counts!", + "healthTrackersServiceHeader": "Track Your Vitals with Ease and effortlessly ", + "healthTrackersServiceDescription": "Input your metrics for continuous heart rate monitoring, blood pressure and precise daily activity summaries. Stay informed and optimize your well-being with ease.", + "waterConsumptionServiceHeader": "Stay Hydrated, Stay Healthy. Track your daily water intake with ease.", + "waterConsumptionServiceDescription": "Quench your thirst, track your health. Effortlessly monitor your daily water intake with our user-friendly app, ensuring optimal hydration and well-being.", + "smartWatchServiceHeader": "Sync Your Smartwatch with Health Apps", + "smartWatchServiceDescription": "Seamlessly connect your smartwatch to our health app for effortless data tracking and personalized insights.", + "liveChatServiceHeader": "24/7 Expert\nAssistance", + "liveChatServiceDescription": "Need help ? Our live chat connects you with expert support for any questions about features, settings, or troubleshooting.", + "emergencyServiceHeader": "ER Check-in, Faster Than Ever. Call ambulance / Rapid Response Team instantly", + "emergencyServiceDescription": "In a medical emergency? Our ambulances and rapid response teams are on standby 24/7. Plus, quick ER check-in for faster care.", + "homeHealthCareServiceHeader": "Your Health, Elevated. Premium care, delivered to your doorstep.", + "homeHealthCareServiceDescription": "We bring quality healthcare to your doorstep. Experienced nurses providing compassionate care in the comfort of your home.", + "profileOnlyText": "Profile", + "information": "Information", + "noFavouriteDoctors": "You don't have any favourite list yet", + "addDoctors": "Add Doctors", + "favouriteList": "Favourite List", + "later": "Later", + "cancelAppointmentConfirmMessage": "Are you sure you want to cancel this appointment?", + "acknowledged": "Acknowledged", + "searchLabResults": "Search lab results", + "callForAssistance": "Call for immediate assistance", + "oneWaySubtitle": "Pickup from location to hospital", + "twoWaySubtitle": "Round trip from location to hospital and back", + "toHospitalSubtitle": "Transfer from your current location to the hospital", + "fromHospitalSubtitle": "Transfer from the hospital back to your home", + "clinicSchedule": "Clinic Schedule", + "doctorAddedToFavourite": "Added to favorites list", + "doctorRemovedFromFavourite": "Removed from favorites list", + "favourites": "Favourites", + "addNewFavorite": "Add new favourite", + "pendingAncillaryOrders": "Pending ancillary orders", + "liveCarePermissions": "LiveCare requires Camera, Microphone & Notifications permissions to enable virtual consultation between patient & doctor, Open app settings to allow them.", + "containsImage": "Contains Image", + "servicesSelected": "Service(s) Selected", + "selectMedicalFileFromFamily": "Select a medical file from your family", + "anyFileFromHMG": "Any active medical file from HMG", + "noDistance": "Distance not available", + "enterAmount": "Please enter amount to continue.", + "selectHospitalContinue": "Please select hospital to continue.", + "year": "Year", + "notifyPrescription": "Notify me before the consumption time", + "doctorRatingAppointment": "Doctor\nRating", + "ratingSubmitted": "Rating Submitted", + "rateTheDoctor": "Please rate the doctor", + "downloadInvoice": "Download Invoice", + "approvalDetails": "Approval Details", + "obese": "Obese", + "overWeight": "OverWeight", + "underWeight": "UnderWeight", + "nearestGate": "Nearest Gate", + "RRTDDetails": "The Rapid response team provides a comprehensive medical service for all sorts of urgent and stable cases of all ages, including adult and paediatric patients.", + "normalMapType": "Default", + "satelliteMapType": "Satellite", + "terrainMapType": "Terrain", + "hybridMapType": "Hybrid", + "openCamera": "Open Camera", + "openGallery": "Open Gallery", + "openFiles": "Open Files", + "generateAiAnalysisRadResult": "Generate AI analysis for this result", + "grantLocationPermission": "Please grant location permission from app settings to see better results.", + "NoBookedAppointments": "No Booked Appointments", + "aiDisclaimerRad": "Your radiology result data will be securely shared with our AI Analyzer for analysis. This helps provide personalized health insights. Do you want to proceed?", + "imageSizeTooLarge": "Image size is too large. Maximum size is 1MB", + "profileImageUpdatedSuccessfully": "Profile image updated successfully", + "failedToProcessImage": "Failed to process image. Please try again", + "cameraGalleryPermissionMessage": "Camera and Gallery permissions are required to upload profile picture. Please grant permissions from app settings.", + "cameraGalleryPermissionRequired": "Camera and Gallery permissions are required", + "cameraPermissionMessage": "Camera permission is required to take photos. Please grant permission from app settings.", + "cameraPermissionRequired": "Camera permission is required", + "galleryPermissionMessage": "Gallery permission is required to select photos. Please grant permission from app settings.", + "galleryPermissionRequired": "Gallery permission is required", + "failedToCheckPermissions": "Failed to check permissions. Please try again", + "takePhotoWithCamera": "Take a photo with your camera", + "selectFromPhotoLibrary": "Select from your photo library", + "selectDocumentFiles": "Select document files", + "profilePicture": "Profile Picture", + "sendingAskDrRequest": "Sending Request...", + "askDrRequestSentSuccess": "Request has been sent successfully, The doctor will get back to you soon.", + "onlineCheckIn24HoursOnly": "Online Check-In is only available within 4 hours of the appointment time." +}; +static const Map> mapLocales = {"ar_SA": _ar_SA, "en_US": _en_US}; +} diff --git a/lib/generated/locale_keys.g.dart b/lib/generated/locale_keys.g.dart index e9f60f5c..1bd8019d 100644 --- a/lib/generated/locale_keys.g.dart +++ b/lib/generated/locale_keys.g.dart @@ -302,6 +302,12 @@ abstract class LocaleKeys { static const medicinesSubtitle = 'medicinesSubtitle'; static const vitalSigns = 'vitalSigns'; static const vitalSignsSubTitle = 'vitalSignsSubTitle'; + static const vitalSignNormal = 'vitalSignNormal'; + static const vitalSignLow = 'vitalSignLow'; + static const vitalSignHigh = 'vitalSignHigh'; + static const vitalSignObese = 'vitalSignObese'; + static const vitalSignOverweight = 'vitalSignOverweight'; + static const vitalSignUnderweight = 'vitalSignUnderweight'; static const myMedical = 'myMedical'; static const myMedicalSubtitle = 'myMedicalSubtitle'; static const myDoctor = 'myDoctor'; @@ -716,6 +722,11 @@ abstract class LocaleKeys { static const bloodDonation = 'bloodDonation'; static const bloodDonationInfo = 'bloodDonationInfo'; static const bloodInstruction = 'bloodInstruction'; + static const aboutBloodDonation = 'aboutBloodDonation'; + static const bloodDonationDescription = 'bloodDonationDescription'; + static const bloodDonationBenefit1 = 'bloodDonationBenefit1'; + static const bloodDonationBenefit2 = 'bloodDonationBenefit2'; + static const bloodDonationBenefit3 = 'bloodDonationBenefit3'; static const viewTerms = 'viewTerms'; static const wantConnectHmgNetwork = 'wantConnectHmgNetwork'; static const failedToAccessHmgServices = 'failedToAccessHmgServices'; @@ -1646,5 +1657,59 @@ abstract class LocaleKeys { static const twoWaySubtitle = 'twoWaySubtitle'; static const toHospitalSubtitle = 'toHospitalSubtitle'; static const fromHospitalSubtitle = 'fromHospitalSubtitle'; + static const clinicSchedule = 'clinicSchedule'; + static const doctorAddedToFavourite = 'doctorAddedToFavourite'; + static const doctorRemovedFromFavourite = 'doctorRemovedFromFavourite'; + static const addNewFavorite = 'addNewFavorite'; + static const favourites = 'favourites'; + static const pendingAncillaryOrders = 'pendingAncillaryOrders'; + static const liveCarePermissions = 'liveCarePermissions'; + static const containsImage = 'containsImage'; + static const servicesSelected = 'servicesSelected'; + static const selectMedicalFileFromFamily = 'selectMedicalFileFromFamily'; + static const anyFileFromHMG = 'anyFileFromHMG'; + static const noDistance = 'noDistance'; + static const enterAmount = 'enterAmount'; + static const selectHospitalContinue = 'selectHospitalContinue'; + static const year = 'year'; + static const notifyPrescription = 'notifyPrescription'; + static const doctorRatingAppointment = 'doctorRatingAppointment'; + static const ratingSubmitted = 'ratingSubmitted'; + static const rateTheDoctor = 'rateTheDoctor'; + static const downloadInvoice = 'downloadInvoice'; + static const approvalDetails = 'approvalDetails'; + static const obese = 'obese'; + static const overWeight = 'overWeight'; + static const underWeight = 'underWeight'; + static const nearestGate = 'nearestGate'; + static const RRTDDetails = 'RRTDDetails'; + static const normalMapType = 'normalMapType'; + static const satelliteMapType = 'satelliteMapType'; + static const terrainMapType = 'terrainMapType'; + static const hybridMapType = 'hybridMapType'; + static const openCamera = 'openCamera'; + static const openGallery = 'openGallery'; + static const openFiles = 'openFiles'; + static const generateAiAnalysisRadResult = 'generateAiAnalysisRadResult'; + static const grantLocationPermission = 'grantLocationPermission'; + static const NoBookedAppointments = 'NoBookedAppointments'; + static const aiDisclaimerRad = 'aiDisclaimerRad'; + static const imageSizeTooLarge = 'imageSizeTooLarge'; + static const profileImageUpdatedSuccessfully = 'profileImageUpdatedSuccessfully'; + static const failedToProcessImage = 'failedToProcessImage'; + static const cameraGalleryPermissionMessage = 'cameraGalleryPermissionMessage'; + static const cameraGalleryPermissionRequired = 'cameraGalleryPermissionRequired'; + static const cameraPermissionMessage = 'cameraPermissionMessage'; + static const cameraPermissionRequired = 'cameraPermissionRequired'; + static const galleryPermissionMessage = 'galleryPermissionMessage'; + static const galleryPermissionRequired = 'galleryPermissionRequired'; + static const failedToCheckPermissions = 'failedToCheckPermissions'; + static const takePhotoWithCamera = 'takePhotoWithCamera'; + static const selectFromPhotoLibrary = 'selectFromPhotoLibrary'; + static const selectDocumentFiles = 'selectDocumentFiles'; + static const profilePicture = 'profilePicture'; + static const sendingAskDrRequest = 'sendingAskDrRequest'; + static const askDrRequestSentSuccess = 'askDrRequestSentSuccess'; + static const onlineCheckIn24HoursOnly = 'onlineCheckIn24HoursOnly'; } diff --git a/lib/main.dart b/lib/main.dart index d38c9aab..e9b599c9 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -10,6 +10,7 @@ import 'package:flutter/services.dart'; import 'package:hmg_patient_app_new/core/app_state.dart'; import 'package:hmg_patient_app_new/core/dependencies.dart'; import 'package:hmg_patient_app_new/core/utils/utils.dart'; +import 'package:hmg_patient_app_new/theme/colors.dart'; import 'package:hmg_patient_app_new/features/ask_doctor/ask_doctor_view_model.dart'; import 'package:hmg_patient_app_new/features/authentication/authentication_view_model.dart'; import 'package:hmg_patient_app_new/features/blood_donation/blood_donation_view_model.dart'; @@ -41,6 +42,8 @@ import 'package:hmg_patient_app_new/features/services_price_list/services_price_ import 'package:hmg_patient_app_new/features/smartwatch_health_data/health_provider.dart'; import 'package:hmg_patient_app_new/features/symptoms_checker/symptoms_checker_view_model.dart'; import 'package:hmg_patient_app_new/features/todo_section/todo_section_view_model.dart'; +import 'package:hmg_patient_app_new/features/translations/custom_asset_loader.dart'; +import 'package:hmg_patient_app_new/features/translations/translation_service.dart'; import 'package:hmg_patient_app_new/features/water_monitor/water_monitor_view_model.dart'; import 'package:hmg_patient_app_new/presentation/health_calculators_and_converts/health_calculator_view_model.dart'; import 'package:hmg_patient_app_new/features/health_trackers/health_trackers_view_model.dart'; @@ -48,6 +51,7 @@ import 'package:hmg_patient_app_new/routes/app_routes.dart'; import 'package:hmg_patient_app_new/services/logger_service.dart'; import 'package:hmg_patient_app_new/services/navigation_service.dart'; import 'package:hmg_patient_app_new/theme/app_theme.dart'; +import 'package:hmg_patient_app_new/theme/colors.dart'; import 'package:hmg_patient_app_new/widgets/date_range_selector/viewmodel/date_range_view_model.dart' show DateRangeSelectorRangeViewModel; import 'package:provider/provider.dart'; import 'package:provider/single_child_widget.dart'; @@ -95,6 +99,9 @@ Future callInitializations() async { WidgetsFlutterBinding.ensureInitialized(); await EasyLocalization.ensureInitialized(); + // Download the latest JSONs from your server + // await TranslationService.downloadTranslations(); + // await Firebase.initializeApp(options: DefaultFirebaseOptions.currentPlatform); try { @@ -125,6 +132,7 @@ void main() async { Locale('ar', 'SA'), ], path: 'assets/langs', + // assetLoader: RemoteFileLoader(), fallbackLocale: Locale('en', 'US'), child: MultiProvider(providers: [ ChangeNotifierProvider( diff --git a/lib/presentation/active_medication/active_medication_page.dart b/lib/presentation/active_medication/active_medication_page.dart index ae46d402..a893d7c0 100644 --- a/lib/presentation/active_medication/active_medication_page.dart +++ b/lib/presentation/active_medication/active_medication_page.dart @@ -137,7 +137,7 @@ class _ActiveMedicationPageState extends State { style: TextStyle( color: AppColors.textColor, fontSize: 27.f, - fontWeight: FontWeight.w600)), + fontWeight: FontWeight.w600,)), SizedBox(height: 16.h), SizedBox( height: 65.h, @@ -163,7 +163,7 @@ class _ActiveMedicationPageState extends State { style: TextStyle( color: AppColors.textColor, fontSize: 16, - fontWeight: FontWeight.w500, + fontWeight: FontWeight.w600, ), ), WidgetSpan( @@ -174,7 +174,7 @@ class _ActiveMedicationPageState extends State { style: TextStyle( fontSize: 12, color: AppColors.textColor, - fontWeight: FontWeight.w500, + fontWeight: FontWeight.w600, ), ), ), @@ -184,7 +184,7 @@ class _ActiveMedicationPageState extends State { style: TextStyle( color: AppColors.textColor, fontSize: 16, - fontWeight: FontWeight.w500, + fontWeight: FontWeight.w600, ), ), ], @@ -194,7 +194,7 @@ class _ActiveMedicationPageState extends State { style: TextStyle( color: AppColors.primaryRedBorderColor, fontSize: 12.f, - fontWeight: FontWeight.w500)), + fontWeight: FontWeight.w600,)), SizedBox(height: 16.h), Expanded( child: SingleChildScrollView( @@ -630,7 +630,7 @@ class _ActiveMedicationPageState extends State { style: TextStyle( fontSize: 12.f, color: AppColors.greyTextColor, - fontWeight: FontWeight.w500, + fontWeight: FontWeight.w600, ), ), ], @@ -722,7 +722,7 @@ class _ActiveMedicationPageState extends State { ? AppColors.primaryRedBorderColor : AppColors.greyTextColor, fontSize: 11.f, - fontWeight: FontWeight.w500), + fontWeight: FontWeight.w600,), ), SizedBox(height: 5.h), Text("${date.day}", diff --git a/lib/presentation/appointments/appointment_details_page.dart b/lib/presentation/appointments/appointment_details_page.dart index dde08085..ef3b0c94 100644 --- a/lib/presentation/appointments/appointment_details_page.dart +++ b/lib/presentation/appointments/appointment_details_page.dart @@ -31,6 +31,7 @@ import 'package:hmg_patient_app_new/generated/locale_keys.g.dart'; import 'package:hmg_patient_app_new/presentation/appointments/appointment_payment_page.dart'; import 'package:hmg_patient_app_new/presentation/appointments/widgets/appointment_checkin_bottom_sheet.dart'; import 'package:hmg_patient_app_new/presentation/appointments/widgets/appointment_doctor_card.dart'; +import 'package:hmg_patient_app_new/presentation/appointments/widgets/appointment_rating_widget.dart'; import 'package:hmg_patient_app_new/presentation/appointments/widgets/ask_doctor_request_type_select.dart'; import 'package:hmg_patient_app_new/presentation/ask_doctor/ask_doctor_page.dart'; import 'package:hmg_patient_app_new/presentation/ask_doctor/doctor_response_page.dart'; @@ -71,16 +72,25 @@ class _AppointmentDetailsPageState extends State { String mapURL = ""; + bool isAppointmentWithin4Hours = false; + @override void initState() { scheduleMicrotask(() async { - if (!AppointmentType.isArrived(widget.patientAppointmentHistoryResponseModel)) { - // CalenderUtilsNew calendarUtils = await CalenderUtilsNew.instance; - // var doesExist = await calendarUtils.checkIfEventExist("${widget.patientAppointmentHistoryResponseModel.appointmentNo}"); - // myAppointmentsViewModel.setAppointmentReminder(doesExist, widget.patientAppointmentHistoryResponseModel); - // setState(() {}); + if (AppointmentType.isArrived(widget.patientAppointmentHistoryResponseModel)) { + myAppointmentsViewModel.getDoctorsRatingCheck( + doctorID: widget.patientAppointmentHistoryResponseModel.doctorID, + clinicID: widget.patientAppointmentHistoryResponseModel.clinicID, + projectID: widget.patientAppointmentHistoryResponseModel.projectID, + appointmentNo: widget.patientAppointmentHistoryResponseModel.appointmentNo); + } else { + Duration difference = DateUtil.convertStringToDate(widget.patientAppointmentHistoryResponseModel.appointmentDate).difference(DateTime.now()); + if (difference.inMinutes > 240) { + isAppointmentWithin4Hours = false; + } else { + isAppointmentWithin4Hours = true; + } } - }); super.initState(); } @@ -231,53 +241,53 @@ class _AppointmentDetailsPageState extends State { LocaleKeys.appointmentStatus.tr(context: context).toText16(isBold: true), SizedBox(height: 4.h), (!AppointmentType.isConfirmed(widget.patientAppointmentHistoryResponseModel) - ? LocaleKeys.notConfirmed.tr(context: context).toText12(color: AppColors.primaryRedColor, fontWeight: FontWeight.w500) - : LocaleKeys.confirmed.tr(context: context).toText12(color: AppColors.successColor, fontWeight: FontWeight.w500)), + ? LocaleKeys.notConfirmed.tr(context: context).toText12(color: AppColors.primaryRedColor, isBold: true) + : LocaleKeys.confirmed.tr(context: context).toText12(color: AppColors.successColor, isBold: true)), SizedBox(height: 16.h), ], ), - ((!AppointmentType.isConfirmed(widget.patientAppointmentHistoryResponseModel) && widget.patientAppointmentHistoryResponseModel.nextAction != 10) - ? CustomButton( - text: LocaleKeys.confirm.tr(), - onPressed: () async { - LoaderBottomSheet.showLoader(loadingText: LocaleKeys.confirmingAppointmentPleaseWait.tr(context: context)); - await myAppointmentsViewModel.confirmAppointment( - patientAppointmentHistoryResponseModel: widget.patientAppointmentHistoryResponseModel, - onSuccess: (apiResponse) { - LoaderBottomSheet.hideLoader(); - myAppointmentsViewModel.setIsAppointmentDataToBeLoaded(true); - myAppointmentsViewModel.initAppointmentsViewModel(); - // myAppointmentsViewModel.getPatientAppointments(true, false); - showCommonBottomSheetWithoutHeight( - title: "", - context, - child: Utils.getSuccessWidget(loadingText: LocaleKeys.appointmentConfirmedSuccessfully.tr(context: context)), - callBackFunc: () { - Navigator.pushAndRemoveUntil( - context, - CustomPageRoute( - page: LandingNavigation(), - ), - (r) => false); - }, - isFullScreen: false, - isCloseButtonVisible: false, - isAutoDismiss: true - ); - }); - }, - backgroundColor: AppColors.successColor, - borderColor: AppColors.successColor, - textColor: Colors.white, - fontSize: 14.f, - fontWeight: FontWeight.w500, - borderRadius: 12.r, - height: 40.h, - icon: AppAssets.confirm_appointment_icon, - iconColor: Colors.white, - iconSize: 16.h, - ) - : SizedBox.shrink()) + // ((!AppointmentType.isConfirmed(widget.patientAppointmentHistoryResponseModel) && widget.patientAppointmentHistoryResponseModel.nextAction != 10) + // ? CustomButton( + // text: LocaleKeys.confirm.tr(), + // onPressed: () async { + // LoaderBottomSheet.showLoader(loadingText: LocaleKeys.confirmingAppointmentPleaseWait.tr(context: context)); + // await myAppointmentsViewModel.confirmAppointment( + // patientAppointmentHistoryResponseModel: widget.patientAppointmentHistoryResponseModel, + // onSuccess: (apiResponse) { + // LoaderBottomSheet.hideLoader(); + // myAppointmentsViewModel.setIsAppointmentDataToBeLoaded(true); + // myAppointmentsViewModel.initAppointmentsViewModel(); + // // myAppointmentsViewModel.getPatientAppointments(true, false); + // showCommonBottomSheetWithoutHeight( + // title: "", + // context, + // child: Utils.getSuccessWidget(loadingText: LocaleKeys.appointmentConfirmedSuccessfully.tr(context: context)), + // callBackFunc: () { + // Navigator.pushAndRemoveUntil( + // context, + // CustomPageRoute( + // page: LandingNavigation(), + // ), + // (r) => false); + // }, + // isFullScreen: false, + // isCloseButtonVisible: false, + // isAutoDismiss: true + // ); + // }); + // }, + // backgroundColor: AppColors.successColor, + // borderColor: AppColors.successColor, + // textColor: Colors.white, + // fontSize: 14.f, + // isBold: true, + // borderRadius: 12.r, + // height: 40.h, + // icon: AppAssets.confirm_appointment_icon, + // iconColor: Colors.white, + // iconSize: 16.h, + // ) + // : SizedBox.shrink()) ], ), //TODO Add countdown timer in case of LiveCare Appointment @@ -290,7 +300,7 @@ class _AppointmentDetailsPageState extends State { child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ - LocaleKeys.doctorWillCallYou.tr(context: context).toText14(color: AppColors.greyTextColor, weight: FontWeight.w500), + LocaleKeys.doctorWillCallYou.tr(context: context).toText14(color: AppColors.greyTextColor, isBold: true), ], ), ), @@ -336,7 +346,7 @@ class _AppointmentDetailsPageState extends State { borderColor: AppointmentType.getNextActionButtonColor(widget.patientAppointmentHistoryResponseModel.nextAction).withValues(alpha: 0.01), textColor: Colors.white, fontSize: 14.f, - fontWeight: FontWeight.w500, + fontWeight: FontWeight.w600, borderRadius: 12.r, padding: EdgeInsets.symmetric(horizontal: 10.w), height: 40.h, @@ -357,12 +367,12 @@ class _AppointmentDetailsPageState extends State { AppCustomChipWidget( labelText: bookAppointmentsVM.isAppointmentNearestGateLoading ? "Floor: Ground Floor" - : "Floor: ${getIt.get().isArabic() ? bookAppointmentsViewModel.appointmentNearestGateResponseModel!.clinicLocationN : bookAppointmentsViewModel.appointmentNearestGateResponseModel!.clinicLocation}", - ).toShimmer2(isShow: bookAppointmentsVM.isAppointmentNearestGateLoading), + : "${LocaleKeys.floor.tr(context: context)}: ${getIt.get().isArabic() ? bookAppointmentsViewModel.appointmentNearestGateResponseModel!.clinicLocationN : bookAppointmentsViewModel.appointmentNearestGateResponseModel!.clinicLocation}", + ).toShimmer2(isShow: bookAppointmentsVM.isAppointmentNearestGateLoading), AppCustomChipWidget( labelText: - "Nearest Gate: ${getIt.get().isArabic() ? bookAppointmentsVM.appointmentNearestGateResponseModel!.nearestGateNumberN : bookAppointmentsVM.appointmentNearestGateResponseModel!.nearestGateNumber}") - .toShimmer2(isShow: bookAppointmentsVM.isAppointmentNearestGateLoading), + "${LocaleKeys.nearestGate.tr(context: context)}: ${getIt.get().isArabic() ? bookAppointmentsVM.appointmentNearestGateResponseModel!.nearestGateNumberN : bookAppointmentsVM.appointmentNearestGateResponseModel!.nearestGateNumber}") + .toShimmer2(isShow: bookAppointmentsVM.isAppointmentNearestGateLoading), ], ) : SizedBox.shrink(), ], @@ -386,13 +396,13 @@ class _AppointmentDetailsPageState extends State { crossAxisAlignment: CrossAxisAlignment.start, children: [ LocaleKeys.setReminder.tr(context: context).toText13(isBold: true), - LocaleKeys.notifyMeBeforeAppointment.tr(context: context).toText11(color: AppColors.textColorLight, weight: FontWeight.w500), + LocaleKeys.notifyMeBeforeAppointment.tr(context: context).toText11(color: AppColors.textColorLight, isBold: true), ], ), const Spacer(), Switch( - // activeThumbColor: AppColors.successColor, - activeTrackColor: AppColors.successColor.withValues(alpha: .15), + activeThumbColor: AppColors.successColor, + // activeTrackColor: AppColors.successColor.withValues(alpha: .15), value: widget.patientAppointmentHistoryResponseModel.hasReminder!, onChanged: (newValue) async { CalenderUtilsNew calender = CalenderUtilsNew.instance; @@ -451,15 +461,16 @@ class _AppointmentDetailsPageState extends State { ], ) // : SizedBox.shrink() - : GridView( - gridDelegate: SliverGridDelegateWithFixedCrossAxisCount( - crossAxisCount: 3, - crossAxisSpacing: 16.h, - mainAxisSpacing: 16.w, - mainAxisExtent: 115.h, - ), - physics: NeverScrollableScrollPhysics(), - padding: EdgeInsets.zero, + : Consumer(builder: (context, myAppointmentsVM, child) { + return GridView( + gridDelegate: SliverGridDelegateWithFixedCrossAxisCount( + crossAxisCount: 3, + crossAxisSpacing: 16.h, + mainAxisSpacing: 16.w, + mainAxisExtent: 115.h, + ), + physics: NeverScrollableScrollPhysics(), + padding: EdgeInsets.zero, shrinkWrap: true, children: [ MedicalFileCard( @@ -543,11 +554,15 @@ class _AppointmentDetailsPageState extends State { clinicDescription: widget.patientAppointmentHistoryResponseModel.clinicName, decimalDoctorRate: widget.patientAppointmentHistoryResponseModel.decimalDoctorRate, name: widget.patientAppointmentHistoryResponseModel.projectName, - isHomeMedicineDeliverySupported: false, + isHomeMedicineDeliverySupported: val.data.first.isHomeMedicineDeliverySupported ?? false, projectID: widget.patientAppointmentHistoryResponseModel.projectID, clinicID: widget.patientAppointmentHistoryResponseModel.clinicID, doctorID: widget.patientAppointmentHistoryResponseModel.doctorID, setupID: widget.patientAppointmentHistoryResponseModel.setupID, + isInOutPatient: widget.patientAppointmentHistoryResponseModel.isInOutPatient, + appointmentNo: widget.patientAppointmentHistoryResponseModel.appointmentNo, + episodeID: widget.patientAppointmentHistoryResponseModel.episodeID, + dischargeNo: 0, ); Navigator.of(context).push( CustomPageRoute( @@ -597,9 +612,55 @@ class _AppointmentDetailsPageState extends State { page: DoctorResponsePage(), ), ); - }), - ], - ), + }), + myAppointmentsVM.isAppointmentRatedResponseLoading + ? MedicalFileCard( + label: "Medical File", + textColor: AppColors.blackColor, + backgroundColor: AppColors.whiteColor, + svgIcon: AppAssets.appointmentRatingIcon, + isLargeText: true, + iconSize: 36.w, + isLocked: false, + ).toShimmer2() + : MedicalFileCard( + label: !((myAppointmentsVM.appointmentRatedResponseModel!).isAllowedToRate ?? false) + ? LocaleKeys.ratingSubmitted.tr(context: context) + : LocaleKeys.doctorRatingAppointment.tr(context: context), + textColor: AppColors.blackColor, + backgroundColor: AppColors.whiteColor, + svgIcon: AppAssets.appointmentRatingIcon, + isLargeText: true, + iconSize: 36.w, + isLocked: !(myAppointmentsVM.appointmentRatedResponseModel!.isAllowedToRate ?? false), + ).onPress(() { + if ((myAppointmentsVM.appointmentRatedResponseModel!.isAllowedToRate ?? true)) { + showCommonBottomSheetWithoutHeight( + context, + title: LocaleKeys.doctorRating.tr(context: context), + isCloseButtonVisible: true, + child: StatefulBuilder( + builder: (context, setState) { + return AppointmentRatingWidget( + patientAppointmentHistoryResponseModel: widget.patientAppointmentHistoryResponseModel, + ); + }, + ), + callBackFunc: () { + myAppointmentsViewModel.getDoctorsRatingCheck( + doctorID: widget.patientAppointmentHistoryResponseModel.doctorID, + clinicID: widget.patientAppointmentHistoryResponseModel.clinicID, + projectID: widget.patientAppointmentHistoryResponseModel.projectID, + appointmentNo: widget.patientAppointmentHistoryResponseModel.appointmentNo); + }, + isFullScreen: false, + ); + } + }), + ], + ); + }), + // Column( // crossAxisAlignment: CrossAxisAlignment.start, // children: [ @@ -686,7 +747,7 @@ class _AppointmentDetailsPageState extends State { // // "Prescribed By: ${widget.patientAppointmentHistoryResponseModel.doctorTitle} ${widget.patientAppointmentHistoryResponseModel.doctorNameObj}" // // .needTranslation // // .toText10( - // // weight: FontWeight.w500, + // // weight: FontWeight.w600, // // color: AppColors.greyTextColor, // // letterSpacing: -0.4), // // ], @@ -733,7 +794,7 @@ class _AppointmentDetailsPageState extends State { // // // borderColor: AppColors.secondaryLightRedColor, // // // textColor: AppColors.primaryRedColor, // // // fontSize: 14, - // // // fontWeight: FontWeight.w500, + // // // isBold: true, // // // borderRadius: 12.h, // // // height: 40.h, // // // icon: AppAssets.appointment_calendar_icon, @@ -761,7 +822,7 @@ class _AppointmentDetailsPageState extends State { // // borderColor: AppColors.secondaryLightRedColor, // // textColor: AppColors.primaryRedColor, // // fontSize: 14.f, - // // fontWeight: FontWeight.w500, + // // isBold: true, // // borderRadius: 12.r, // // height: 40.h, // // icon: AppAssets.requests, @@ -790,7 +851,7 @@ class _AppointmentDetailsPageState extends State { // // borderColor: AppColors.secondaryLightRedColor, // // textColor: AppColors.primaryRedColor, // // fontSize: 14.f, - // // fontWeight: FontWeight.w500, + // // isBold: true, // // borderRadius: 12.r, // // height: 40.h, // // icon: AppAssets.requests, @@ -843,8 +904,7 @@ class _AppointmentDetailsPageState extends State { children: [ Expanded( child: LocaleKeys.upcomingPaymentNow - .tr(context: context) - .toText12(fontWeight: FontWeight.w500, color: AppColors.greyTextColor)), + .tr(context: context).toText12(isBold: true, color: AppColors.greyTextColor)), "VAT 15%(${widget.patientAppointmentHistoryResponseModel.patientTaxAmount})".toText14(isBold: true, color: AppColors.greyTextColor, letterSpacing: -0.64), ], ), @@ -871,11 +931,12 @@ class _AppointmentDetailsPageState extends State { ], ).paddingOnly(left: 16.h, top: 24.h, right: 16.h, bottom: 0.h), AppointmentType.isArrived(widget.patientAppointmentHistoryResponseModel) - ? - (widget.patientAppointmentHistoryResponseModel.isLiveCareAppointment! || - widget.patientAppointmentHistoryResponseModel.isExecludeDoctor! || - Utils.isClinicAllowedForRebook(widget.patientAppointmentHistoryResponseModel.clinicID)) - ? CustomButton( + ? (!(widget.patientAppointmentHistoryResponseModel.isActiveDoctor ?? true) || + widget.patientAppointmentHistoryResponseModel.isLiveCareAppointment! || + widget.patientAppointmentHistoryResponseModel.isExecludeDoctor! || + !Utils.isClinicAllowedForRebook(widget.patientAppointmentHistoryResponseModel.clinicID)) + ? SizedBox.shrink() + : CustomButton( text: LocaleKeys.rebookAppointment.tr(context: context), onPressed: () { openDoctorScheduleCalendar(); @@ -884,15 +945,14 @@ class _AppointmentDetailsPageState extends State { borderColor: AppColors.primaryRedColor, textColor: Colors.white, fontSize: 16.f, - fontWeight: FontWeight.w500, - borderRadius: 12.r, + fontWeight: FontWeight.w600, + borderRadius: 12.r, padding: EdgeInsets.symmetric(horizontal: 10.w), height: 50.h, icon: AppAssets.add_icon, iconColor: Colors.white, iconSize: 18.h, ).paddingSymmetrical(16.h, 24.h) - : SizedBox.shrink() : CustomButton( text: AppointmentType.getNextActionText(widget.patientAppointmentHistoryResponseModel.nextAction), onPressed: () { @@ -903,7 +963,7 @@ class _AppointmentDetailsPageState extends State { AppointmentType.getNextActionButtonColor(widget.patientAppointmentHistoryResponseModel.nextAction).withOpacity(0.01), textColor: widget.patientAppointmentHistoryResponseModel.nextAction == 15 ? AppColors.textColor : Colors.white, fontSize: 16.f, - fontWeight: FontWeight.w500, + fontWeight: FontWeight.w600, borderRadius: 12.r, padding: EdgeInsets.symmetric(horizontal: 10.w), height: 56.h, @@ -933,7 +993,7 @@ class _AppointmentDetailsPageState extends State { SizedBox(height: 4.h), LocaleKeys.confirmYourLocationDesc.tr(context: context).toText12( color: AppColors.greyTextColor, - fontWeight: FontWeight.w500, + isBold: true, ), SizedBox(height: 24.h), CustomButton( @@ -947,7 +1007,7 @@ class _AppointmentDetailsPageState extends State { borderColor: AppColors.primaryRedColor, textColor: Colors.white, fontSize: 16.f, - fontWeight: FontWeight.w500, + fontWeight: FontWeight.w600, borderRadius: 12.r, // padding: EdgeInsets.fromLTRB(10, 0, 10, 0), height: 56.h, @@ -973,7 +1033,7 @@ class _AppointmentDetailsPageState extends State { borderColor: AppColors.chipSecondaryLightRedColor, textColor: AppColors.primaryRedColor, fontSize: 16.f, - fontWeight: FontWeight.w500, + fontWeight: FontWeight.w600, borderRadius: 12.r, // padding: EdgeInsets.fromLTRB(10, 0, 10, 0), height: 56.h, @@ -1086,10 +1146,14 @@ class _AppointmentDetailsPageState extends State { case 90: showCommonBottomSheetWithoutHeight(context, title: LocaleKeys.onlineCheckIn.tr(context: context), - child: AppointmentCheckinBottomSheet( - patientAppointmentHistoryResponseModel: widget.patientAppointmentHistoryResponseModel, - myAppointmentsViewModel: myAppointmentsViewModel, - ), + child: isAppointmentWithin4Hours + ? AppointmentCheckinBottomSheet( + patientAppointmentHistoryResponseModel: widget.patientAppointmentHistoryResponseModel, + myAppointmentsViewModel: myAppointmentsViewModel, + ) + : Utils.getOnlineCheckInWidget( + loadingText: LocaleKeys.onlineCheckIn24HoursOnly.tr(context: context), + ), callBackFunc: () {}, isFullScreen: false); default: diff --git a/lib/presentation/appointments/appointment_payment_page.dart b/lib/presentation/appointments/appointment_payment_page.dart index e755cfeb..54d7991a 100644 --- a/lib/presentation/appointments/appointment_payment_page.dart +++ b/lib/presentation/appointments/appointment_payment_page.dart @@ -111,7 +111,7 @@ class _AppointmentPaymentPageState extends State { Image.asset(AppAssets.mada, width: 72.h, height: 25.h) .toShimmer2(isShow: myAppointmentsVM.isAppointmentPatientShareLoading), SizedBox(height: 16.h), - "Mada".toText16(isBold: true).toShimmer2(isShow: myAppointmentsVM.isAppointmentPatientShareLoading), + LocaleKeys.mada.tr(context: context).toText16(isBold: true).toShimmer2(isShow: myAppointmentsVM.isAppointmentPatientShareLoading), ], ), SizedBox(width: 8.h), @@ -153,7 +153,7 @@ class _AppointmentPaymentPageState extends State { ], ).toShimmer2(isShow: myAppointmentsVM.isAppointmentPatientShareLoading), SizedBox(height: 16.h), - "Visa or Mastercard" + LocaleKeys.visaOrMastercard.tr(context: context) .toText16(isBold: true) .toShimmer2(isShow: myAppointmentsVM.isAppointmentPatientShareLoading), ], @@ -193,7 +193,7 @@ class _AppointmentPaymentPageState extends State { Image.asset(AppAssets.tamaraEng, width: 72.h, height: 25.h) .toShimmer2(isShow: myAppointmentsVM.isAppointmentPatientShareLoading), SizedBox(height: 16.h), - "Tamara" + LocaleKeys.tamara.tr(context: context) .toText16(isBold: true) .toShimmer2(isShow: myAppointmentsVM.isAppointmentPatientShareLoading), ], @@ -249,7 +249,7 @@ class _AppointmentPaymentPageState extends State { mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ LocaleKeys.insuranceExpiredOrInactive.tr(context: context) - .toText14(color: AppColors.primaryRedColor, weight: FontWeight.w500) + .toText14(color: AppColors.primaryRedColor, isBold: true) .paddingSymmetrical(24.h, 0.h), CustomButton( text: LocaleKeys.updateInsurance.tr(context: context), @@ -265,7 +265,7 @@ class _AppointmentPaymentPageState extends State { borderColor: AppColors.secondaryLightRedBorderColor, textColor: AppColors.whiteColor, fontSize: 10.f, - fontWeight: FontWeight.w500, + fontWeight: FontWeight.w600, borderRadius: 8, padding: EdgeInsets.fromLTRB(15, 0, 15, 0), height: 30.h, diff --git a/lib/presentation/appointments/appointment_queue_page.dart b/lib/presentation/appointments/appointment_queue_page.dart index f205aeb9..59522149 100644 --- a/lib/presentation/appointments/appointment_queue_page.dart +++ b/lib/presentation/appointments/appointment_queue_page.dart @@ -15,6 +15,7 @@ import 'package:hmg_patient_app_new/widgets/appbar/collapsing_list_view.dart'; import 'package:hmg_patient_app_new/widgets/buttons/custom_button.dart'; import 'package:hmg_patient_app_new/widgets/chip/app_custom_chip_widget.dart'; import 'package:hmg_patient_app_new/widgets/routes/custom_page_route.dart'; +import 'package:lottie/lottie.dart'; import 'package:provider/provider.dart'; class AppointmentQueuePage extends StatelessWidget { @@ -32,182 +33,177 @@ class AppointmentQueuePage extends StatelessWidget { children: [ Expanded( child: CollapsingListView( - title: LocaleKeys.queueing.tr(context: context), - child: SingleChildScrollView( - child: Padding( - padding: EdgeInsets.all(24.0), - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Container( - decoration: RoundedRectangleBorder().toSmoothCornerDecoration( - color: AppColors.whiteColor, - borderRadius: 20.h, - hasShadow: false, - side: BorderSide( - color: myAppointmentsVM.isAppointmentQueueDetailsLoading - ? AppColors.whiteColor - : Utils.getCardBorderColor(myAppointmentsVM.currentQueueStatus), - width: 2.w), - ), - child: Padding( - padding: EdgeInsets.all(16.h), - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Row( - mainAxisAlignment: MainAxisAlignment.spaceBetween, + title: LocaleKeys.queueing.tr(context: context), + child: RefreshIndicator( + color: AppColors.primaryRedColor, + onRefresh: () async { + await myAppointmentsVM.getPatientAppointmentQueueDetails(); + }, + child: SingleChildScrollView( + physics: const AlwaysScrollableScrollPhysics(), + child: Padding( + padding: EdgeInsets.all(24.0), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Container( + decoration: RoundedRectangleBorder().toSmoothCornerDecoration( + color: AppColors.whiteColor, + borderRadius: 20.h, + hasShadow: false, + side: BorderSide( + color: myAppointmentsVM.isAppointmentQueueDetailsLoading ? AppColors.whiteColor : Utils.getCardBorderColor(myAppointmentsVM.currentQueueStatus), width: 2.w), + ), + child: Padding( + padding: EdgeInsets.all(16.h), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, children: [ - AppCustomChipWidget( - labelText: myAppointmentsVM.currentQueueStatus == 0 ? LocaleKeys.inQueue.tr(context: context) : LocaleKeys.yourTurn.tr(context: context), - backgroundColor: Utils.getCardBorderColor(myAppointmentsVM.currentQueueStatus).withValues(alpha: 0.20), - textColor: Utils.getCardBorderColor(myAppointmentsVM.currentQueueStatus), - ), - Utils.buildSvgWithAssets(icon: AppAssets.waiting_icon, width: 24.h, height: 24.h), + Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + AppCustomChipWidget( + labelText: myAppointmentsVM.currentQueueStatus == 0 ? LocaleKeys.inQueue.tr(context: context) : LocaleKeys.yourTurn.tr(context: context), + backgroundColor: Utils.getCardBorderColor(myAppointmentsVM.currentQueueStatus).withValues(alpha: 0.20), + textColor: Utils.getCardBorderColor(myAppointmentsVM.currentQueueStatus), + ).toShimmer2(isShow: myAppointmentsVM.patientQueueDetailsList.isEmpty), + Container( + decoration: RoundedRectangleBorder().toSmoothCornerDecoration( + color: AppColors.greyColor, + borderRadius: 10.h, + hasShadow: false, + ), + padding: EdgeInsets.all(6.h), + child: Lottie.asset(AppAnimations.hourGlass, repeat: true, reverse: false, frameRate: FrameRate(60), width: 40.h, height: 40.h, fit: BoxFit.fill)), + ], + ).toShimmer2(isShow: myAppointmentsVM.isAppointmentQueueDetailsLoading), + SizedBox(height: 10.h), + "Hala ${appState!.getAuthenticatedUser()!.firstName}!!!".toText16(isBold: true).toShimmer2(isShow: myAppointmentsVM.isAppointmentQueueDetailsLoading), + SizedBox(height: 8.h), + LocaleKeys.thankYouForPatience + .tr(context: context) + .toText12(isBold: true, color: AppColors.textColorLight) + .toShimmer2(isShow: myAppointmentsVM.isAppointmentQueueDetailsLoading), + SizedBox(height: 8.h), + (myAppointmentsVM.currentPatientQueueDetails.queueNo ?? "IM-123") + .toText32(isBold: true) + .toShimmer2(isShow: myAppointmentsVM.isAppointmentQueueDetailsLoading || myAppointmentsVM.patientQueueDetailsList.isEmpty), + SizedBox(height: 8.h), + CustomButton( + text: Utils.getCardButtonText(myAppointmentsVM.currentQueueStatus, myAppointmentsVM.currentPatientQueueDetails.roomNo ?? ""), + onPressed: () {}, + backgroundColor: Utils.getCardButtonColor(myAppointmentsVM.currentQueueStatus), + borderColor: Utils.getCardButtonColor(myAppointmentsVM.currentQueueStatus).withValues(alpha: 0.01), + textColor: Utils.getCardButtonTextColor(myAppointmentsVM.currentQueueStatus), + fontSize: 12.f, + fontWeight: FontWeight.w600, + borderRadius: 12.r, + padding: EdgeInsets.symmetric(horizontal: 10.w), + height: 40.h, + iconColor: AppColors.whiteColor, + iconSize: 18.h, + ).toShimmer2(isShow: myAppointmentsVM.isAppointmentQueueDetailsLoading || myAppointmentsVM.patientQueueDetailsList.isEmpty), ], - ).toShimmer2(isShow: myAppointmentsVM.isAppointmentQueueDetailsLoading), - SizedBox(height: 10.h), - "Hala ${appState!.getAuthenticatedUser()!.firstName}!!!" - .toText16(isBold: true) - .toShimmer2(isShow: myAppointmentsVM.isAppointmentQueueDetailsLoading), - SizedBox(height: 8.h), - LocaleKeys.thankYouForPatience.tr(context: context) - .toText12(fontWeight: FontWeight.w500, color: AppColors.textColorLight) - .toShimmer2(isShow: myAppointmentsVM.isAppointmentQueueDetailsLoading), - SizedBox(height: 8.h), - myAppointmentsVM.currentPatientQueueDetails.queueNo! - .toText32(isBold: true) - .toShimmer2(isShow: myAppointmentsVM.isAppointmentQueueDetailsLoading), - SizedBox(height: 8.h), - CustomButton( - text: Utils.getCardButtonText( - myAppointmentsVM.currentQueueStatus, myAppointmentsVM.currentPatientQueueDetails.roomNo ?? ""), - onPressed: () {}, - backgroundColor: Utils.getCardButtonColor(myAppointmentsVM.currentQueueStatus), - borderColor: Utils.getCardButtonColor(myAppointmentsVM.currentQueueStatus).withValues(alpha: 0.01), - textColor: Utils.getCardButtonTextColor(myAppointmentsVM.currentQueueStatus), - fontSize: 12.f, - fontWeight: FontWeight.w600, - borderRadius: 12.r, - padding: EdgeInsets.symmetric(horizontal: 10.w), - height: 40.h, - iconColor: AppColors.whiteColor, - iconSize: 18.h, - ).toShimmer2(isShow: myAppointmentsVM.isAppointmentQueueDetailsLoading), - ], - ), - ), - ), - SizedBox(height: 16.h), - myAppointmentsVM.patientQueueDetailsList.isNotEmpty - ? Container( - decoration: RoundedRectangleBorder().toSmoothCornerDecoration( - color: AppColors.whiteColor, - borderRadius: 20.h, - hasShadow: true, ), - child: Padding( - padding: EdgeInsets.all(16.h), - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - LocaleKeys.servingNow.tr(context: context) - .toText16(isBold: true) - .toShimmer2(isShow: myAppointmentsVM.isAppointmentQueueDetailsLoading), - SizedBox(height: 18.h), - ListView.separated( - padding: EdgeInsets.zero, - shrinkWrap: true, - itemCount: myAppointmentsVM.patientQueueDetailsList.length, - physics: NeverScrollableScrollPhysics(), - itemBuilder: (BuildContext context, int index) { - return Row( - mainAxisAlignment: MainAxisAlignment.spaceBetween, - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - myAppointmentsVM.patientQueueDetailsList[index].queueNo!.toText17(isBold: true), - Row( - crossAxisAlignment: CrossAxisAlignment.center, + ), + ), + SizedBox(height: 16.h), + myAppointmentsVM.patientQueueDetailsList.isNotEmpty + ? Container( + decoration: RoundedRectangleBorder().toSmoothCornerDecoration( + color: AppColors.whiteColor, + borderRadius: 20.h, + hasShadow: true, + ), + child: Padding( + padding: EdgeInsets.all(16.h), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + LocaleKeys.servingNow.tr(context: context).toText16(isBold: true).toShimmer2(isShow: myAppointmentsVM.isAppointmentQueueDetailsLoading), + SizedBox(height: 18.h), + ListView.separated( + padding: EdgeInsets.zero, + shrinkWrap: true, + itemCount: myAppointmentsVM.patientQueueDetailsList.length, + physics: NeverScrollableScrollPhysics(), + itemBuilder: (BuildContext context, int index) { + return Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + crossAxisAlignment: CrossAxisAlignment.start, children: [ - "Room: ${myAppointmentsVM.patientQueueDetailsList[index].roomNo}" - .toText12(fontWeight: FontWeight.w500), - SizedBox(width: 8.w), - AppCustomChipWidget( - deleteIcon: myAppointmentsVM.patientQueueDetailsList[index].callType == 1 - ? AppAssets.call_for_vitals - : AppAssets.call_for_doctor, - labelText: myAppointmentsVM.patientQueueDetailsList[index].callType == 1 - ? LocaleKeys.callForVitalSigns.tr(context: context) - : LocaleKeys.callForDoctor.tr(context: context), - iconColor: myAppointmentsVM.patientQueueDetailsList[index].callType == 1 - ? AppColors.primaryRedColor - : AppColors.successColor, - textColor: myAppointmentsVM.patientQueueDetailsList[index].callType == 1 - ? AppColors.primaryRedColor - : AppColors.successColor, - iconSize: 14.w, - backgroundColor: myAppointmentsVM.patientQueueDetailsList[index].callType == 1 - ? AppColors.primaryRedColor.withValues(alpha: 0.1) - : AppColors.successColor.withValues(alpha: 0.1), - labelPadding: EdgeInsetsDirectional.only(start: 8.h, end: -2.h), + myAppointmentsVM.patientQueueDetailsList[index].queueNo!.toText17(isBold: true), + Row( + crossAxisAlignment: CrossAxisAlignment.center, + children: [ + "Room: ${myAppointmentsVM.patientQueueDetailsList[index].roomNo}".toText12(isBold: true), + SizedBox(width: 8.w), + AppCustomChipWidget( + deleteIcon: myAppointmentsVM.patientQueueDetailsList[index].callType == 1 ? AppAssets.call_for_vitals : AppAssets.call_for_doctor, + labelText: myAppointmentsVM.patientQueueDetailsList[index].callType == 1 + ? LocaleKeys.callForVitalSigns.tr(context: context) + : LocaleKeys.callForDoctor.tr(context: context), + iconColor: myAppointmentsVM.patientQueueDetailsList[index].callType == 1 ? AppColors.primaryRedColor : AppColors.successColor, + textColor: myAppointmentsVM.patientQueueDetailsList[index].callType == 1 ? AppColors.primaryRedColor : AppColors.successColor, + iconSize: 14.w, + backgroundColor: myAppointmentsVM.patientQueueDetailsList[index].callType == 1 + ? AppColors.primaryRedColor.withValues(alpha: 0.1) + : AppColors.successColor.withValues(alpha: 0.1), + labelPadding: EdgeInsetsDirectional.only(start: 8.h, end: -2.h), + ), + ], ), ], - ), - ], - ); - }, - separatorBuilder: (BuildContext cxt, int index) => SizedBox(height: 8.h), - ).toShimmer2(isShow: myAppointmentsVM.isAppointmentQueueDetailsLoading), - ], - ), - ), - ) - : SizedBox.shrink(), - SizedBox(height: 16.h), - Container( - decoration: RoundedRectangleBorder().toSmoothCornerDecoration( - color: AppColors.whiteColor, - borderRadius: 24.4, - hasShadow: true, - ), - child: Padding( - padding: EdgeInsets.all(16.h), - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Row( + ); + }, + separatorBuilder: (BuildContext cxt, int index) => SizedBox(height: 8.h), + ).toShimmer2(isShow: myAppointmentsVM.isAppointmentQueueDetailsLoading), + ], + ), + ), + ) + : SizedBox.shrink(), + SizedBox(height: 16.h), + Container( + decoration: RoundedRectangleBorder().toSmoothCornerDecoration( + color: AppColors.whiteColor, + borderRadius: 24.4, + hasShadow: true, + ), + child: Padding( + padding: EdgeInsets.all(16.h), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, children: [ - Utils.buildSvgWithAssets(icon: AppAssets.bulb_icon, width: 24.w, height: 24.h), - SizedBox(width: 8.w), - LocaleKeys.thingsToAskDoctor.tr(context: context).toText16(isBold: true), + Row( + children: [ + Utils.buildSvgWithAssets(icon: AppAssets.bulb_icon, width: 24.w, height: 24.h), + SizedBox(width: 8.w), + LocaleKeys.thingsToAskDoctor.tr(context: context).toText16(isBold: true), + ], + ), + SizedBox(height: 8.h), + "• ${LocaleKeys.improveOverallHealth.tr(context: context)}".toText12(isBold: true, color: AppColors.textColorLight), + SizedBox(height: 4.h), + "• ${LocaleKeys.routineScreenings.tr(context: context)}".toText12(isBold: true, color: AppColors.textColorLight), + SizedBox(height: 4.h), + "• ${LocaleKeys.whatIsThisMedicationFor.tr(context: context)}".toText12(isBold: true, color: AppColors.textColorLight), + SizedBox(height: 4.h), + "• ${LocaleKeys.sideEffectsToKnow.tr(context: context)}".toText12(isBold: true, color: AppColors.textColorLight), + SizedBox(height: 4.h), + "• ${LocaleKeys.whenFollowUp.tr(context: context)}".toText12(isBold: true, color: AppColors.textColorLight), + SizedBox(height: 16.h), ], - ), - SizedBox(height: 8.h), - "• ${LocaleKeys.improveOverallHealth.tr(context: context)}" - .toText12(fontWeight: FontWeight.w500, color: AppColors.textColorLight), - SizedBox(height: 4.h), - "• ${LocaleKeys.routineScreenings.tr(context: context)}" - .toText12(fontWeight: FontWeight.w500, color: AppColors.textColorLight), - SizedBox(height: 4.h), - "• ${LocaleKeys.whatIsThisMedicationFor.tr(context: context)}" - .toText12(fontWeight: FontWeight.w500, color: AppColors.textColorLight), - SizedBox(height: 4.h), - "• ${LocaleKeys.sideEffectsToKnow.tr(context: context)}" - .toText12(fontWeight: FontWeight.w500, color: AppColors.textColorLight), - SizedBox(height: 4.h), - "• ${LocaleKeys.whenFollowUp.tr(context: context)}" - .toText12(fontWeight: FontWeight.w500, color: AppColors.textColorLight), - - SizedBox(height: 16.h), - ], - ).toShimmer2(isShow: myAppointmentsVM.isAppointmentQueueDetailsLoading), - ), + ).toShimmer2(isShow: myAppointmentsVM.isAppointmentQueueDetailsLoading), + ), + ), + ], ), - ], + ), ), ), ), - ), ), Container( decoration: RoundedRectangleBorder().toSmoothCornerDecoration( @@ -229,7 +225,7 @@ class AppointmentQueuePage extends StatelessWidget { borderColor: AppColors.primaryRedColor, textColor: AppColors.whiteColor, fontSize: 16.f, - fontWeight: FontWeight.w500, + fontWeight: FontWeight.w600, borderRadius: 12.r, padding: EdgeInsets.symmetric(horizontal: 10.w), height: 50.h, diff --git a/lib/presentation/appointments/my_appointments_page.dart b/lib/presentation/appointments/my_appointments_page.dart index 71bf30bf..1f54e3d1 100644 --- a/lib/presentation/appointments/my_appointments_page.dart +++ b/lib/presentation/appointments/my_appointments_page.dart @@ -113,7 +113,7 @@ class _MyAppointmentsPageState extends State { borderColor: myAppointmentsVM.isAppointmentsSortByClinic ? AppColors.primaryRedColor : AppColors.textColor.withValues(alpha: 0.2), textColor: myAppointmentsVM.isAppointmentsSortByClinic ? AppColors.primaryRedColor : AppColors.blackColor, fontSize: 12, - fontWeight: FontWeight.w500, + isBold: true, borderRadius: 10, padding: EdgeInsets.fromLTRB(10, 0, 10, 0), height: 40.h, @@ -128,7 +128,7 @@ class _MyAppointmentsPageState extends State { borderColor: myAppointmentsVM.isAppointmentsSortByClinic ? AppColors.textColor.withValues(alpha: 0.2) : AppColors.primaryRedColor, textColor: myAppointmentsVM.isAppointmentsSortByClinic ? AppColors.blackColor : AppColors.primaryRedColor, fontSize: 12, - fontWeight: FontWeight.w500, + isBold: true, borderRadius: 10, padding: EdgeInsets.fromLTRB(10, 0, 10, 0), height: 40.h, @@ -191,7 +191,7 @@ class _MyAppointmentsPageState extends State { children: [ AppCustomChipWidget( labelText: - "${myAppointmentsVM.patientAppointmentsViewList[index].patientDoctorAppointmentList!.length} Appointments"), + "${myAppointmentsVM.patientAppointmentsViewList[index].patientDoctorAppointmentList!.length} ${LocaleKeys.appointments.tr(context: context)}", isEnglishOnly: true,), Icon(isExpanded ? Icons.expand_less : Icons.expand_more), ], ), @@ -266,7 +266,7 @@ class _MyAppointmentsPageState extends State { borderColor: Color(0xffFEE9EA), textColor: Color(0xffED1C2B), fontSize: 14.f, - fontWeight: FontWeight.w500, + isBold: true, borderRadius: 12.r, padding: EdgeInsets.fromLTRB(10.w, 0, 10.w, 0), height: 40.h, diff --git a/lib/presentation/appointments/my_doctors_page.dart b/lib/presentation/appointments/my_doctors_page.dart index 26e5927b..93733770 100644 --- a/lib/presentation/appointments/my_doctors_page.dart +++ b/lib/presentation/appointments/my_doctors_page.dart @@ -89,7 +89,7 @@ class _MyDoctorsPageState extends State { borderColor: isSortByClinic ? AppColors.primaryRedColor : AppColors.textColor.withValues(alpha: 0.2), textColor: isSortByClinic ? AppColors.primaryRedColor : AppColors.blackColor, fontSize: 12, - fontWeight: FontWeight.w500, + isBold: true, borderRadius: 10, padding: EdgeInsets.fromLTRB(10, 0, 10, 0), height: 40.h, @@ -107,7 +107,7 @@ class _MyDoctorsPageState extends State { borderColor: isSortByClinic ? AppColors.textColor.withValues(alpha: 0.2) : AppColors.primaryRedColor, textColor: isSortByClinic ? AppColors.blackColor : AppColors.primaryRedColor, fontSize: 12, - fontWeight: FontWeight.w500, + isBold: true, borderRadius: 10, padding: EdgeInsets.fromLTRB(10, 0, 10, 0), height: 40.h, @@ -234,7 +234,7 @@ class _MyDoctorsPageState extends State { SizedBox(height: 8.h), Text( displayName, - style: TextStyle(fontSize: 16.h, fontWeight: FontWeight.w600), + style: TextStyle(fontSize: 16.h, fontWeight: FontWeight.w600,), overflow: TextOverflow.ellipsis, ), ], @@ -276,7 +276,7 @@ class _MyDoctorsPageState extends State { ).circle(100), SizedBox(width: 8.h), Expanded( - child: (doctor?.doctorName ?? "").toString().toText14(weight: FontWeight.w500), + child: (doctor?.doctorName ?? "").toString().toText14(isBold: true), ), ], ), @@ -318,7 +318,7 @@ class _MyDoctorsPageState extends State { LoaderBottomSheet.hideLoader(); Navigator.of(context).push( CustomPageRoute( - page: DoctorProfilePage(), + page: DoctorProfilePage(isDoctorAllowedToBook: !doctor?.isLiveCareClinic), ), ); }, onError: (err) { @@ -336,7 +336,7 @@ class _MyDoctorsPageState extends State { borderColor: doctor?.isActiveDoctorProfile == false ? AppColors.inputLabelTextColor.withValues(alpha: 0.01) : AppColors.secondaryLightRedColor, textColor: doctor?.isActiveDoctorProfile == false ? AppColors.inputLabelTextColor.withValues(alpha: 1.0) : AppColors.primaryRedColor, fontSize: 14, - fontWeight: FontWeight.w500, + isBold: true, borderRadius: 12, padding: const EdgeInsets.fromLTRB(10, 0, 10, 0), height: 40.h, diff --git a/lib/presentation/appointments/widgets/appointment_card.dart b/lib/presentation/appointments/widgets/appointment_card.dart index 679c4bac..db08c923 100644 --- a/lib/presentation/appointments/widgets/appointment_card.dart +++ b/lib/presentation/appointments/widgets/appointment_card.dart @@ -40,6 +40,7 @@ class AppointmentCard extends StatelessWidget { final ContactUsViewModel? contactUsViewModel; final BookAppointmentsViewModel bookAppointmentsViewModel; final bool isForRate; + // bool isAppointmentWithin4Hours = false; const AppointmentCard( {super.key, @@ -205,7 +206,7 @@ class AppointmentCard extends StatelessWidget { richText: isLoading ? 'Cardiology'.toText10().toShimmer2(isShow: isLoading) : "${DateUtil.formatDateToDate(DateUtil.convertStringToDate(patientAppointmentHistoryResponseModel.appointmentDate), false)} ${DateUtil.formatDateToTimeLang(DateUtil.convertStringToDate(patientAppointmentHistoryResponseModel.appointmentDate), false)}" - .toText10(isEnglishOnly: true), + .toText10(isEnglishOnly: true, isBold: true), ).toShimmer2(isShow: isLoading), ), @@ -243,42 +244,123 @@ class AppointmentCard extends StatelessWidget { (patientAppointmentHistoryResponseModel.isExecludeDoctor ?? false) || !Utils.isClinicAllowedForRebook(patientAppointmentHistoryResponseModel.clinicID ?? 0))) && AppointmentType.isArrived(patientAppointmentHistoryResponseModel)) { + // if (((patientAppointmentHistoryResponseModel.isLiveCareAppointment ?? false) && + // DateTime.now().difference(DateUtil.convertStringToDate(patientAppointmentHistoryResponseModel.appointmentDate)).inDays <= 15)) { + // return Row( + // children: [ + // Expanded( + // flex: 6, + // child: CustomButton( + // text: LocaleKeys.askDoctor.tr(context: context), + // onPressed: () async { + // LoaderBottomSheet.showLoader(loadingText: LocaleKeys.checkingDoctorAvailability.tr(context: context)); + // await myAppointmentsViewModel.isDoctorAvailable( + // projectID: patientAppointmentHistoryResponseModel.projectID, + // doctorId: patientAppointmentHistoryResponseModel.doctorID, + // clinicId: patientAppointmentHistoryResponseModel.clinicID, + // onSuccess: (value) async { + // if (value) { + // await myAppointmentsViewModel.getAskDoctorRequestTypes(onSuccess: (val) { + // LoaderBottomSheet.hideLoader(); + // showCommonBottomSheetWithoutHeight( + // context, + // title: LocaleKeys.askDoctor.tr(context: context), + // child: AskDoctorRequestTypeSelect( + // askDoctorRequestTypeList: myAppointmentsViewModel.askDoctorRequestTypeList, + // myAppointmentsViewModel: myAppointmentsViewModel, + // patientAppointmentHistoryResponseModel: patientAppointmentHistoryResponseModel, + // ), + // callBackFunc: () {}, + // isFullScreen: false, + // isCloseButtonVisible: true, + // ); + // }); + // } else { + // print("Doctor is not available"); + // } + // }); + // }, + // backgroundColor: AppColors.secondaryLightRedColor, + // borderColor: AppColors.secondaryLightRedColor, + // textColor: AppColors.primaryRedColor, + // fontSize: (isFoldable || isTablet) ? 12.f : 14.f, + // fontWeight: FontWeight.w600, + // borderRadius: 12.r, + // padding: EdgeInsets.symmetric(horizontal: 10.w), + // // height: isTablet || isFoldable ? 46.h : 40.h, + // height: 40.h, + // icon: AppAssets.ask_doctor_icon, + // iconColor: AppColors.primaryRedColor, + // iconSize: 16.h, + // ), + // ), + // SizedBox(width: 8.h), + // Expanded( + // flex: 1, + // child: Container( + // height: (isFoldable || isTablet) ? 50.h : 40.h, + // decoration: RoundedRectangleBorder().toSmoothCornerDecoration( + // color: AppColors.textColor, + // borderRadius: 10.h, + // side: BorderSide( + // color: AppColors.textColor, + // width: 1.2, + // ), + // ), + // child: Transform.flip( + // flipX: appState.isArabic(), + // child: Utils.buildSvgWithAssets( + // icon: AppAssets.forward_arrow_icon, + // iconColor: AppColors.whiteColor, + // width: 24.w, + // height: 24.h, + // fit: BoxFit.contain, + // ), + // ), + // ).toShimmer2(isShow: isLoading).onPress(() { + // _goToDetails(context); + // }), + // ), + // ], + // ); + // } else { return CustomButton( text: isFromMedicalReport ? LocaleKeys.selectAppointment.tr(context: context) : LocaleKeys.viewDetails.tr(context: context), - onPressed: () { - if (isFromMedicalReport) { - if (isForFeedback) { - contactUsViewModel!.setPatientFeedbackSelectedAppointment(patientAppointmentHistoryResponseModel); + onPressed: () { + if (isFromMedicalReport) { + if (isForFeedback) { + contactUsViewModel!.setPatientFeedbackSelectedAppointment(patientAppointmentHistoryResponseModel); + } else { + medicalFileViewModel!.setSelectedMedicalReportAppointment(patientAppointmentHistoryResponseModel); + } + Navigator.pop(context, false); } else { - medicalFileViewModel!.setSelectedMedicalReportAppointment(patientAppointmentHistoryResponseModel); + Navigator.of(context) + .push( + CustomPageRoute( + page: AppointmentDetailsPage(patientAppointmentHistoryResponseModel: patientAppointmentHistoryResponseModel), + ), + ) + .then((_) { + myAppointmentsViewModel.initAppointmentsViewModel(); + myAppointmentsViewModel.getPatientAppointments(true, false); + }); } - Navigator.pop(context, false); - } else { - Navigator.of(context) - .push( - CustomPageRoute( - page: AppointmentDetailsPage(patientAppointmentHistoryResponseModel: patientAppointmentHistoryResponseModel), - ), - ) - .then((_) { - myAppointmentsViewModel.initAppointmentsViewModel(); - myAppointmentsViewModel.getPatientAppointments(true, false); - }); - } - }, - backgroundColor: AppColors.secondaryLightRedColor, - borderColor: AppColors.secondaryLightRedColor, - textColor: AppColors.primaryRedColor, - fontSize: (isFoldable || isTablet) ? 12.f : 14.f, - fontWeight: FontWeight.w500, - borderRadius: 12.r, - padding: EdgeInsets.symmetric(horizontal: 10.w), - // height: isTablet || isFoldable ? 46.h : 40.h, - height: 40.h, - icon: isFromMedicalReport ? AppAssets.checkmark_icon : null, - iconColor: AppColors.primaryRedColor, - iconSize: 16.h, - ); + }, + backgroundColor: AppColors.secondaryLightRedColor, + borderColor: AppColors.secondaryLightRedColor, + textColor: AppColors.primaryRedColor, + fontSize: (isFoldable || isTablet) ? 12.f : 14.f, + fontWeight: FontWeight.w600, + borderRadius: 12.r, + padding: EdgeInsets.symmetric(horizontal: 10.w), + // height: isTablet || isFoldable ? 46.h : 40.h, + height: 40.h, + icon: isFromMedicalReport ? AppAssets.checkmark_icon : null, + iconColor: AppColors.primaryRedColor, + iconSize: 16.h, + ); + // } } else { if (isFromMedicalReport) { if (isForEyeMeasurements) { @@ -298,7 +380,7 @@ class AppointmentCard extends StatelessWidget { borderColor: AppColors.secondaryLightRedColor, textColor: AppColors.primaryRedColor, fontSize: (isFoldable || isTablet) ? 12.f : 14.f, - fontWeight: FontWeight.w500, + fontWeight: FontWeight.w600, borderRadius: 12.r, padding: EdgeInsets.symmetric(horizontal: 10.w), // height: isTablet || isFoldable ? 46.h : 40.h, @@ -309,9 +391,9 @@ class AppointmentCard extends StatelessWidget { ); } } - - return Row( - children: [ + return (patientAppointmentHistoryResponseModel.isActiveDoctor ?? true) + ? Row( + children: [ Expanded( flex: 6, child: (AppointmentType.isArrived(patientAppointmentHistoryResponseModel) @@ -323,7 +405,7 @@ class AppointmentCard extends StatelessWidget { borderColor: AppointmentType.getNextActionButtonColor(patientAppointmentHistoryResponseModel.nextAction).withValues(alpha: 0.01), textColor: AppointmentType.getNextActionTextColor(patientAppointmentHistoryResponseModel.nextAction), fontSize: (isFoldable || isTablet) ? 12.f : 14.f, - fontWeight: FontWeight.w500, + fontWeight: FontWeight.w600, borderRadius: 12.r, padding: EdgeInsets.symmetric(horizontal: 10.w), height: 40.h, @@ -361,60 +443,91 @@ class AppointmentCard extends StatelessWidget { }), ), ], - ); + ) + : CustomButton( + text: LocaleKeys.viewDetails.tr(context: context), + onPressed: () { + Navigator.of(context) + .push( + CustomPageRoute( + page: AppointmentDetailsPage(patientAppointmentHistoryResponseModel: patientAppointmentHistoryResponseModel), + ), + ) + .then((_) { + myAppointmentsViewModel.initAppointmentsViewModel(); + myAppointmentsViewModel.getPatientAppointments(true, false); + }); + }, + backgroundColor: AppColors.secondaryLightRedColor, + borderColor: AppColors.secondaryLightRedColor, + textColor: AppColors.primaryRedColor, + fontSize: (isFoldable || isTablet) ? 12.f : 14.f, + fontWeight: FontWeight.w600, + borderRadius: 12.r, + padding: EdgeInsets.symmetric(horizontal: 10.w), + // height: isTablet || isFoldable ? 46.h : 40.h, + height: 40.h, + icon: isFromMedicalReport ? AppAssets.checkmark_icon : null, + iconColor: AppColors.primaryRedColor, + iconSize: 16.h, + ); } } Widget _getArrivedButton(BuildContext context) { - final isRecent = DateTime.now().difference(DateUtil.convertStringToDate(patientAppointmentHistoryResponseModel.appointmentDate)).inDays <= 15; - - if (isRecent) { - return CustomButton( - text: LocaleKeys.askDoctor.tr(context: context), - onPressed: () async { - LoaderBottomSheet.showLoader(loadingText: LocaleKeys.checkingDoctorAvailability.tr(context: context)); - await myAppointmentsViewModel.isDoctorAvailable( - projectID: patientAppointmentHistoryResponseModel.projectID, - doctorId: patientAppointmentHistoryResponseModel.doctorID, - clinicId: patientAppointmentHistoryResponseModel.clinicID, - onSuccess: (value) async { - if (value) { - await myAppointmentsViewModel.getAskDoctorRequestTypes(onSuccess: (val) { - LoaderBottomSheet.hideLoader(); - showCommonBottomSheetWithoutHeight( - context, - title: LocaleKeys.askDoctor.tr(context: context), - child: AskDoctorRequestTypeSelect( - askDoctorRequestTypeList: myAppointmentsViewModel.askDoctorRequestTypeList, - myAppointmentsViewModel: myAppointmentsViewModel, - patientAppointmentHistoryResponseModel: patientAppointmentHistoryResponseModel, - ), - callBackFunc: () {}, - isFullScreen: false, - isCloseButtonVisible: true, - ); - }); - } else { - print("Doctor is not available"); - } - }); - }, - backgroundColor: AppColors.secondaryLightRedColor, - borderColor: AppColors.secondaryLightRedColor, - textColor: AppColors.primaryRedColor, - fontSize: (isFoldable || isTablet) ? 12.f : 14.f, - fontWeight: FontWeight.w500, - borderRadius: 12.r, - padding: EdgeInsets.symmetric(horizontal: 10.w), - // height: isTablet || isFoldable ? 46.h : 40.h, - height: 40.h, - icon: AppAssets.ask_doctor_icon, - iconColor: AppColors.primaryRedColor, - iconSize: 16.h, - ); - } + // final isRecent = DateTime.now().difference(DateUtil.convertStringToDate(patientAppointmentHistoryResponseModel.appointmentDate)).inDays <= 15; + // + // if (isRecent) { + // return CustomButton( + // text: LocaleKeys.askDoctor.tr(context: context), + // onPressed: () async { + // LoaderBottomSheet.showLoader(loadingText: LocaleKeys.checkingDoctorAvailability.tr(context: context)); + // await myAppointmentsViewModel.isDoctorAvailable( + // projectID: patientAppointmentHistoryResponseModel.projectID, + // doctorId: patientAppointmentHistoryResponseModel.doctorID, + // clinicId: patientAppointmentHistoryResponseModel.clinicID, + // onSuccess: (value) async { + // if (value) { + // await myAppointmentsViewModel.getAskDoctorRequestTypes(onSuccess: (val) { + // LoaderBottomSheet.hideLoader(); + // showCommonBottomSheetWithoutHeight( + // context, + // title: LocaleKeys.askDoctor.tr(context: context), + // child: AskDoctorRequestTypeSelect( + // askDoctorRequestTypeList: myAppointmentsViewModel.askDoctorRequestTypeList, + // myAppointmentsViewModel: myAppointmentsViewModel, + // patientAppointmentHistoryResponseModel: patientAppointmentHistoryResponseModel, + // ), + // callBackFunc: () {}, + // isFullScreen: false, + // isCloseButtonVisible: true, + // ); + // }); + // } else { + // print("Doctor is not available"); + // } + // }); + // }, + // backgroundColor: AppColors.secondaryLightRedColor, + // borderColor: AppColors.secondaryLightRedColor, + // textColor: AppColors.primaryRedColor, + // fontSize: (isFoldable || isTablet) ? 12.f : 14.f, + // fontWeight: FontWeight.w600, + // borderRadius: 12.r, + // padding: EdgeInsets.symmetric(horizontal: 10.w), + // // height: isTablet || isFoldable ? 46.h : 40.h, + // height: 40.h, + // icon: AppAssets.ask_doctor_icon, + // iconColor: AppColors.primaryRedColor, + // iconSize: 16.h, + // ); + // } return CustomButton( + borderSide: BorderSide( + color: AppColors.textColor, + width: 1.2, + ), text: LocaleKeys.rebookSameDoctor.tr(context: context), onPressed: () => openDoctorScheduleCalendar(context), backgroundColor: AppColors.transparent, @@ -422,7 +535,7 @@ class AppointmentCard extends StatelessWidget { textColor: AppColors.blackColor, borderWidth: 1.h, fontSize: (isFoldable || isTablet) ? 12.f : 14.f, - fontWeight: FontWeight.w500, + fontWeight: FontWeight.w600, borderRadius: 12.r, padding: EdgeInsets.symmetric(horizontal: 10.w), height: 40.h, diff --git a/lib/presentation/appointments/widgets/appointment_checkin_bottom_sheet.dart b/lib/presentation/appointments/widgets/appointment_checkin_bottom_sheet.dart index 2acc076e..817ebfa0 100644 --- a/lib/presentation/appointments/widgets/appointment_checkin_bottom_sheet.dart +++ b/lib/presentation/appointments/widgets/appointment_checkin_bottom_sheet.dart @@ -120,7 +120,7 @@ class AppointmentCheckinBottomSheet extends StatelessWidget { crossAxisAlignment: CrossAxisAlignment.start, children: [ title.toText16(isBold: true, color: AppColors.textColor), - subTitle.toText12(fontWeight: FontWeight.w500, color: AppColors.greyTextColor), + subTitle.toText12(isBold: true, color: AppColors.greyTextColor), ], ), ), @@ -168,7 +168,7 @@ class AppointmentCheckinBottomSheet extends StatelessWidget { // page: AppointmentQueuePage(), // ), // ); - }, isFullScreen: false); + }, isFullScreen: false, isAutoDismiss: true, isCloseButtonVisible: false); }, onError: (error) { LoaderBottomSheet.hideLoader(); diff --git a/lib/presentation/appointments/widgets/appointment_doctor_card.dart b/lib/presentation/appointments/widgets/appointment_doctor_card.dart index f00284c0..4b5df07b 100644 --- a/lib/presentation/appointments/widgets/appointment_doctor_card.dart +++ b/lib/presentation/appointments/widgets/appointment_doctor_card.dart @@ -126,7 +126,7 @@ class AppointmentDoctorCard extends StatelessWidget { DateUtil.convertStringToDate(patientAppointmentHistoryResponseModel.appointmentDate), false, )}" - .toText10(isEnglishOnly: true), + .toText10(isBold: true), ), ), AppCustomChipWidget( @@ -142,8 +142,10 @@ class AppointmentDoctorCard extends StatelessWidget { ], ), ), - Utils.buildSvgWithAssets(icon: AppAssets.doctor_profile_icon, width: 20.h, height: 20.h, fit: BoxFit.scaleDown).onPress(() async { - DoctorsListResponseModel selectedDoctor = DoctorsListResponseModel(); + patientAppointmentHistoryResponseModel.isLiveCareAppointment! + ? SizedBox.shrink() + : Utils.buildSvgWithAssets(icon: AppAssets.doctor_profile_icon, width: 20.h, height: 20.h, fit: BoxFit.scaleDown).onPress(() async { + DoctorsListResponseModel selectedDoctor = DoctorsListResponseModel(); selectedDoctor.doctorID = patientAppointmentHistoryResponseModel.doctorID; selectedDoctor.doctorImageURL = patientAppointmentHistoryResponseModel.doctorImageURL; selectedDoctor.name = patientAppointmentHistoryResponseModel.doctorNameObj; @@ -161,7 +163,9 @@ class AppointmentDoctorCard extends StatelessWidget { LoaderBottomSheet.hideLoader(); Navigator.of(context).push( CustomPageRoute( - page: DoctorProfilePage(), + page: DoctorProfilePage( + isDoctorAllowedToBook: !(patientAppointmentHistoryResponseModel.isLiveCareAppointment ?? false), + ), ), ); }, onError: (err) { @@ -202,7 +206,7 @@ class AppointmentDoctorCard extends StatelessWidget { borderColor: AppColors.secondaryLightRedColor, textColor: AppColors.primaryRedColor, fontSize: 14.f, - fontWeight: FontWeight.w500, + isBold: true, borderRadius: 12.r, padding: EdgeInsets.fromLTRB(10.w, 0, 10.w, 0), height: 40.h, @@ -221,7 +225,7 @@ class AppointmentDoctorCard extends StatelessWidget { // borderColor: AppColors.greyColor, // textColor: AppColors.blackColor, // fontSize: 12.f, - // fontWeight: FontWeight.w500, + // isBold: true, // borderRadius: 12.r, // padding: EdgeInsets.fromLTRB(10.w, 0, 10.w, 0), // height: 40.h, @@ -242,7 +246,7 @@ class AppointmentDoctorCard extends StatelessWidget { borderColor: AppColors.primaryRedColor, textColor: AppColors.whiteColor, fontSize: 14.f, - fontWeight: FontWeight.w500, + isBold: true, borderRadius: 12.r, height: 40.h, icon: AppAssets.cancel, @@ -261,9 +265,9 @@ class AppointmentDoctorCard extends StatelessWidget { borderColor: AppColors.secondaryLightRedColor, textColor: AppColors.primaryRedColor, fontSize: 14.f, - fontWeight: FontWeight.w500, + isBold: true, borderRadius: 12.r, - height: 40.h, + height: 46.h, icon: AppAssets.appointment_calendar_icon, iconColor: AppColors.primaryRedColor, iconSize: 16.h, @@ -280,9 +284,9 @@ class AppointmentDoctorCard extends StatelessWidget { borderColor: AppColors.primaryRedColor, textColor: AppColors.whiteColor, fontSize: 14.f, - fontWeight: FontWeight.w500, + isBold: true, borderRadius: 12.r, - height: 40.h, + height: 46.h, icon: AppAssets.cancel, iconColor: AppColors.whiteColor, iconSize: 16.h, diff --git a/lib/presentation/appointments/widgets/appointment_rating_widget.dart b/lib/presentation/appointments/widgets/appointment_rating_widget.dart new file mode 100644 index 00000000..9b13c6cd --- /dev/null +++ b/lib/presentation/appointments/widgets/appointment_rating_widget.dart @@ -0,0 +1,181 @@ +import 'package:easy_localization/easy_localization.dart'; +import 'package:flutter/material.dart'; +import 'package:hmg_patient_app_new/core/app_export.dart'; +import 'package:hmg_patient_app_new/extensions/string_extensions.dart'; +import 'package:hmg_patient_app_new/extensions/widget_extensions.dart'; +import 'package:hmg_patient_app_new/features/book_appointments/book_appointments_view_model.dart'; +import 'package:hmg_patient_app_new/features/my_appointments/models/resp_models/patient_appointment_history_response_model.dart'; +import 'package:hmg_patient_app_new/features/my_appointments/my_appointments_view_model.dart'; +import 'package:hmg_patient_app_new/generated/locale_keys.g.dart'; +import 'package:hmg_patient_app_new/presentation/appointments/widgets/doctor_row_appointment_rating.dart'; +import 'package:hmg_patient_app_new/theme/colors.dart'; +import 'package:hmg_patient_app_new/widgets/buttons/custom_button.dart'; +import 'package:hmg_patient_app_new/widgets/loader/bottomsheet_loader.dart'; +import 'package:provider/provider.dart'; + +class AppointmentRatingWidget extends StatefulWidget { + const AppointmentRatingWidget({super.key, required this.patientAppointmentHistoryResponseModel}); + + final PatientAppointmentHistoryResponseModel patientAppointmentHistoryResponseModel; + + @override + State createState() => _AppointmentRatingWidgetState(); +} + +class _AppointmentRatingWidgetState extends State { + late MyAppointmentsViewModel myAppointmentsViewModel; + late BookAppointmentsViewModel bookAppointmentsViewModel; + + int rating = 0; + String notes = ""; + + @override + Widget build(BuildContext context) { + myAppointmentsViewModel = Provider.of(context, listen: false); + bookAppointmentsViewModel = Provider.of(context, listen: false); + return SizedBox( + height: MediaQuery.of(context).size.height * 0.6, + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + // Scrollable main content + Expanded( + child: Padding( + padding: const EdgeInsets.only(top: 0.0, left: 0, right: 0), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + // Doctor row + Container( + decoration: RoundedRectangleBorder().toSmoothCornerDecoration( + color: AppColors.whiteColor, + borderRadius: 24.r, + hasShadow: false, + ), + child: BuildDoctorRowAppointmentRating( + patientAppointmentHistoryResponseModel: widget.patientAppointmentHistoryResponseModel, + )), + + SizedBox(height: 16), + + // Rating box + Container( + decoration: RoundedRectangleBorder().toSmoothCornerDecoration( + color: AppColors.whiteColor, + borderRadius: 24.r, + hasShadow: false, + ), + width: double.infinity, + child: Padding( + padding: const EdgeInsets.all(12.0), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + LocaleKeys.rateTheDoctor.tr(context: context).toText16(isBold: true), + SizedBox(height: 12), + Row( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + ...List.generate( + 5, + (index) => AnimatedSwitcher( + duration: Duration(milliseconds: 1000), + switchInCurve: Curves.elasticOut, + switchOutCurve: Curves.elasticIn, + transitionBuilder: (Widget child, Animation animation) { + return ScaleTransition(child: child, scale: animation); + }, + child: Container( + key: ValueKey(rating), + child: IconButton( + iconSize: 45.0, + onPressed: () { + setState(() { + rating = index + 1; + }); + }, + color: rating >= (index + 1) ? Color.fromRGBO(255, 186, 0, 1.0) : Colors.grey[400], + icon: Icon(rating >= (index + 1) ? Icons.star : Icons.star)), + ), + ), + ) + ], + ), + ], + ), + ), + ), + + SizedBox(height: 12), + + // Note text field + Container( + decoration: RoundedRectangleBorder().toSmoothCornerDecoration( + color: AppColors.whiteColor, + borderRadius: 24.r, + hasShadow: false, + ), + child: Padding( + padding: EdgeInsets.all(16.0), + child: TextField( + maxLines: 4, + decoration: InputDecoration.collapsed( + hintText: LocaleKeys.notes.tr(context: context), + hintStyle: TextStyle(fontSize: 16.f, fontWeight: FontWeight.w600, color: Color(0xff2B353E), letterSpacing: -0.64, height: 23 / 16)), + onChanged: (value) { + setState(() { + notes = value; + }); + }, + ))), + ], + ), + ), + ), + // Bottom action buttons pinned to bottom + SafeArea( + top: false, + child: Padding( + padding: const EdgeInsets.symmetric(vertical: 12.0), + child: Row( + children: [ + Expanded( + child: CustomButton( + text: LocaleKeys.later.tr(context: context), + backgroundColor: Color(0xffFEE9EA), + borderColor: Color(0xffFEE9EA), + textColor: Color(0xffED1C2B), + onPressed: () { + Navigator.pop(context); + }, + ), + ), + SizedBox(width: 10), + Expanded( + child: CustomButton( + text: LocaleKeys.submit.tr(context: context), + isDisabled: rating == 0, + onPressed: rating == 0 ? null : () async { + // Set up clinic rating and show clinic rating view + // appointmentRatingViewModel!.setTitle(LocaleKeys.rateDoctor.tr(context: context),); + // appointmentRatingViewModel!.setSubTitle(LocaleKeys.howWasYourLastVisitWithDoctor.tr(context: context),); + // appointmentRatingViewModel!.setClinicOrDoctor(true); + + LoaderBottomSheet.showLoader(); + await myAppointmentsViewModel.submitDoctorRating(docRate: rating, docNote: notes, patientAppointmentHistoryResponseModel: widget.patientAppointmentHistoryResponseModel); + // await appointmentRatingViewModel!.submitClinicRating(clinicRate: rating, clinicNote: note); + LoaderBottomSheet.hideLoader(); + Navigator.pop(context); + setState(() {}); + }, + ), + ), + ], + ), + ), + ), + ], + ), + ); + } +} diff --git a/lib/presentation/appointments/widgets/ask_doctor_request_type_select.dart b/lib/presentation/appointments/widgets/ask_doctor_request_type_select.dart index 3002c1c9..bf74863a 100644 --- a/lib/presentation/appointments/widgets/ask_doctor_request_type_select.dart +++ b/lib/presentation/appointments/widgets/ask_doctor_request_type_select.dart @@ -1,5 +1,6 @@ import 'package:easy_localization/easy_localization.dart'; import 'package:flutter/material.dart'; +import 'package:get_it/get_it.dart'; import 'package:hmg_patient_app_new/core/app_assets.dart'; import 'package:hmg_patient_app_new/core/utils/size_utils.dart'; import 'package:hmg_patient_app_new/core/utils/utils.dart'; @@ -10,6 +11,7 @@ import 'package:hmg_patient_app_new/features/my_appointments/models/resp_models/ import 'package:hmg_patient_app_new/features/my_appointments/models/resp_models/patient_appointment_history_response_model.dart'; import 'package:hmg_patient_app_new/features/my_appointments/my_appointments_view_model.dart'; import 'package:hmg_patient_app_new/generated/locale_keys.g.dart'; +import 'package:hmg_patient_app_new/services/navigation_service.dart'; import 'package:hmg_patient_app_new/theme/colors.dart'; import 'package:hmg_patient_app_new/widgets/buttons/custom_button.dart'; import 'package:hmg_patient_app_new/widgets/common_bottom_sheet.dart'; @@ -47,7 +49,7 @@ class AskDoctorRequestTypeSelect extends StatelessWidget { listTileTheme: ListTileThemeData(horizontalTitleGap: 4), ), child: RadioListTile( - title: (askDoctorRequestTypeList[index].description ?? '').toText14(weight: FontWeight.w500), + title: (askDoctorRequestTypeList[index].description ?? '').toText14(isBold: true), value: index, fillColor: WidgetStateProperty.resolveWith((states) { if (states.contains(WidgetState.selected)) { @@ -95,8 +97,8 @@ class AskDoctorRequestTypeSelect extends StatelessWidget { child: CustomButton( text: LocaleKeys.confirm.tr(), onPressed: () async { - Navigator.pop(context); - LoaderBottomSheet.showLoader(loadingText: "Sending Request..."); + Navigator.of(context).pop(); + LoaderBottomSheet.showLoader(loadingText: LocaleKeys.sendingAskDrRequest.tr(context: context)); await myAppointmentsViewModel.sendAskDocCallRequest( patientAppointmentHistoryResponseModel: patientAppointmentHistoryResponseModel, requestType: selectedParameterCodeValue.toString(), @@ -104,17 +106,10 @@ class AskDoctorRequestTypeSelect extends StatelessWidget { userMobileNumber: myAppointmentsViewModel.appState.getAuthenticatedUser()!.mobileNumber!, onSuccess: (val) { LoaderBottomSheet.hideLoader(); - showCommonBottomSheetWithoutHeight( - context, - child: Utils.getSuccessWidget(loadingText: "Request has been sent successfully, you will be contacted soon."), - callBackFunc: () { - Navigator.of(context).pop(); - }, - title: "", - isCloseButtonVisible: true, - isDismissible: false, - isFullScreen: false, - ); + showCommonBottomSheetWithoutHeight(GetIt.instance().navigatorKey.currentContext!, + child: Padding(padding: EdgeInsets.symmetric(horizontal: 24.h), child: Utils.getSuccessWidget(loadingText: LocaleKeys.askDrRequestSentSuccess.tr())), callBackFunc: () { + Navigator.of(context).pop(); + }, title: "", isCloseButtonVisible: false, isDismissible: false, isFullScreen: false, isAutoDismiss: true); }, onError: (errMessage) { LoaderBottomSheet.hideLoader(); diff --git a/lib/presentation/appointments/widgets/doctor_row_appointment_rating.dart b/lib/presentation/appointments/widgets/doctor_row_appointment_rating.dart new file mode 100644 index 00000000..8872f4ea --- /dev/null +++ b/lib/presentation/appointments/widgets/doctor_row_appointment_rating.dart @@ -0,0 +1,67 @@ +import 'package:flutter/material.dart'; +import 'package:hmg_patient_app_new/core/app_assets.dart'; +import 'package:hmg_patient_app_new/core/utils/date_util.dart'; +import 'package:hmg_patient_app_new/core/utils/size_utils.dart'; +import 'package:hmg_patient_app_new/extensions/string_extensions.dart'; +import 'package:hmg_patient_app_new/extensions/widget_extensions.dart'; +import 'package:hmg_patient_app_new/features/my_appointments/models/resp_models/appointment_details_resp_model.dart'; +import 'package:hmg_patient_app_new/features/my_appointments/models/resp_models/patient_appointment_history_response_model.dart'; +import 'package:hmg_patient_app_new/widgets/chip/app_custom_chip_widget.dart'; + +import 'dart:ui' as ui; + +class BuildDoctorRowAppointmentRating extends StatelessWidget { + final PatientAppointmentHistoryResponseModel patientAppointmentHistoryResponseModel; + + BuildDoctorRowAppointmentRating({super.key, required this.patientAppointmentHistoryResponseModel}); + + @override + Widget build(BuildContext context) { + return Padding( + padding: EdgeInsets.all(16), + child: Row( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Image.network( + patientAppointmentHistoryResponseModel.doctorImageURL!, + width: 63.h, + height: 63.h, + fit: BoxFit.cover, + ).circle(100), + SizedBox(width: 16.h), + Expanded( + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + patientAppointmentHistoryResponseModel.doctorNameObj!.toString().toText16(isBold: true, maxlines: 1), + SizedBox(height: 8.h), + Wrap( + direction: Axis.horizontal, + spacing: 3.h, + runSpacing: 4.h, + children: [ + AppCustomChipWidget( + labelText: patientAppointmentHistoryResponseModel.projectName.toString(), + ), + AppCustomChipWidget( + labelText: patientAppointmentHistoryResponseModel.clinicName.toString(), + ), + Directionality( + textDirection: ui.TextDirection.ltr, + child: AppCustomChipWidget( + labelPadding: EdgeInsetsDirectional.only(start: -8.w, end: 6.w), + icon: AppAssets.ic_date_filter, + labelText: DateUtil.formatDateToDate(DateUtil.convertStringToDate(patientAppointmentHistoryResponseModel.appointmentDate), false), + isEnglishOnly: true, + ), + ), + ], + ), + ], + ), + ), + ], + ), + ); + } +} diff --git a/lib/presentation/appointments/widgets/faculity_selection/facility_selection_item.dart b/lib/presentation/appointments/widgets/faculity_selection/facility_selection_item.dart index 2f5589c8..a6e3e8d2 100644 --- a/lib/presentation/appointments/widgets/faculity_selection/facility_selection_item.dart +++ b/lib/presentation/appointments/widgets/faculity_selection/facility_selection_item.dart @@ -82,7 +82,7 @@ class FacilitySelectionItem extends StatelessWidget { subTitle, style: TextStyle( fontSize: 12.h, - fontWeight: FontWeight.w500, + fontWeight: FontWeight.w600, color: AppColors.greyTextColor, ), ), diff --git a/lib/presentation/appointments/widgets/faculity_selection/facility_type_selection_widget.dart b/lib/presentation/appointments/widgets/faculity_selection/facility_type_selection_widget.dart index b6366dbf..eb084ca7 100644 --- a/lib/presentation/appointments/widgets/faculity_selection/facility_type_selection_widget.dart +++ b/lib/presentation/appointments/widgets/faculity_selection/facility_type_selection_widget.dart @@ -39,7 +39,7 @@ class FacilityTypeSelectionWidget extends StatelessWidget { LocaleKeys.selectFacilitiesSubTitle.tr(), style: TextStyle( fontSize: 16, - fontWeight: FontWeight.w500, + fontWeight: FontWeight.w600, color: AppColors.greyTextColor, ), ), 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 8904e585..4264ba17 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 @@ -36,7 +36,7 @@ import '../../../emergency_services/call_ambulance/widgets/type_selection_widget // LocaleKeys.selectHospital.tr(context: context), // style: TextStyle( // fontSize: 21, -// fontWeight: FontWeight.w600, +// isBold: true, // color: AppColors.blackColor, // ), // ), @@ -44,7 +44,7 @@ import '../../../emergency_services/call_ambulance/widgets/type_selection_widget // LocaleKeys.selectHospitalSubTitle.tr(context: context), // style: TextStyle( // fontSize: 16, -// fontWeight: FontWeight.w500, +// isBold: true, // color: AppColors.greyTextColor, // ), // ), @@ -186,7 +186,7 @@ class HospitalBottomSheetBody extends StatelessWidget { children: [ LocaleKeys.sortByLocation.tr(context: context).toText14(isBold: true), // SizedBox(height: 4.h), - LocaleKeys.sortByNearestLocation.tr(context: context).toText11(color: AppColors.textColorLight, weight: FontWeight.w500), + LocaleKeys.sortByNearestLocation.tr(context: context).toText11(color: AppColors.textColorLight, isBold: true), ], ), ], diff --git a/lib/presentation/appointments/widgets/hospital_bottom_sheet/hospital_list_items.dart b/lib/presentation/appointments/widgets/hospital_bottom_sheet/hospital_list_items.dart index 26b4526f..8b395e7b 100644 --- a/lib/presentation/appointments/widgets/hospital_bottom_sheet/hospital_list_items.dart +++ b/lib/presentation/appointments/widgets/hospital_bottom_sheet/hospital_list_items.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/core/utils/utils.dart'; import 'package:hmg_patient_app_new/extensions/string_extensions.dart'; import 'package:hmg_patient_app_new/extensions/widget_extensions.dart'; import 'package:hmg_patient_app_new/features/my_appointments/models/resp_models/doctor_list_api_response.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'; @@ -95,7 +97,7 @@ class HospitalListItem extends StatelessWidget { child: Row( children: [ AppCustomChipWidget( - labelText: "Distance not available", + labelText: LocaleKeys.noDistance.tr(), textColor: AppColors.blackColor, ), // SizedBox( diff --git a/lib/presentation/appointments/widgets/region_bottomsheet/region_list_item.dart b/lib/presentation/appointments/widgets/region_bottomsheet/region_list_item.dart index 99f31be6..7f88074c 100644 --- a/lib/presentation/appointments/widgets/region_bottomsheet/region_list_item.dart +++ b/lib/presentation/appointments/widgets/region_bottomsheet/region_list_item.dart @@ -68,7 +68,7 @@ class RegionListItem extends StatelessWidget { text: TextSpan( text: count, style: TextStyle(fontSize: 12.h, fontWeight: FontWeight.w700, color: AppColors.blackColor), - children: [TextSpan(text: title, style: TextStyle(fontSize: 12.h, fontWeight: FontWeight.w500, color: AppColors.blackColor))])), + children: [TextSpan(text: title, style: TextStyle(fontSize: 12.h, fontWeight: FontWeight.w600, color: AppColors.blackColor))])), ); } @@ -93,7 +93,7 @@ class RegionListItem extends StatelessWidget { subTitle, style: TextStyle( fontSize: 12.h, - fontWeight: FontWeight.w500, + fontWeight: FontWeight.w600, color: AppColors.greyTextColor, ), ), diff --git a/lib/presentation/appointments/widgets/region_bottomsheet/region_list_widget.dart b/lib/presentation/appointments/widgets/region_bottomsheet/region_list_widget.dart index ef80ec7b..b689162c 100644 --- a/lib/presentation/appointments/widgets/region_bottomsheet/region_list_widget.dart +++ b/lib/presentation/appointments/widgets/region_bottomsheet/region_list_widget.dart @@ -133,7 +133,7 @@ class _RegionBottomSheetBodyState extends State { // children: [ // LocaleKeys.sortByLocation.tr(context: context).toText14(isBold: true), // // SizedBox(height: 4.h), - // LocaleKeys.sortByNearestLocation.tr(context: context).toText11(color: AppColors.textColorLight, weight: FontWeight.w500), + // LocaleKeys.sortByNearestLocation.tr(context: context).toText11(color: AppColors.textColorLight, isBold: true), // ], // ), // ], diff --git a/lib/presentation/ask_doctor/widgets/ask_doctor_appointment_card.dart b/lib/presentation/ask_doctor/widgets/ask_doctor_appointment_card.dart index d58b75e9..4ebd9b74 100644 --- a/lib/presentation/ask_doctor/widgets/ask_doctor_appointment_card.dart +++ b/lib/presentation/ask_doctor/widgets/ask_doctor_appointment_card.dart @@ -203,7 +203,7 @@ class AskDoctorAppointmentCard extends StatelessWidget { borderColor: AppColors.secondaryLightRedColor, textColor: AppColors.primaryRedColor, fontSize: 14.f, - fontWeight: FontWeight.w500, + fontWeight: FontWeight.w600, borderRadius: 12.r, padding: EdgeInsets.symmetric(horizontal: 10.w), // height: isTablet || isFoldable ? 46.h : 40.h, diff --git a/lib/presentation/ask_doctor/widgets/doctor_response_transactions.dart b/lib/presentation/ask_doctor/widgets/doctor_response_transactions.dart index 33b544d7..59516d15 100644 --- a/lib/presentation/ask_doctor/widgets/doctor_response_transactions.dart +++ b/lib/presentation/ask_doctor/widgets/doctor_response_transactions.dart @@ -77,7 +77,7 @@ class DoctorResponseTransactions extends StatelessWidget { borderColor: AppColors.greyColor, textColor: AppColors.textColor, fontSize: 14.f, - fontWeight: FontWeight.w500, + fontWeight: FontWeight.w600, borderRadius: 10.r, height: 40.h, ).paddingSymmetrical(16.w, 0.h), @@ -93,7 +93,7 @@ class DoctorResponseTransactions extends StatelessWidget { color: AppColors.infoColor.withAlpha(20), borderRadius: BorderRadius.circular(8.r), ), - child: doctorResponseModel.requestTypeDescription!.toText12(color: AppColors.infoColor, fontWeight: FontWeight.w500), + child: doctorResponseModel.requestTypeDescription!.toText12(color: AppColors.infoColor, isBold: true), ), ], ), @@ -115,7 +115,7 @@ class DoctorResponseTransactions extends StatelessWidget { color: AppColors.infoColor.withAlpha(20), borderRadius: BorderRadius.circular(8.r), ), - child: doctorResponseModel.transactions![index].doctorResponse!.toText12(color: AppColors.textColor, fontWeight: FontWeight.w500), + child: doctorResponseModel.transactions![index].doctorResponse!.toText12(color: AppColors.textColor, isBold: true), ), 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 62d0c9e2..d559ebbc 100644 --- a/lib/presentation/authentication/login.dart +++ b/lib/presentation/authentication/login.dart @@ -116,7 +116,7 @@ class LoginScreenState extends State { child: RichText( textAlign: TextAlign.center, text: TextSpan( - style: context.dynamicTextStyle(color: AppColors.textColor, fontSize: 14.f, height: 26 / 16, fontWeight: FontWeight.w500), + style: context.dynamicTextStyle(color: AppColors.textColor, fontSize: 14.f, height: 26 / 16, fontWeight: FontWeight.w600,), children: [ TextSpan(text: LocaleKeys.dontHaveAccount.tr(context: context), style: context.dynamicTextStyle()), TextSpan(text: " "), @@ -126,7 +126,7 @@ class LoginScreenState extends State { color: AppColors.primaryRedColor, fontSize: 14.f, // Adjusted to sizer unit height: 26 / 16, // Ratio - fontWeight: FontWeight.w500), + fontWeight: FontWeight.w600,), recognizer: TapGestureRecognizer() ..onTap = () { Navigator.of(context).push( diff --git a/lib/presentation/authentication/quick_login.dart b/lib/presentation/authentication/quick_login.dart index 19e90d64..319402b0 100644 --- a/lib/presentation/authentication/quick_login.dart +++ b/lib/presentation/authentication/quick_login.dart @@ -49,13 +49,13 @@ class QuickLoginState extends State { ], ), Utils.showLottie(context: context, assetPath: AppAnimations.checkmark, width: 120, height: 120, repeat: true), - LocaleKeys.allSet.tr(context: context).toText16(textAlign: TextAlign.center, weight: FontWeight.w500) + LocaleKeys.allSet.tr(context: context).toText16(textAlign: TextAlign.center, isBold: true) // Text( // ' TranslationBase.of(context).allSet', // textAlign: TextAlign.center, // style: context.dynamicTextStyle( // fontSize: 16, - // fontWeight: FontWeight.w500, + // isBold: true, // color: Colors.black, // ), // ), diff --git a/lib/presentation/authentication/register.dart b/lib/presentation/authentication/register.dart index 535cdff1..2f181e16 100644 --- a/lib/presentation/authentication/register.dart +++ b/lib/presentation/authentication/register.dart @@ -160,7 +160,7 @@ class _RegisterNew extends State { children: [ Text( LocaleKeys.iAcceptThe.tr(context: context), - style: context.dynamicTextStyle(fontSize: 14.f, fontWeight: FontWeight.w500, color: Color(0xFF2E3039)), + style: context.dynamicTextStyle(fontSize: 14.f, fontWeight: FontWeight.w600, color: Color(0xFF2E3039)), ), GestureDetector( onTap: () { @@ -173,7 +173,7 @@ class _RegisterNew extends State { " ${LocaleKeys.termsConditoins.tr(context: context)}", style: context.dynamicTextStyle( fontSize: 14.f, - fontWeight: FontWeight.w500, + fontWeight: FontWeight.w600, color: AppColors.primaryRedColor, decoration: TextDecoration.underline, decorationColor: AppColors.primaryRedBorderColor, @@ -185,7 +185,7 @@ class _RegisterNew extends State { // Expanded( // child: Text( // LocaleKeys.iAcceptTermsConditions.tr(context: context).split("the").first, - // style: context.dynamicTextStyle(fontSize: 14.fSize, fontWeight: FontWeight.w500, color: Color(0xFF2E3039)), + // style: context.dynamicTextStyle(fontSize: 14.fSize, isBold: true, color: Color(0xFF2E3039)), // ), // ), ], @@ -222,7 +222,7 @@ class _RegisterNew extends State { color: Colors.black, fontSize: 16.f, height: 26 / 16, - fontWeight: FontWeight.w500, + fontWeight: FontWeight.w600, ), children: [ TextSpan(text: LocaleKeys.alreadyHaveAccount.tr(context: context), style: context.dynamicTextStyle()), @@ -233,7 +233,7 @@ class _RegisterNew extends State { color: AppColors.primaryRedColor, fontSize: 16.f, height: 26 / 16, - fontWeight: FontWeight.w500, + fontWeight: FontWeight.w600, ), recognizer: TapGestureRecognizer() ..onTap = () { diff --git a/lib/presentation/authentication/register_step2.dart b/lib/presentation/authentication/register_step2.dart index dc87aa1d..f01dd036 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(context: context).toText26(color: AppColors.textColor, weight: FontWeight.w600, letterSpacing: -2), + LocaleKeys.personalDetailsVerification.tr(context: context).toText26(color: AppColors.textColor, isBold: true, letterSpacing: -2), SizedBox(height: 24.h), // Insurance Card Section diff --git a/lib/presentation/authentication/saved_login_screen.dart b/lib/presentation/authentication/saved_login_screen.dart index dcb2c765..6d326939 100644 --- a/lib/presentation/authentication/saved_login_screen.dart +++ b/lib/presentation/authentication/saved_login_screen.dart @@ -8,6 +8,7 @@ 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/authentication/authentication_view_model.dart'; import 'package:hmg_patient_app_new/generated/locale_keys.g.dart'; import 'package:hmg_patient_app_new/presentation/authentication/login.dart'; @@ -66,6 +67,7 @@ class _SavedLogin extends State { appBar: CustomAppBar( onBackPressed: () { Navigator.of(context).pop(); + authVm.setIsInitialLoginNavigated(true); }, onLanguageChanged: (value) { context.setLocale(value == 'en' ? Locale('en', 'US') : Locale('ar', 'SA')); @@ -88,15 +90,11 @@ class _SavedLogin extends State { : SizedBox(), SizedBox(height: 24.h), Container( + padding: EdgeInsets.all(16.h), - decoration: BoxDecoration( - color: AppColors.whiteColor, - border: Border.all(color: AppColors.whiteColor), - borderRadius: BorderRadius.circular(24.h), - boxShadow: [ + decoration: RoundedRectangleBorder().toSmoothCornerDecoration(color: AppColors.whiteColor, borderRadius: 20.h, hasShadow: false, isCustomShadow: [ BoxShadow(color: Color(0x0D000000), blurRadius: 16.h, offset: Offset(0, 0), spreadRadius: 5.h), - ], - ), + ]), child: Column( children: [ // Last login info - show WhatsApp only if isOther AND loginType is SMS @@ -144,7 +142,7 @@ class _SavedLogin extends State { borderColor: AppColors.primaryRedColor, textColor: Colors.white, fontSize: 12.f, - fontWeight: FontWeight.w500, + fontWeight: FontWeight.w600, borderRadius: 12.r, height: 40.h, padding: EdgeInsets.symmetric(vertical: 10.h), @@ -161,7 +159,7 @@ class _SavedLogin extends State { padding: EdgeInsets.symmetric(horizontal: 16.w), child: Text( LocaleKeys.oR.tr(), - style: context.dynamicTextStyle(fontSize: 16.f, fontWeight: FontWeight.w500), + style: context.dynamicTextStyle(fontSize: 16.f, fontWeight: FontWeight.w600,), ), ), SizedBox(height: 24.h), @@ -290,6 +288,7 @@ class _SavedLogin extends State { child: CustomButton( text: LocaleKeys.guest.tr(), onPressed: () { + authVm.setIsInitialLoginNavigated(true); Navigator.pushAndRemoveUntil( context, CustomPageRoute( @@ -304,7 +303,7 @@ class _SavedLogin extends State { borderColor: Color(0xffFEE9EA), textColor: Color(0xffED1C2B), fontSize: 16.f, - fontWeight: FontWeight.w500, + fontWeight: FontWeight.w600, borderRadius: 12.r, padding: EdgeInsets.fromLTRB(0, 10.h, 0, 10.h), // icon: "assets/images/svg/apple-finder.svg", @@ -329,7 +328,7 @@ class _SavedLogin extends State { borderColor: Color(0xffFEE9EA), textColor: Color(0xffED1C2B), fontSize: 15.f, - fontWeight: FontWeight.w500, + fontWeight: FontWeight.w600, borderRadius: 12.r, padding: EdgeInsets.fromLTRB(0, 10.h, 0, 10.h), // icon: "assets/images/svg/apple-finder.svg", diff --git a/lib/presentation/blood_donation/blood_donation_page.dart b/lib/presentation/blood_donation/blood_donation_page.dart index 7c6c625f..c6767c2d 100644 --- a/lib/presentation/blood_donation/blood_donation_page.dart +++ b/lib/presentation/blood_donation/blood_donation_page.dart @@ -56,11 +56,11 @@ class _BloodDonationPageState extends State { child: CollapsingListView( title: LocaleKeys.bloodDonation.tr(), trailing: CustomButton( - text: "Book", + text: LocaleKeys.book.tr(context: context), onPressed: () { if (bloodDonationVM.isUserAuthanticated()) { bloodDonationVM.fetchHospitalsList().then((value) { - showCommonBottomSheetWithoutHeight(context, title: "Select Hospital", isDismissible: false, child: Consumer(builder: (_, data, __) { + showCommonBottomSheetWithoutHeight(context, title: LocaleKeys.selectHospital.tr(context: context), isDismissible: false, child: Consumer(builder: (_, data, __) { return HospitalBottomSheetBodySelection( isHideTitle: true, onUserHospitalSelection: (BdGetProjectsHaveBdClinic userChoice) { @@ -146,11 +146,11 @@ class _BloodDonationPageState extends State { Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ - LocaleKeys.city.tr().toText16(color: AppColors.textColor, weight: FontWeight.w500), + LocaleKeys.city.tr().toText16(color: AppColors.textColor, isBold: true), (appState.isArabic() ? (bloodDonationVM.selectedCity?.descriptionN ?? LocaleKeys.select.tr()) : bloodDonationVM.selectedCity?.description ?? LocaleKeys.select.tr(context: context)) - .toText14(color: AppColors.greyTextColor, weight: FontWeight.w500), + .toText14(color: AppColors.greyTextColor, isBold: true), ], ), ], @@ -174,11 +174,11 @@ class _BloodDonationPageState extends State { Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ - LocaleKeys.gender.tr().toText16(color: AppColors.textColor, weight: FontWeight.w500), + LocaleKeys.gender.tr().toText16(color: AppColors.textColor, isBold: true), (appState.isArabic() ? (bloodDonationVM.selectedGender?.typeAr ?? LocaleKeys.select.tr()) : bloodDonationVM.selectedGender?.type ?? LocaleKeys.select.tr(context: context)) - .toText14(color: AppColors.greyTextColor, weight: FontWeight.w500), + .toText14(color: AppColors.greyTextColor, isBold: true), ], ), ], @@ -205,12 +205,12 @@ class _BloodDonationPageState extends State { Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ - LocaleKeys.bloodType.tr().toText16(color: AppColors.textColor, weight: FontWeight.w500), - // bloodDonationVM.selectedBloodType?.toText14(color: AppColors.greyTextColor, weight: FontWeight.w500), + LocaleKeys.bloodType.tr().toText16(color: AppColors.textColor, isBold: true), + // bloodDonationVM.selectedBloodType?.toText14(color: AppColors.greyTextColor, isBold: true), (appState.isArabic() ? (bloodDonationVM.selectedBloodType ?? LocaleKeys.select.tr()) : bloodDonationVM.selectedBloodType ?? LocaleKeys.select.tr(context: context)) - .toText14(color: AppColors.greyTextColor, weight: FontWeight.w500) + .toText14(color: AppColors.greyTextColor, isBold: true) ], ), ], @@ -224,6 +224,109 @@ class _BloodDonationPageState extends State { child: SelectBloodGroupWidget(isArabic: appState.isArabic(), bloodDonationViewModel: bloodDonationVM), callBackFunc: () {}); }), + SizedBox(height: 24.h), + Container( + padding: EdgeInsets.all(16.h), + decoration: RoundedRectangleBorder().toSmoothCornerDecoration( + color: AppColors.bgRedLightColor.withValues(alpha: 0.1), + borderRadius: 12.r, + hasShadow: false, + ), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Row( + children: [ + Container( + padding: EdgeInsets.all(8.h), + decoration: BoxDecoration( + color: AppColors.primaryRedColor.withValues(alpha: 0.1), + borderRadius: BorderRadius.circular(8.r), + ), + child: Utils.buildSvgWithAssets( + icon: AppAssets.blood_icon, + width: 20.h, + height: 20.h, + iconColor: AppColors.primaryRedColor, + ), + ), + SizedBox(width: 12.w), + Expanded( + child: LocaleKeys.aboutBloodDonation.tr(context: context).toText16( + color: AppColors.primaryRedColor, + isBold: true, + ), + ), + ], + ), + SizedBox(height: 12.h), + LocaleKeys.bloodDonationDescription.tr(context: context).toText14( + color: AppColors.greyTextColor, + isBold: true, + ), + SizedBox(height: 12.h), + Row( + children: [ + Container( + width: 4.w, + height: 4.h, + decoration: BoxDecoration( + color: AppColors.primaryRedColor, + shape: BoxShape.circle, + ), + ), + SizedBox(width: 8.w), + Expanded( + child: LocaleKeys.bloodDonationBenefit1.tr(context: context).toText13( + color: AppColors.textColor, + isBold: true, + ), + ), + ], + ), + SizedBox(height: 8.h), + Row( + children: [ + Container( + width: 4.w, + height: 4.h, + decoration: BoxDecoration( + color: AppColors.primaryRedColor, + shape: BoxShape.circle, + ), + ), + SizedBox(width: 8.w), + Expanded( + child: LocaleKeys.bloodDonationBenefit2.tr(context: context).toText13( + color: AppColors.textColor, + isBold: true, + ), + ), + ], + ), + SizedBox(height: 8.h), + Row( + children: [ + Container( + width: 4.w, + height: 4.h, + decoration: BoxDecoration( + color: AppColors.primaryRedColor, + shape: BoxShape.circle, + ), + ), + SizedBox(width: 8.w), + Expanded( + child: LocaleKeys.bloodDonationBenefit3.tr(context: context).toText13( + color: AppColors.textColor, + isBold: true, + ), + ), + ], + ), + ], + ), + ), ], ), ), @@ -264,7 +367,7 @@ class _BloodDonationPageState extends State { children: [ Text( "${LocaleKeys.iAcceptThe.tr()} ", - style: context.dynamicTextStyle(fontSize: 14.f, fontWeight: FontWeight.w500, color: Color(0xFF2E3039)), + style: context.dynamicTextStyle(fontSize: 14.f, fontWeight: FontWeight.w600, color: Color(0xFF2E3039)), ), GestureDetector( onTap: () { @@ -282,7 +385,7 @@ class _BloodDonationPageState extends State { LocaleKeys.termsConditoins.tr(), style: context.dynamicTextStyle( fontSize: 14.f, - fontWeight: FontWeight.w500, + fontWeight: FontWeight.w600, color: AppColors.primaryRedColor, decoration: TextDecoration.underline, decorationColor: AppColors.primaryRedBorderColor, @@ -294,7 +397,7 @@ class _BloodDonationPageState extends State { // Expanded( // child: Text( // LocaleKeys.iAcceptTermsConditions.tr().split("the").first, - // style: context.dynamicTextStyle(fontSize: 14.fSize, fontWeight: FontWeight.w500, color: Color(0xFF2E3039)), + // style: context.dynamicTextStyle(fontSize: 14.fSize, isBold: true, color: Color(0xFF2E3039)), // ), // ), ], @@ -365,7 +468,7 @@ class _BloodDonationPageState extends State { borderColor: AppColors.primaryRedColor, textColor: AppColors.whiteColor, fontSize: 16.f, - fontWeight: FontWeight.w500, + isBold: true, borderRadius: 12.r, padding: EdgeInsets.symmetric(horizontal: 10.w), height: 50.h, diff --git a/lib/presentation/book_appointment/book_appointment_page.dart b/lib/presentation/book_appointment/book_appointment_page.dart index 1ac49f85..aff3e275 100644 --- a/lib/presentation/book_appointment/book_appointment_page.dart +++ b/lib/presentation/book_appointment/book_appointment_page.dart @@ -117,7 +117,7 @@ class _BookAppointmentPageState extends State { SizedBox(height: 24.h), getSelectedTabData(bookAppointmentsVM.selectedTabIndex), SizedBox(height: 24.h), - if (appState.isAuthenticated) ...[ + if (appState.isAuthenticated && bookAppointmentsVM.selectedTabIndex == 0) ...[ Consumer(builder: (context, myAppointmentsVM, child) { return myAppointmentsVM.isPatientMyDoctorsLoading ? SizedBox( @@ -138,7 +138,7 @@ class _BookAppointmentPageState extends State { fit: BoxFit.cover, ).circle(100).toShimmer2(isShow: true, radius: 50.r), SizedBox(height: 8.h), - ("Dr. John").toString().toText12(fontWeight: FontWeight.w500, isCenter: true, maxLine: 2).toShimmer2(isShow: true), + ("Dr. John").toString().toText12(isBold: true, isCenter: true, maxLine: 2).toShimmer2(isShow: true), ], ); }, @@ -157,7 +157,7 @@ class _BookAppointmentPageState extends State { LocaleKeys.recentVisits.tr(context: context).toText18(isBold: true).paddingSymmetrical(24.w, 0.h), Row( children: [ - LocaleKeys.viewAll.tr(context: context).toText14(color: AppColors.primaryRedColor, weight: FontWeight.w500), + LocaleKeys.viewAll.tr(context: context).toText14(color: AppColors.primaryRedColor, isBold: true), SizedBox(width: 2.h), Icon(Icons.arrow_forward_ios, color: AppColors.primaryRedColor, size: 14.h), ], @@ -201,7 +201,7 @@ class _BookAppointmentPageState extends State { width: 80.w, child: (myAppointmentsVM.patientMyDoctorsList[index].doctorName) .toString() - .toText12(fontWeight: FontWeight.w500, isCenter: true, maxLine: 2) + .toText12(isBold: true, isCenter: true, maxLine: 2) .toShimmer2(isShow: false), ), ], @@ -217,7 +217,7 @@ class _BookAppointmentPageState extends State { LoaderBottomSheet.hideLoader(); Navigator.of(context).push( CustomPageRoute( - page: DoctorProfilePage(), + page: DoctorProfilePage(isDoctorAllowedToBook: !(myAppointmentsVM.patientMyDoctorsList[index].isLiveCareClinic ?? false)), ), ); }, onError: (err) { @@ -249,13 +249,14 @@ class _BookAppointmentPageState extends State { crossAxisAlignment: CrossAxisAlignment.start, children: [ SizedBox(height: 24.h), - LocaleKeys.favouriteDoctors.tr(context: context).toText18(isBold: true).paddingSymmetrical(24.w, 0.h), + LocaleKeys.favouriteList.tr(context: context).toText18(isBold: true).paddingSymmetrical(24.w, 0.h), SizedBox(height: 16.h), SizedBox( height: 110.h, child: ListView.separated( scrollDirection: Axis.horizontal, - itemCount: 3, // Show 3 shimmer items + itemCount: 3, + // Show 3 shimmer items shrinkWrap: true, padding: EdgeInsets.only(left: 24.w, right: 24.w), itemBuilder: (context, index) { @@ -311,7 +312,7 @@ class _BookAppointmentPageState extends State { child: Column( children: [ Center( - child: LocaleKeys.noFavouriteDoctors.tr(context: context).toText14(color: AppColors.greyTextColor, weight: FontWeight.w500), + child: LocaleKeys.noFavouriteDoctors.tr(context: context).toText14(color: AppColors.greyTextColor, isBold: true), ), SizedBox(height: 12.h), CustomButton( @@ -323,7 +324,7 @@ class _BookAppointmentPageState extends State { borderColor: Color(0xffFEE9EA), textColor: Color(0xffED1C2B), fontSize: 14.f, - fontWeight: FontWeight.w500, + isBold: true, padding: EdgeInsets.fromLTRB(10.h, 0, 10.h, 0), icon: AppAssets.add_icon, iconColor: AppColors.primaryRedColor, @@ -339,16 +340,35 @@ class _BookAppointmentPageState extends State { crossAxisAlignment: CrossAxisAlignment.start, children: [ SizedBox(height: 24.h), - LocaleKeys.favouriteDoctors.tr(context: context).toText18(isBold: true).paddingSymmetrical(24.w, 0.h), - SizedBox(height: 16.h), - SizedBox( + LocaleKeys.favourites.tr(context: context).toText18(isBold: true).paddingSymmetrical(24.w, 0.h), + SizedBox(height: 16.h), + SizedBox( height: 110.h, child: ListView.separated( scrollDirection: Axis.horizontal, - itemCount: myAppointmentsVM.patientFavouriteDoctorsList.length, + itemCount: myAppointmentsVM.patientFavouriteDoctorsList.length + 1, shrinkWrap: true, padding: EdgeInsets.only(left: 24.w, right: 24.w), itemBuilder: (context, index) { + // Last item: static "Add" button + if (index == myAppointmentsVM.patientFavouriteDoctorsList.length) { + return Column( + crossAxisAlignment: CrossAxisAlignment.center, + children: [ + Utils.buildSvgWithAssets(icon: AppAssets.add_new_family_icon, height: 64.h, width: 64.h, applyThemeColor: false), + SizedBox( + width: 80.w, + child: LocaleKeys.add.tr(context: context).toText12( + color: AppColors.textColor, + isBold: true, + isCenter: true, + ).paddingOnly(top: 4.h), + ), + ], + ).onPress(() { + Navigator.of(context).push(CustomPageRoute(page: SearchDoctorByName())); + }); + } return AnimationConfiguration.staggeredList( position: index, duration: const Duration(milliseconds: 1000), @@ -370,7 +390,7 @@ class _BookAppointmentPageState extends State { width: 80.w, child: (myAppointmentsVM.patientFavouriteDoctorsList[index].doctorName) .toString() - .toText12(fontWeight: FontWeight.w500, isCenter: true, maxLine: 2) + .toText12(isBold: true, isCenter: true, maxLine: 2) .toShimmer2(isShow: false), ), ], @@ -386,7 +406,7 @@ class _BookAppointmentPageState extends State { LoaderBottomSheet.hideLoader(); Navigator.of(context).push( CustomPageRoute( - page: DoctorProfilePage(), + page: DoctorProfilePage(isDoctorAllowedToBook: true), ), ); }, onError: (err) { @@ -407,8 +427,8 @@ class _BookAppointmentPageState extends State { separatorBuilder: (BuildContext cxt, int index) => SizedBox(width: 8.h), ), ), - ], - ); + ], + ); }), ], ], @@ -451,8 +471,8 @@ class _BookAppointmentPageState extends State { Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ - LocaleKeys.searchByClinic.tr(context: context).toText14(color: AppColors.textColor, weight: FontWeight.w500), - LocaleKeys.tapToSelectClinic.tr(context: context).toText12(color: AppColors.primaryRedColor, fontWeight: FontWeight.w500), + LocaleKeys.searchByClinic.tr(context: context).toText14(color: AppColors.textColor, isBold: true), + LocaleKeys.tapToSelectClinic.tr(context: context).toText12(color: AppColors.primaryRedColor, isBold: true), ], ), ], @@ -483,8 +503,8 @@ class _BookAppointmentPageState extends State { Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ - LocaleKeys.searchByDoctor.tr(context: context).toText14(color: AppColors.textColor, weight: FontWeight.w500), - LocaleKeys.tapToSelect.tr(context: context).toText12(color: AppColors.primaryRedColor, fontWeight: FontWeight.w500), + LocaleKeys.searchByDoctor.tr(context: context).toText14(color: AppColors.textColor, isBold: true), + LocaleKeys.tapToSelect.tr(context: context).toText12(color: AppColors.primaryRedColor, isBold: true), ], ), ], @@ -513,8 +533,8 @@ class _BookAppointmentPageState extends State { Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ - LocaleKeys.searchByRegion.tr(context: context).toText14(color: AppColors.textColor, weight: FontWeight.w500), - LocaleKeys.centralRegion.tr(context: context).toText12(color: AppColors.primaryRedColor, fontWeight: FontWeight.w500), + LocaleKeys.searchByRegion.tr(context: context).toText14(color: AppColors.textColor, isBold: true), + LocaleKeys.centralRegion.tr(context: context).toText12(color: AppColors.primaryRedColor, isBold: true), ], ), ], @@ -557,8 +577,8 @@ class _BookAppointmentPageState extends State { Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ - LocaleKeys.immediateConsultation.tr(context: context).toText14(color: AppColors.textColor, weight: FontWeight.w500), - LocaleKeys.tapToSelectClinic.tr(context: context).toText12(color: AppColors.primaryRedColor, fontWeight: FontWeight.w500), + LocaleKeys.immediateConsultation.tr(context: context).toText14(color: AppColors.textColor, isBold: true), + LocaleKeys.tapToSelectClinic.tr(context: context).toText12(color: AppColors.primaryRedColor, isBold: true), ], ), ], @@ -599,8 +619,8 @@ class _BookAppointmentPageState extends State { Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ - LocaleKeys.scheduledConsultation.tr(context: context).toText14(color: AppColors.textColor, weight: FontWeight.w500), - LocaleKeys.tapToSelectClinic.tr(context: context).toText12(color: AppColors.primaryRedColor, fontWeight: FontWeight.w500), + LocaleKeys.scheduledConsultation.tr(context: context).toText14(color: AppColors.textColor, isBold: true), + LocaleKeys.tapToSelectClinic.tr(context: context).toText12(color: AppColors.primaryRedColor, isBold: true), ], ), ], @@ -640,10 +660,10 @@ class _BookAppointmentPageState extends State { mainAxisSize: MainAxisSize.min, crossAxisAlignment: CrossAxisAlignment.start, children: [ - LocaleKeys.notSureHelpMeChooseClinic.tr(context: context).toText16(weight: FontWeight.w600, color: AppColors.textColor), + LocaleKeys.notSureHelpMeChooseClinic.tr(context: context).toText16(isBold: true, color: AppColors.textColor), SizedBox(height: 8.h), LocaleKeys.mentionYourSymptomsAndFindDoctors.tr(context: context).toText12( - fontWeight: FontWeight.w500, + isBold: true, color: AppColors.greyTextColor, ), ], @@ -803,7 +823,7 @@ class _BookAppointmentPageState extends State { getTitle(AppointmentViaRegionViewmodel data) { if (data.selectedRegionId == null) { - return LocaleKeys.selectRegion.tr(context: context).toText20(weight: FontWeight.w600); + return LocaleKeys.selectRegion.tr(context: context).toText20(isBold: true); } else { return Transform.flip( flipX: data.isArabic ? true : false, @@ -831,7 +851,7 @@ class _BookAppointmentPageState extends State { crossAxisAlignment: CrossAxisAlignment.start, children: [ LocaleKeys.immediateService.tr(context: context).toText18(color: AppColors.textColor, isBold: true), - LocaleKeys.noNeedToWaitGetMedicalConsultation.tr(context: context).toText14(color: AppColors.greyTextColor, weight: FontWeight.w500), + LocaleKeys.noNeedToWaitGetMedicalConsultation.tr(context: context).toText14(color: AppColors.greyTextColor, isBold: true), ], ), ), @@ -847,7 +867,7 @@ class _BookAppointmentPageState extends State { crossAxisAlignment: CrossAxisAlignment.start, children: [ LocaleKeys.noVisitRequired.tr(context: context).toText18(color: AppColors.textColor, isBold: true), - LocaleKeys.livecarePoint5.tr(context: context).toText14(color: AppColors.greyTextColor, weight: FontWeight.w500), + LocaleKeys.livecarePoint5.tr(context: context).toText14(color: AppColors.greyTextColor, isBold: true), ], ), ), @@ -863,7 +883,7 @@ class _BookAppointmentPageState extends State { crossAxisAlignment: CrossAxisAlignment.start, children: [ LocaleKeys.doctorWillContact.tr(context: context).toText18(color: AppColors.textColor, isBold: true), - LocaleKeys.specialisedDoctorWillContactYou.tr(context: context).toText14(color: AppColors.greyTextColor, weight: FontWeight.w500), + LocaleKeys.specialisedDoctorWillContactYou.tr(context: context).toText14(color: AppColors.greyTextColor, isBold: true), ], ), ), @@ -879,7 +899,7 @@ class _BookAppointmentPageState extends State { crossAxisAlignment: CrossAxisAlignment.start, children: [ LocaleKeys.freeMedicineDelivery.tr(context: context).toText18(color: AppColors.textColor, isBold: true), - LocaleKeys.offersFreeMedicineDelivery.tr(context: context).toText14(color: AppColors.greyTextColor, weight: FontWeight.w500), + LocaleKeys.offersFreeMedicineDelivery.tr(context: context).toText14(color: AppColors.greyTextColor, isBold: true), ], ), ), @@ -895,7 +915,7 @@ class _BookAppointmentPageState extends State { borderColor: AppColors.primaryRedColor, textColor: AppColors.whiteColor, fontSize: 16.f, - fontWeight: FontWeight.w500, + isBold: true, borderRadius: 12.r, padding: EdgeInsets.fromLTRB(10, 0, 10, 0), height: 50.h, @@ -918,7 +938,7 @@ class _BookAppointmentPageState extends State { 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,), + LocaleKeys.mentionYourSymptomsAndFindDoctors.tr(context: context).toText12(color: AppColors.greyTextColor, isBold: true,), SizedBox(height: 24.h), CustomButton( text: LocaleKeys.yesPleaseINeedHelp.tr(context: context), @@ -930,7 +950,7 @@ class _BookAppointmentPageState extends State { borderColor: AppColors.primaryRedColor, textColor: AppColors.whiteColor, fontSize: 16.f, - fontWeight: FontWeight.w500, + isBold: true, borderRadius: 12.r, // padding: EdgeInsets.fromLTRB(10, 0, 10, 0), height: 56.h, @@ -945,7 +965,7 @@ class _BookAppointmentPageState extends State { borderColor: AppColors.chipSecondaryLightRedColor, textColor: AppColors.primaryRedColor, fontSize: 16.f, - fontWeight: FontWeight.w500, + isBold: true, borderRadius: 12.r, // padding: EdgeInsets.fromLTRB(10, 0, 10, 0), height: 56.h, diff --git a/lib/presentation/book_appointment/doctor_filter/doctors_filter.dart b/lib/presentation/book_appointment/doctor_filter/doctors_filter.dart index 9cfcc1f8..02dd9b97 100644 --- a/lib/presentation/book_appointment/doctor_filter/doctors_filter.dart +++ b/lib/presentation/book_appointment/doctor_filter/doctors_filter.dart @@ -60,7 +60,7 @@ class DoctorsFilters extends StatelessWidget{ LocaleKeys.filters.tr(), style:TextStyle( fontFamily: 'Poppins', - fontWeight: FontWeight.w600, + fontWeight: FontWeight.w600, fontSize: 27.f, color: AppColors.textColor, letterSpacing: -1 @@ -70,7 +70,7 @@ class DoctorsFilters extends StatelessWidget{ LocaleKeys.clearAllFilters.tr(), style:TextStyle( fontFamily: 'Poppins', - fontWeight: FontWeight.w500, + fontWeight: FontWeight.w600, fontSize: 14.f, color: AppColors.errorColor ) @@ -181,7 +181,7 @@ class DoctorsFilters extends StatelessWidget{ textColor: Colors.white, fontSize: 16.f, padding: EdgeInsets.zero, - fontWeight: FontWeight.w500, + fontWeight: FontWeight.w600, borderRadius: 10.r, icon: AppAssets.add_icon, iconColor: AppColors.primaryRedColor, diff --git a/lib/presentation/book_appointment/doctor_profile_page.dart b/lib/presentation/book_appointment/doctor_profile_page.dart index 90d0fc99..dbb885b3 100644 --- a/lib/presentation/book_appointment/doctor_profile_page.dart +++ b/lib/presentation/book_appointment/doctor_profile_page.dart @@ -22,7 +22,9 @@ import 'package:hmg_patient_app_new/widgets/loader/bottomsheet_loader.dart'; import 'package:provider/provider.dart'; class DoctorProfilePage extends StatelessWidget { - const DoctorProfilePage({super.key}); + const DoctorProfilePage({super.key, required this.isDoctorAllowedToBook}); + + final bool isDoctorAllowedToBook; @override Widget build(BuildContext context) { @@ -54,11 +56,9 @@ class DoctorProfilePage extends StatelessWidget { doctorID: viewModel.doctorsProfileResponseModel.doctorID ?? 0, isActive: viewModel.isFavouriteDoctor, onSuccess: (response) { + Utils.showToast(viewModel.isFavouriteDoctor ? LocaleKeys.doctorAddedToFavourite.tr(context: context) : LocaleKeys.doctorRemovedFromFavourite.tr(context: context)); // Successfully added/removed favorite - refresh the favorites list getIt.get().refreshFavouriteDoctors(); - print( - viewModel.isFavouriteDoctor ? "Doctor added to favorites" : "Doctor removed from favorites", - ); }, onError: (error) { // Revert the state on error @@ -100,7 +100,7 @@ class DoctorProfilePage extends StatelessWidget { ), (bookAppointmentsViewModel.doctorsProfileResponseModel.specialty!.isNotEmpty ? bookAppointmentsViewModel.doctorsProfileResponseModel.specialty!.first : "") .toString() - .toText18(weight: FontWeight.w500, color: AppColors.primaryRedColor), + .toText18(isBold: true, color: AppColors.primaryRedColor), ], ), ], @@ -139,7 +139,7 @@ class DoctorProfilePage extends StatelessWidget { children: [ Utils.buildSvgWithAssets(icon: AppAssets.doctor_profile_rating_icon, width: 48.w, height: 48.h, fit: BoxFit.contain, applyThemeColor: false), SizedBox(height: 16.h), - LocaleKeys.ratings.tr(context: context).toText12(fontWeight: FontWeight.w500, color: AppColors.greyTextColor), + LocaleKeys.ratings.tr(context: context).toText12(isBold: true, color: AppColors.greyTextColor), bookAppointmentsViewModel.doctorsProfileResponseModel.decimalDoctorRate .toString() .toText16(isBold: true, color: AppColors.textColor, isUnderLine: true, decorationColor: AppColors.textColor, fontFamily: "Poppins"), @@ -160,7 +160,7 @@ class DoctorProfilePage extends StatelessWidget { children: [ Utils.buildSvgWithAssets(icon: AppAssets.doctor_profile_reviews_icon, width: 48.w, height: 48.h, fit: BoxFit.contain, applyThemeColor: false), SizedBox(height: 16.h), - LocaleKeys.reviews.tr(context: context).toText12(fontWeight: FontWeight.w500, color: AppColors.greyTextColor), + LocaleKeys.reviews.tr(context: context).toText12(isBold: true, color: AppColors.greyTextColor), NumberFormat.decimalPattern().format(bookAppointmentsViewModel.doctorsProfileResponseModel.noOfPatientsRate ?? 0) .toText16(isBold: true, color: AppColors.textColor, isUnderLine: true, decorationColor: AppColors.textColor, fontFamily: "Poppins"), ], @@ -180,16 +180,16 @@ class DoctorProfilePage extends StatelessWidget { SizedBox(height: 16.h), Divider(color: AppColors.borderOnlyColor.withValues(alpha: 0.1), height: 1.h), SizedBox(height: 16.h), - LocaleKeys.information.tr(context: context).toText14(weight: FontWeight.w600, color: AppColors.textColor), + LocaleKeys.information.tr(context: context).toText14(isBold: true, color: AppColors.textColor), SizedBox(height: 6.h), - (bookAppointmentsViewModel.doctorsProfileResponseModel.doctorProfileInfo ?? "").trim().toText12(fontWeight: FontWeight.w600, color: AppColors.greyTextColor), + (bookAppointmentsViewModel.doctorsProfileResponseModel.doctorProfileInfo ?? "").trim().toText12(isBold: true, color: AppColors.greyTextColor), SizedBox(height: 24.h), ], ).paddingSymmetrical(24.h, 0.h), ), ), ), - Container( + isDoctorAllowedToBook ? Container( decoration: RoundedRectangleBorder().toSmoothCornerDecoration( color: AppColors.whiteColor, borderRadius: 24.h, @@ -259,7 +259,7 @@ class DoctorProfilePage extends StatelessWidget { borderColor: AppColors.primaryRedColor, textColor: Colors.white, fontSize: 16, - fontWeight: FontWeight.w500, + isBold: true, borderRadius: 12, padding: EdgeInsets.fromLTRB(10, 0, 10, 0), height: 50.h, @@ -267,7 +267,7 @@ class DoctorProfilePage extends StatelessWidget { iconColor: Colors.white, iconSize: 20.h, ).paddingSymmetrical(24.h, 24.h), - ), + ) : SizedBox.shrink(), ], ), ); diff --git a/lib/presentation/book_appointment/laser/widgets/body_part_listing.dart b/lib/presentation/book_appointment/laser/widgets/body_part_listing.dart index c01899cb..51d964eb 100644 --- a/lib/presentation/book_appointment/laser/widgets/body_part_listing.dart +++ b/lib/presentation/book_appointment/laser/widgets/body_part_listing.dart @@ -118,7 +118,7 @@ class BodyPartsListing extends StatelessWidget { textColor: _isSelected ? AppColors.chipPrimaryRedBorderColor : AppColors.blackColor, // labelText: "${parts[index].timeDuration!} ${LocaleKeys.mins.tr()}", richText: "${parts[index].timeDuration!} ${LocaleKeys.mins.tr()}".toText12( - fontWeight: FontWeight.w500, + isBold: true, color: _isSelected ? AppColors.chipPrimaryRedBorderColor : AppColors.blackColor, ), icon: AppAssets.waiting_time_clock, 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 673532ac..1228e1e4 100644 --- a/lib/presentation/book_appointment/livecare/immediate_livecare_payment_details.dart +++ b/lib/presentation/book_appointment/livecare/immediate_livecare_payment_details.dart @@ -75,14 +75,20 @@ class ImmediateLiveCarePaymentDetails extends StatelessWidget { Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ - "${appState.getAuthenticatedUser()!.firstName} ${appState.getAuthenticatedUser()!.lastName}".toText16(isBold: true), + "${appState.getAuthenticatedUser()!.firstName} ${appState.getAuthenticatedUser()!.lastName}".toText16(isBold: true, isEnglishOnly: 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( + richText: Row( + children: [ + "${appState.getAuthenticatedUser()!.age} ".toText10(color: AppColors.blackColor, isEnglishOnly: true), + LocaleKeys.yearsOld.tr(context: context).toText10(color: AppColors.blackColor), + ], + ),), AppCustomChipWidget(labelText: "${LocaleKeys.gender.tr(context: context)}: ${appState.getAuthenticatedUser()?.gender == 1 ? LocaleKeys.malE.tr(context: context) : LocaleKeys.femaleGender.tr(context: context)}"), ], ), @@ -163,7 +169,7 @@ class ImmediateLiveCarePaymentDetails extends StatelessWidget { child: Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ - LocaleKeys.insuranceExpiredOrInactive.tr(context: context).toText14(color: AppColors.primaryRedColor, weight: FontWeight.w500).paddingSymmetrical(24.h, 0.h), + LocaleKeys.insuranceExpiredOrInactive.tr(context: context).toText14(color: AppColors.primaryRedColor, isBold: true).paddingSymmetrical(24.h, 0.h), CustomButton( text: LocaleKeys.updateInsurance.tr(context: context), onPressed: () { @@ -192,7 +198,7 @@ class ImmediateLiveCarePaymentDetails extends StatelessWidget { borderColor: AppColors.secondaryLightRedBorderColor, textColor: AppColors.whiteColor, fontSize: 10, - fontWeight: FontWeight.w500, + fontWeight: FontWeight.w600, borderRadius: 8, padding: EdgeInsets.fromLTRB(15, 0, 15, 0), height: 30.h, @@ -208,9 +214,9 @@ class ImmediateLiveCarePaymentDetails extends StatelessWidget { 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() == "ريال"), + Utils.getPaymentAmountWithSymbol((immediateLiveCareVM.liveCareImmediateAppointmentFeesList.amount ?? "").toText16(isBold: true, isEnglishOnly: true), AppColors.blackColor, 13, + isSaudiCurrency: (immediateLiveCareVM.liveCareImmediateAppointmentFeesList.currency ?? "sar").toLowerCase() == "sar" || + (immediateLiveCareVM.liveCareImmediateAppointmentFeesList.currency ?? "ريال").toLowerCase() == "ريال"), ], ).paddingSymmetrical(24.h, 0.h), Row( @@ -218,9 +224,9 @@ class ImmediateLiveCarePaymentDetails extends StatelessWidget { 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() == "ريال")), + (immediateLiveCareVM.liveCareImmediateAppointmentFeesList.tax ?? "0.0").toText14(isBold: true, color: AppColors.greyTextColor, isEnglishOnly: true), AppColors.greyTextColor, 13, + isSaudiCurrency: ((immediateLiveCareVM.liveCareImmediateAppointmentFeesList.currency ?? "sar").toLowerCase() == "sar" || + (immediateLiveCareVM.liveCareImmediateAppointmentFeesList.currency ?? "ريال").toLowerCase() == "ريال")), ], ).paddingSymmetrical(24.h, 0.h), SizedBox(height: 17.h), @@ -228,9 +234,9 @@ class ImmediateLiveCarePaymentDetails extends StatelessWidget { mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ SizedBox(width: 200.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() == "ريال")), + Utils.getPaymentAmountWithSymbol((immediateLiveCareVM.liveCareImmediateAppointmentFeesList.total ?? "0.0").toText24(isBold: true, isEnglishOnly: true), AppColors.blackColor, 17, + isSaudiCurrency: ((immediateLiveCareVM.liveCareImmediateAppointmentFeesList.currency ?? "sar").toLowerCase() == "sar" || + (immediateLiveCareVM.liveCareImmediateAppointmentFeesList.currency ?? "ريال").toLowerCase() == "ريال")), ], ).paddingSymmetrical(24.h, 0.h), (immediateLiveCareVM.liveCareImmediateAppointmentFeesList.total == "0" || immediateLiveCareVM.liveCareImmediateAppointmentFeesList.total == "0.0") @@ -289,7 +295,7 @@ class ImmediateLiveCarePaymentDetails extends StatelessWidget { borderColor: AppColors.successColor, textColor: AppColors.whiteColor, fontSize: 16, - fontWeight: FontWeight.w500, + fontWeight: FontWeight.w600, borderRadius: 12, padding: EdgeInsets.fromLTRB(10, 0, 10, 0), height: 50.h, @@ -332,7 +338,7 @@ class ImmediateLiveCarePaymentDetails extends StatelessWidget { borderColor: AppColors.infoColor, textColor: AppColors.whiteColor, fontSize: 16, - fontWeight: FontWeight.w500, + fontWeight: FontWeight.w600, borderRadius: 12, padding: EdgeInsets.fromLTRB(10, 0, 10, 0), height: 50.h, @@ -358,7 +364,8 @@ class ImmediateLiveCarePaymentDetails extends StatelessWidget { bool cameraGranted = statuses[Permission.camera]?.isGranted ?? false; bool micGranted = statuses[Permission.microphone]?.isGranted ?? false; bool notifGranted = statuses[Permission.notification]?.isGranted ?? false; - bool alertWindowGranted = Platform.isAndroid ? (statuses[Permission.systemAlertWindow]?.isGranted ?? false) : true; + // bool alertWindowGranted = Platform.isAndroid ? (statuses[Permission.systemAlertWindow]?.isGranted ?? false) : true; + bool alertWindowGranted = true; // If all required permissions are already granted if (cameraGranted && micGranted && notifGranted && alertWindowGranted) return true; @@ -374,30 +381,31 @@ class ImmediateLiveCarePaymentDetails extends StatelessWidget { 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.'; + // 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.'; + final message = LocaleKeys.liveCarePermissions.tr(); await LiveCarePermissionService.instance.showOpenSettingsDialog( context, - title: "Permissions Required", + title: LocaleKeys.permissionsProfile.tr(), 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 (retry != true) return false; + // 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 (retry != true) return false; // Request only the missing permissions final newStatuses = {}; @@ -425,7 +433,8 @@ class ImmediateLiveCarePaymentDetails extends StatelessWidget { return false; } - return cameraGranted && micGranted && notifGranted && alertWindowGranted; + // return cameraGranted && micGranted && notifGranted && alertWindowGranted; + return cameraGranted && micGranted && notifGranted; } // Future askVideoCallPermission() async { 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 4018b42a..031bb0b8 100644 --- a/lib/presentation/book_appointment/livecare/immediate_livecare_payment_page.dart +++ b/lib/presentation/book_appointment/livecare/immediate_livecare_payment_page.dart @@ -234,7 +234,7 @@ class _ImmediateLiveCarePaymentPageState extends State( - valueListenable: immediateLiveCareVM.durationNotifier, - builder: (context, duration, child) { - return Column( - mainAxisAlignment: MainAxisAlignment.center, + LocaleKeys.immediateLiveCareRequest.tr(context: context).toText16(isBold: true), + SizedBox(height: 10.h), + Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Wrap( children: [ - buildTime(duration), + AppCustomChipWidget( + labelText: immediateLiveCareViewModel.patientLiveCareHistoryList.isNotEmpty ? 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.isNotEmpty ? immediateLiveCareViewModel.patientLiveCareHistoryList[0].arrivalTime : ""), + false)), ], - ); - }, + ), + Container( + decoration: RoundedRectangleBorder().toSmoothCornerDecoration( + color: AppColors.greyColor, + borderRadius: 10.h, + hasShadow: false, + ), + padding: EdgeInsets.all(6.h), + child: Lottie.asset(AppAnimations.hourGlass, repeat: true, reverse: false, frameRate: FrameRate(60), width: 40.h, height: 40.h, fit: BoxFit.fill)), + // Lottie.asset(AppAnimations.pending_loading_animation, repeat: true, reverse: false, frameRate: FrameRate(60), width: 80.h, height: 80.h, fit: BoxFit.cover), + ], ), SizedBox(height: 8.h), + SizedBox(height: 8.h), + LocaleKeys.yourTurnIsAfterPatients.tr(namedArgs: { + 'count': (immediateLiveCareViewModel.patientLiveCareHistoryList.isNotEmpty ? immediateLiveCareViewModel.patientLiveCareHistoryList[0].patCount ?? 0 : 0).toString() + }, context: context).toText14(isBold: true), + // "Your turn is after ${immediateLiveCareViewModel.patientLiveCareHistoryList[0].patCount} patients.".toText16(isBold: true), + SizedBox(height: 8.h), ], ), ), @@ -106,38 +132,31 @@ class _ImmediateLiveCarePendingRequestPageState extends State( + valueListenable: immediateLiveCareVM.durationNotifier, + builder: (context, duration, child) { + return Column( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + buildTime(duration), + ], + ); + }, + ), SizedBox(height: 8.h), ], ), ), - ) + ), ], ), ), @@ -158,7 +177,7 @@ class _ImmediateLiveCarePendingRequestPageState extends State(); + appState = getIt.get(); return Container( padding: EdgeInsets.all(16.h), decoration: RoundedRectangleBorder().toSmoothCornerDecoration( @@ -36,10 +45,13 @@ class LiveCareClinicCard extends StatelessWidget { Utils.buildSvgWithAssets(icon: AppAssets.generic_clinic_icon, width: 24.h, height: 24.h, fit: BoxFit.contain).toShimmer2(isShow: isLoading), AppCustomChipWidget( icon: AppAssets.livecare_online_icon, - iconColor: AppColors.whiteColor, - richText: (liveCareClinicListResponseModel.isOnline == 1 ? LocaleKeys.online.tr(context: context) : LocaleKeys.offline.tr()).toText10(isBold: true, color: AppColors.whiteColor), - backgroundColor: liveCareClinicListResponseModel.isOnline == 1 ? AppColors.successColor : AppColors.primaryRedColor, - textColor: AppColors.whiteColor, + iconColor: liveCareClinicListResponseModel.isOnline == 1 ? AppColors.successColor : AppColors.primaryRedColor, + richText: (liveCareClinicListResponseModel.isOnline == 1 ? LocaleKeys.online.tr(context: context) : LocaleKeys.offline.tr()).toText12( + isBold: true, + color: liveCareClinicListResponseModel.isOnline == 1 ? AppColors.successColor : AppColors.primaryRedColor, + ), + backgroundColor: AppColors.transparent, + textColor: liveCareClinicListResponseModel.isOnline == 1 ? AppColors.successColor : AppColors.primaryRedColor, ), ]), SizedBox(height: 8.h), @@ -48,12 +60,194 @@ class LiveCareClinicCard extends StatelessWidget { child: (isLoading ? "Cardiology" : (appState.isArabic() ? liveCareClinicListResponseModel.serviceNameN : liveCareClinicListResponseModel.serviceName))! .toText16(isBold: true) .toShimmer2(isShow: isLoading)), - Transform.flip( - flipX: appState.isArabic(), - child: Utils.buildSvgWithAssets(icon: AppAssets.forward_arrow_icon, width: 40.h, height: 40.h, fit: BoxFit.contain, iconColor: AppColors.textColor).toShimmer2(isShow: isLoading)), + // Transform.flip( + // flipX: appState.isArabic(), + // child: Utils.buildSvgWithAssets(icon: AppAssets.forward_arrow_icon, width: 40.h, height: 40.h, fit: BoxFit.contain, iconColor: AppColors.textColor).toShimmer2(isShow: isLoading)), ]), + SizedBox(height: 8.h), + Row( + children: [ + Expanded( + flex: 7, + child: CustomButton( + text: LocaleKeys.bookNow.tr(), + onPressed: () async { + onImmediateLiveCareClinicSelected(liveCareClinicListResponseModel); + }, + backgroundColor: AppColors.successLightBgColor, + borderColor: AppColors.successLightBgColor, + textColor: AppColors.successColor, + borderWidth: 1.h, + fontSize: (isFoldable || isTablet) ? 12.f : 14.f, + fontWeight: FontWeight.w600, + borderRadius: 12.r, + padding: EdgeInsets.symmetric(horizontal: 10.w), + height: 40.h, + icon: AppAssets.bookAppoBottom, + iconColor: liveCareClinicListResponseModel.isOnline == 0 ? AppColors.textColorLight : AppColors.successColor, + iconSize: 16.h, + isDisabled: liveCareClinicListResponseModel.isOnline == 0, + ), + ), + SizedBox(width: 8.w), + Expanded( + flex: 1, + child: Container( + height: (isFoldable || isTablet) ? 50.h : 40.h, + decoration: RoundedRectangleBorder().toSmoothCornerDecoration( + // color: Color(0xFFEFEFF0), + color: AppColors.transparent, + borderRadius: 10.h, + side: BorderSide( + color: AppColors.textColor, + width: 1.2, + ), + ), + child: Transform.flip( + flipX: appState.isArabic(), + child: Utils.buildSvgWithAssets( + icon: AppAssets.calendar, + iconColor: AppColors.textColor, + width: 5.w, + height: 5.h, + fit: BoxFit.scaleDown, + ), + ), + ).onPress(() { + onClinicScheduleTapped(liveCareClinicListResponseModel.serviceID!); + }), + ), + ], + ), ], ), ); } + + onClinicScheduleTapped(int serviceID) async { + LoaderBottomSheet.showLoader(loadingText: LocaleKeys.loadingText.tr()); + await immediateLiveCareViewModel.getLiveCareImmediateClinicSchedule( + serviceID: serviceID, + onSuccess: (val) { + LoaderBottomSheet.hideLoader(); + showCommonBottomSheetWithoutHeight(GetIt.instance().navigatorKey.currentContext!, + child: ListView.separated( + shrinkWrap: true, + itemBuilder: (_, index) { + return DecoratedBox( + decoration: RoundedRectangleBorder().toSmoothCornerDecoration( + color: AppColors.whiteColor, + borderRadius: 20.h, + hasShadow: false, + ), + child: Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Expanded( + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + spacing: 8.h, + children: [ + checkInOptionCard(AppAssets.calendarGrey, immediateLiveCareViewModel.liveCareClinicScheduleList[index].dayOfWeekStr!, + "${immediateLiveCareViewModel.liveCareClinicScheduleList[index].shiftTimings!.first.startTime} to ${immediateLiveCareViewModel.liveCareClinicScheduleList[index].shiftTimings!.first.endTime}"), + ], + ), + ), + ], + ), + ).paddingSymmetrical(0.h, 0.h).onPress(() {}); + }, + separatorBuilder: (_, __) => SizedBox(height: 16.h), + itemCount: immediateLiveCareViewModel.liveCareClinicScheduleList.length), + title: LocaleKeys.clinicSchedule.tr(), + isCloseButtonVisible: true, + useSafeArea: true, + isFullScreen: true); + }); + } + + onImmediateLiveCareClinicSelected(GetLiveCareClinicListResponseModel liveCareClinic) { + //TODO: add implementation to show clinic schedule + // if (liveCareClinic.isOnline == 1) { + showCommonBottomSheetWithoutHeight(GetIt.instance().navigatorKey.currentContext!, child: SelectLiveCareCallType(immediateLiveCareViewModel: immediateLiveCareViewModel), + callBackFunc: () async { + if (immediateLiveCareViewModel.liveCareSelectedCallType != 0) { + immediateLiveCareViewModel.setImmediateLiveCareSelectedClinic(liveCareClinic); + LoaderBottomSheet.showLoader(loadingText: LocaleKeys.fetchingFeesPleaseWait.tr()); + await immediateLiveCareViewModel.getLiveCareImmediateAppointmentFees(onSuccess: (val) { + LoaderBottomSheet.hideLoader(); + Navigator.of(GetIt.instance().navigatorKey.currentContext!).push( + CustomPageRoute( + page: ImmediateLiveCarePaymentDetails(), + ), + ); + }, onError: (err) { + LoaderBottomSheet.hideLoader(); + showCommonBottomSheetWithoutHeight( + GetIt.instance().navigatorKey.currentContext!, + child: Utils.getErrorWidget(loadingText: err), + callBackFunc: () {}, + isFullScreen: false, + isCloseButtonVisible: true, + ); + }); + } + }, title: LocaleKeys.selectLiveCareCallType.tr(), isCloseButtonVisible: true, isFullScreen: false); + } + + Widget checkInOptionCard(String icon, String title, String subTitle) { + return Container( + decoration: RoundedRectangleBorder().toSmoothCornerDecoration( + color: AppColors.whiteColor, + borderRadius: 20.h, + hasShadow: false, + ), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Expanded( + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + title.toText16(isBold: true, color: AppColors.textColor), + SizedBox(height: 8.h), + Row( + children: [ + AppCustomChipWidget( + icon: AppAssets.appointment_time_icon, + iconColor: AppColors.textColor, + labelText: subTitle.split("to").first.trim(), + isEnglishOnly: true, + ), + SizedBox(width: 8.w), + Transform.flip( + flipX: appState.isArabic(), + child: Utils.buildSvgWithAssets( + icon: AppAssets.arrow_forward, + height: 24.h, + width: 24.h, + iconColor: AppColors.textColor, + ), + ), + SizedBox(width: 8.w), + AppCustomChipWidget( + icon: AppAssets.appointment_time_icon, + iconColor: AppColors.textColor, + labelText: subTitle.split("to")[1].trim(), + isEnglishOnly: true, + ), + ], + ), + ], + ), + ), + ], + ), + ], + ).paddingAll(16.h), + ); + } } diff --git a/lib/presentation/book_appointment/livecare/widgets/select_livecare_call_type.dart b/lib/presentation/book_appointment/livecare/widgets/select_livecare_call_type.dart index 8bb07897..916e0486 100644 --- a/lib/presentation/book_appointment/livecare/widgets/select_livecare_call_type.dart +++ b/lib/presentation/book_appointment/livecare/widgets/select_livecare_call_type.dart @@ -71,7 +71,7 @@ class SelectLiveCareCallType extends StatelessWidget { crossAxisAlignment: CrossAxisAlignment.start, children: [ title.toText16(isBold: true, color: AppColors.textColor), - subTitle.toText12(fontWeight: FontWeight.w500, color: AppColors.greyTextColor), + subTitle.toText12(isBold: true, color: AppColors.greyTextColor), ], ), ), diff --git a/lib/presentation/book_appointment/review_appointment_page.dart b/lib/presentation/book_appointment/review_appointment_page.dart index a9f07b05..689e6cf3 100644 --- a/lib/presentation/book_appointment/review_appointment_page.dart +++ b/lib/presentation/book_appointment/review_appointment_page.dart @@ -101,7 +101,7 @@ class _ReviewAppointmentPageState extends State { SizedBox(height: 2.h), (bookAppointmentsViewModel.selectedDoctor.speciality!.isNotEmpty ? bookAppointmentsViewModel.selectedDoctor.speciality!.first : "") .toString() - .toText12(fontWeight: FontWeight.w500, color: AppColors.greyTextColor, maxLine: 1), + .toText12(isBold: true, color: AppColors.greyTextColor, maxLine: 1), ], ), ], @@ -161,7 +161,7 @@ class _ReviewAppointmentPageState extends State { spacing: 4.h, runSpacing: 4.h, children: [ - AppCustomChipWidget(labelText: "${appState.getAuthenticatedUser()!.age} Years Old"), + AppCustomChipWidget(labelText: "${appState.getAuthenticatedUser()!.age} ${LocaleKeys.yearsOld.tr(context: context)}"), AppCustomChipWidget(labelText: "${LocaleKeys.gender.tr(context: context)}: ${appState.getAuthenticatedUser()?.gender == 1 ? LocaleKeys.malE.tr(context: context) : LocaleKeys.femaleGender.tr(context: context)}"), ], ), @@ -197,12 +197,12 @@ class _ReviewAppointmentPageState extends State { AppCustomChipWidget( labelText: bookAppointmentsVM.isAppointmentNearestGateLoading ? "Floor: Ground Floor" - : "Floor: ${getIt.get().isArabic() ? bookAppointmentsViewModel.appointmentNearestGateResponseModel!.clinicLocationN : bookAppointmentsViewModel.appointmentNearestGateResponseModel!.clinicLocation}", - ).toShimmer2(isShow: bookAppointmentsVM.isAppointmentNearestGateLoading), + : "${LocaleKeys.floor.tr(context: context)} ${getIt.get().isArabic() ? bookAppointmentsViewModel.appointmentNearestGateResponseModel!.clinicLocationN : bookAppointmentsViewModel.appointmentNearestGateResponseModel!.clinicLocation}", + ).toShimmer2(isShow: bookAppointmentsVM.isAppointmentNearestGateLoading), AppCustomChipWidget( labelText: - "Nearest Gate: ${getIt.get().isArabic() ? bookAppointmentsVM.appointmentNearestGateResponseModel!.nearestGateNumberN : bookAppointmentsVM.appointmentNearestGateResponseModel!.nearestGateNumber}") - .toShimmer2(isShow: bookAppointmentsVM.isAppointmentNearestGateLoading), + "${LocaleKeys.nearestGate.tr(context: context)} ${getIt.get().isArabic() ? bookAppointmentsVM.appointmentNearestGateResponseModel!.nearestGateNumberN : bookAppointmentsVM.appointmentNearestGateResponseModel!.nearestGateNumber}") + .toShimmer2(isShow: bookAppointmentsVM.isAppointmentNearestGateLoading), ], ) : SizedBox.shrink(), @@ -235,7 +235,7 @@ class _ReviewAppointmentPageState extends State { borderColor: AppColors.primaryRedColor, textColor: Colors.white, fontSize: 16.f, - fontWeight: FontWeight.w500, + fontWeight: FontWeight.w600, borderRadius: 12, padding: EdgeInsets.fromLTRB(10, 0, 10, 0), height: 50.h, @@ -317,7 +317,8 @@ class _ReviewAppointmentPageState extends State { void initiateBookAppointment() async { // LoadingUtils.showFullScreenLoader(barrierDismissible: true, isSuccessDialog: false, loadingText: bookAppointmentsViewModel.isPatientRescheduleAppointment ? LocaleKeys.reschedulingAppo.tr(context: context) : LocaleKeys.bookingYourAppointment.tr(context: context)); - LoaderBottomSheet.showLoader(loadingText: bookAppointmentsViewModel.isPatientRescheduleAppointment ? LocaleKeys.reschedulingAppo.tr(context: context) : LocaleKeys.bookingYourAppointment.tr(context: context)); + // LoaderBottomSheet.showLoader(loadingText: bookAppointmentsViewModel.isPatientRescheduleAppointment ? LocaleKeys.reschedulingAppo.tr(context: context) : LocaleKeys.bookingYourAppointment.tr(context: context)); + LoaderBottomSheet.showLoader(loadingText: LocaleKeys.loadingText.tr(context: context)); myAppointmentsViewModel.setIsAppointmentDataToBeLoaded(true); if (bookAppointmentsViewModel.isLiveCareSchedule) { diff --git a/lib/presentation/book_appointment/search_doctor_by_name.dart b/lib/presentation/book_appointment/search_doctor_by_name.dart index 533229a0..3222fbd8 100644 --- a/lib/presentation/book_appointment/search_doctor_by_name.dart +++ b/lib/presentation/book_appointment/search_doctor_by_name.dart @@ -154,7 +154,7 @@ class _SearchDoctorByNameState extends State { // borderColor: bookAppointmentsVM.isSortByClinic ? AppColors.primaryRedColor : AppColors.textColor.withOpacity(0.2), // textColor: bookAppointmentsVM.isSortByClinic ? AppColors.primaryRedColor : AppColors.blackColor, // fontSize: 12, - // fontWeight: FontWeight.w500, + // isBold: true, // borderRadius: 10, // padding: EdgeInsets.fromLTRB(10, 0, 10, 0), // height: 40.h, @@ -169,7 +169,7 @@ class _SearchDoctorByNameState extends State { // borderColor: bookAppointmentsVM.isSortByClinic ? AppColors.textColor.withOpacity(0.2) : AppColors.primaryRedColor, // textColor: bookAppointmentsVM.isSortByClinic ? AppColors.blackColor : AppColors.primaryRedColor, // fontSize: 12, - // fontWeight: FontWeight.w500, + // isBold: true, // borderRadius: 10, // padding: EdgeInsets.fromLTRB(10, 0, 10, 0), // height: 40.h, @@ -218,7 +218,7 @@ class _SearchDoctorByNameState extends State { LoaderBottomSheet.hideLoader(); Navigator.of(context).push( CustomPageRoute( - page: DoctorProfilePage(), + page: DoctorProfilePage(isDoctorAllowedToBook: true), ), ); }, @@ -306,7 +306,7 @@ class _SearchDoctorByNameState extends State { // borderColor: AppColors.greyColor, // textColor: AppColors.blackColor, // fontSize: 10, - // fontWeight: FontWeight.w500, + // isBold: true, // borderRadius: 8, // padding: EdgeInsets.fromLTRB(10, 0, 10, 0), // height: 30.h), @@ -319,7 +319,7 @@ class _SearchDoctorByNameState extends State { // bookAppointmentsVM.isSortByClinic // ? (groupedDoctors[index].first.clinicName ?? 'Unknown') // : (groupedDoctors[index].first.projectName ?? 'Unknown'), - // style: TextStyle(fontSize: 16.h, fontWeight: FontWeight.w600), + // style: TextStyle(fontSize: 16.h, isBold: true), // overflow: TextOverflow.ellipsis, // ), // // Expanded content - list of doctors in this group @@ -440,7 +440,7 @@ class _SearchDoctorByNameState extends State { borderColor: AppColors.primaryRedColor, textColor: AppColors.whiteColor, fontSize: 16.f, - fontWeight: FontWeight.w500, + isBold: true, borderRadius: 10.r, padding: EdgeInsets.fromLTRB(10, 0, 10, 0), height: 50.h, diff --git a/lib/presentation/book_appointment/select_clinic_page.dart b/lib/presentation/book_appointment/select_clinic_page.dart index 6812a349..e617194e 100644 --- a/lib/presentation/book_appointment/select_clinic_page.dart +++ b/lib/presentation/book_appointment/select_clinic_page.dart @@ -98,7 +98,7 @@ class _SelectClinicPageState extends State { backgroundColor: AppColors.primaryRedColor, borderColor: AppColors.primaryRedColor, borderRadius: 12.r, - fontWeight: FontWeight.w500), + fontWeight: FontWeight.w600,), ), child: getCalculatorResultWidget(type: widget.calculatorType!, calculatedResult: widget.calculatedResult).paddingSymmetrical(18.w, 24.h), ); @@ -619,7 +619,7 @@ class _SelectClinicPageState extends State { SizedBox( width: 8.w, ), - calculatedResult["lastPeriodDate"].toString().toText16(color: AppColors.successColor, weight: FontWeight.w600), + calculatedResult["lastPeriodDate"].toString().toText16(color: AppColors.successColor, isBold: true), ], ), ], @@ -641,7 +641,7 @@ class _SelectClinicPageState extends State { SizedBox( width: 8.w, ), - calculatedResult["ovulationDate"].toString().toText16(color: AppColors.successColor, weight: FontWeight.w600), + calculatedResult["ovulationDate"].toString().toText16(color: AppColors.successColor, isBold: true), ], ), SizedBox(height: 8.h), @@ -655,7 +655,7 @@ class _SelectClinicPageState extends State { borderRadius: 8.h, textColor: AppColors.textColor), SizedBox(height: 16.h), - "Fertility Window".toText16(color: AppColors.textColor, weight: FontWeight.w600), + "Fertility Window".toText16(color: AppColors.textColor, isBold: true), SizedBox(height: 8.h), Row( children: [ @@ -663,7 +663,7 @@ class _SelectClinicPageState extends State { SizedBox( width: 8.w, ), - calculatedResult["fertileWindowStart"].toString().toText13(color: AppColors.successColor, weight: FontWeight.w600), + calculatedResult["fertileWindowStart"].toString().toText13(color: AppColors.successColor, isBold: true), SizedBox( width: 4.w, ), @@ -671,12 +671,12 @@ class _SelectClinicPageState extends State { SizedBox( width: 4.w, ), - calculatedResult["fertileWindowEnd"].toString().toText13(color: AppColors.successColor, weight: FontWeight.w600), + calculatedResult["fertileWindowEnd"].toString().toText13(color: AppColors.successColor, isBold: true), ], ), SizedBox(height: 8.h), "This is your fertile window. Maintaining a healthy calorie range supports your body during this period, considering your Basal Metabolic Rate and activity. Your Body Mass Index is within the healthy range." - .toText12(height: 1.5, color: AppColors.textColorLight, fontWeight: FontWeight.w500) + .toText12(height: 1.5, color: AppColors.textColorLight, isBold: true) ], ), ), @@ -697,7 +697,7 @@ class _SelectClinicPageState extends State { ), Row( children: [ - "Tips to keep in mind".toText16(color: AppColors.textColor, weight: FontWeight.w600), + "Tips to keep in mind".toText16(color: AppColors.textColor, isBold: true), ], ), ], @@ -735,7 +735,7 @@ class _SelectClinicPageState extends State { children: [ Utils.buildSvgWithAssets(icon: AppAssets.pregnancyDueDateAccordion, width: 30.h, height: 30.h), SizedBox(width: 8.w), - calculatedResult["dueDate"].toString().toText16(color: AppColors.successColor, weight: FontWeight.w600), + calculatedResult["dueDate"].toString().toText16(color: AppColors.successColor, isBold: true), ], ), Expanded(child: SizedBox(width: 8.w)), @@ -762,7 +762,7 @@ class _SelectClinicPageState extends State { children: [ Row( children: [ - "First Trimester".toText16(color: AppColors.textColor, weight: FontWeight.w600), + "First Trimester".toText16(color: AppColors.textColor, isBold: true), Expanded(child: SizedBox(width: 8.h)), CustomChipWidget( height: 30.h, @@ -782,7 +782,7 @@ class _SelectClinicPageState extends State { SizedBox( width: 8.w, ), - calculatedResult["firstTrimester"]["start"].toString().toText13(color: AppColors.successColor, weight: FontWeight.w600), + calculatedResult["firstTrimester"]["start"].toString().toText13(color: AppColors.successColor, isBold: true), SizedBox( width: 4.w, ), @@ -790,7 +790,7 @@ class _SelectClinicPageState extends State { SizedBox( width: 4.w, ), - calculatedResult["firstTrimester"]["end"].toString().toText13(color: AppColors.successColor, weight: FontWeight.w600), + calculatedResult["firstTrimester"]["end"].toString().toText13(color: AppColors.successColor, isBold: true), ], ), SizedBox(height: 8.h), @@ -806,7 +806,7 @@ class _SelectClinicPageState extends State { children: [ Row( children: [ - "Second Trimester".toText16(color: AppColors.textColor, weight: FontWeight.w600), + "Second Trimester".toText16(color: AppColors.textColor, isBold: true), Expanded(child: SizedBox(width: 8.h)), CustomChipWidget( height: 30.h, @@ -826,7 +826,7 @@ class _SelectClinicPageState extends State { SizedBox( width: 8.w, ), - calculatedResult["secondTrimester"]["start"].toString().toText13(color: AppColors.successColor, weight: FontWeight.w600), + calculatedResult["secondTrimester"]["start"].toString().toText13(color: AppColors.successColor, isBold: true), SizedBox( width: 4.w, ), @@ -834,7 +834,7 @@ class _SelectClinicPageState extends State { SizedBox( width: 4.w, ), - calculatedResult["secondTrimester"]["end"].toString().toText13(color: AppColors.successColor, weight: FontWeight.w600), + calculatedResult["secondTrimester"]["end"].toString().toText13(color: AppColors.successColor, isBold: true), ], ), SizedBox(height: 8.h), @@ -850,7 +850,7 @@ class _SelectClinicPageState extends State { children: [ Row( children: [ - "Third Trimester".toText16(color: AppColors.textColor, weight: FontWeight.w600), + "Third Trimester".toText16(color: AppColors.textColor, isBold: true), Expanded(child: SizedBox(width: 8.h)), CustomChipWidget( height: 30.h, @@ -870,7 +870,7 @@ class _SelectClinicPageState extends State { SizedBox( width: 8.w, ), - calculatedResult["thirdTrimester"]["start"].toString().toText13(color: AppColors.successColor, weight: FontWeight.w600), + calculatedResult["thirdTrimester"]["start"].toString().toText13(color: AppColors.successColor, isBold: true), SizedBox( width: 4.w, ), @@ -878,7 +878,7 @@ class _SelectClinicPageState extends State { SizedBox( width: 4.w, ), - calculatedResult["thirdTrimester"]["end"].toString().toText13(color: AppColors.successColor, weight: FontWeight.w600), + calculatedResult["thirdTrimester"]["end"].toString().toText13(color: AppColors.successColor, isBold: true), ], ), SizedBox(height: 8.h), @@ -904,7 +904,7 @@ class _SelectClinicPageState extends State { ), ), Expanded( - child: text.toText12(color: AppColors.textColorLight, fontWeight: FontWeight.w500, height: 1.2.h), + child: text.toText12(color: AppColors.textColorLight, isBold: true, height: 1.2.h), ), ], ); @@ -924,7 +924,7 @@ class _SelectClinicPageState extends State { children: [ ('${calories.toStringAsFixed(0)}').toText32(color: getTextColor(title), isBold: true), SizedBox(width: 10.w), - hide ? SizedBox() : 'kcal/day'.toText12(color: AppColors.inputLabelTextColor, isBold: true, fontWeight: FontWeight.w600) + hide ? SizedBox() : 'kcal/day'.toText12(color: AppColors.inputLabelTextColor, isBold: true) ], ), _buildStatusIcon(title) @@ -950,9 +950,9 @@ class _SelectClinicPageState extends State { ), SizedBox(height: 8.h), ], - title.toText16(color: AppColors.textColor, weight: FontWeight.w600), + title.toText16(color: AppColors.textColor, isBold: true), SizedBox(height: 8.h), - description.toText12(height: 1.2.h, color: AppColors.textColorLight, fontWeight: FontWeight.w500) + description.toText12(height: 1.2.h, color: AppColors.textColorLight, isBold: true) ], ), ); @@ -1064,24 +1064,28 @@ class _SelectClinicPageState extends State { ); } } else { - if (clinic.clinicID == 253) { - LoaderBottomSheet.showLoader(loadingText: LocaleKeys.loadingText.tr(context: context)); - await bookAppointmentsViewModel.getLaserHospitals( - onSuccess: (_) { - LoaderBottomSheet.hideLoader(); - _showLaserHospitalBottomSheet(); - }, - onError: (err) { - LoaderBottomSheet.hideLoader(); - }, - ); - return; - } + // if (clinic.clinicID == 253) { + // LoaderBottomSheet.showLoader(loadingText: LocaleKeys.loadingText.tr(context: context)); + // await bookAppointmentsViewModel.getLaserHospitals( + // onSuccess: (_) { + // LoaderBottomSheet.hideLoader(); + // _showLaserHospitalBottomSheet(); + // }, + // onError: (err) { + // LoaderBottomSheet.hideLoader(); + // }, + // ); + // return; + // } var bottomSheetType = RegionBottomSheetType.FOR_CLINIIC; if (clinic.clinicID == 17) { - regionalViewModel.setBottomSheetState(AppointmentViaRegionState.HOSPITAL_SELECTION); + // regionalViewModel.setBottomSheetState(AppointmentViaRegionState.HOSPITAL_SELECTION); + // bottomSheetType = RegionBottomSheetType.REGION_FOR_DENTAL_AND_LASER; bottomSheetType = RegionBottomSheetType.REGION_FOR_DENTAL_AND_LASER; } + // if (clinic.clinicID == 253) { + // bottomSheetType = RegionBottomSheetType.REGION_FOR_DENTAL_AND_LASER; + // } openRegionListBottomSheet(context, bottomSheetType); } } @@ -1122,10 +1126,19 @@ class _SelectClinicPageState extends State { regionalViewModel.handleLastStepForRegion(); } else if (regionalViewModel.regionBottomSheetType == RegionBottomSheetType.FOR_CLINIIC) { regionalViewModel.setBottomSheetState(AppointmentViaRegionState.DOCTOR_SELECTION); - regionalViewModel.handleLastStepForClinic(); + var appointmentsViewModel = Provider.of(context, listen: false); + if (appointmentsViewModel.selectedClinic.clinicID == 253) { + appointmentsViewModel.setProjectID(regionalViewModel.selectedHospital?.patientDoctorAppointmentList!.first.iD?.toString() ?? ""); + appointmentsViewModel.resetLaserData(); + appointmentsViewModel.getLaserClinic(); + regionalViewModel.handleLastStepForClinicForDentalAndLaser(appointmentsViewModel.selectedClinic.clinicID ?? -1); + } else { + regionalViewModel.handleLastStepForClinic(); + } } else if (regionalViewModel.regionBottomSheetType == RegionBottomSheetType.REGION_FOR_DENTAL_AND_LASER) { var appointmentsViewModel = Provider.of(context, listen: false); regionalViewModel.setBottomSheetState(AppointmentViaRegionState.DOCTOR_SELECTION); + appointmentsViewModel.setProjectID(regionalViewModel.selectedHospital?.hospitalList.first.iD?.toString() ?? ""); regionalViewModel.handleLastStepForClinicForDentalAndLaser(appointmentsViewModel.selectedClinic.clinicID ?? -1); } }, @@ -1157,12 +1170,18 @@ class _SelectClinicPageState extends State { } } - if (bookAppointmentsViewModel.selectedClinic.clinicID == 253) { - { - bookAppointmentsViewModel.resetLaserData(); - bookAppointmentsViewModel.getLaserClinic(); - } - } + // if (bookAppointmentsViewModel.selectedClinic.clinicID == 253) { + // // { + // // bookAppointmentsViewModel.resetLaserData(); + // // bookAppointmentsViewModel.getLaserClinic(); + // // Navigator.push( + // // context, + // // CustomPageRoute( + // // page: LaserAppointment(), + // // ), + // // ); + // // } + // } bookAppointmentsViewModel.setProjectID(id); return SizedBox.shrink(); } else { @@ -1173,7 +1192,7 @@ class _SelectClinicPageState extends State { getTitle(AppointmentViaRegionViewmodel data) { if (data.selectedRegionId == null) { - return LocaleKeys.selectRegion.tr(context: context).toText20(weight: FontWeight.w600); + return LocaleKeys.selectRegion.tr(context: context).toText20(isBold: true); } else { return Transform.flip( flipX: data.isArabic ? true : false, @@ -1201,7 +1220,7 @@ class _SelectClinicPageState extends State { child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ - LocaleKeys.youHaveExistingTreatmentPlan.tr(context: context).toText14(weight: FontWeight.w500), + LocaleKeys.youHaveExistingTreatmentPlan.tr(context: context).toText14(isBold: true), SizedBox(height: 8.h), Container( width: double.infinity, @@ -1256,7 +1275,7 @@ class _SelectClinicPageState extends State { ), ), SizedBox(height: 16.h), - LocaleKeys.wouldYouLikeToContinue.tr(context: context).toText14(weight: FontWeight.w500), + LocaleKeys.wouldYouLikeToContinue.tr(context: context).toText14(isBold: true), SizedBox(height: 16.h), Row( children: [ diff --git a/lib/presentation/book_appointment/select_doctor_page.dart b/lib/presentation/book_appointment/select_doctor_page.dart index 4e56b083..d9012573 100644 --- a/lib/presentation/book_appointment/select_doctor_page.dart +++ b/lib/presentation/book_appointment/select_doctor_page.dart @@ -88,7 +88,7 @@ class _SelectDoctorPageState extends State { // backgroundColor: AppColors.primaryRedColor, // borderColor: AppColors.primaryRedColor, // borderRadius: 12.r, - // fontWeight: FontWeight.w500), + // isBold: true), // ), child: SingleChildScrollView( controller: _scrollController, @@ -167,14 +167,14 @@ class _SelectDoctorPageState extends State { ? GestureDetector( onTap: () { searchEditingController.clear(); - bookAppointmentsViewModel.filterClinics(""); + bookAppointmentsViewModel.filterDoctors(""); textFocusNode.unfocus(); }, child: Utils.buildSvgWithAssets(icon: AppAssets.close_bottom_sheet_icon, width: 20.h, height: 20.h, fit: BoxFit.scaleDown), ) : null, onChange: (value) { - bookAppointmentsViewModel.filterClinics(value!); + bookAppointmentsViewModel.filterDoctors(value ?? ""); }, padding: EdgeInsets.symmetric( vertical: ResponsiveExtension(10).h, @@ -197,7 +197,7 @@ class _SelectDoctorPageState extends State { borderColor: bookAppointmentsVM.isSortByClinic ? AppColors.primaryRedColor : AppColors.textColor.withOpacity(0.2), textColor: bookAppointmentsVM.isSortByClinic ? AppColors.primaryRedColor : AppColors.blackColor, fontSize: 12, - fontWeight: FontWeight.w500, + isBold: true, borderRadius: 10, padding: EdgeInsets.fromLTRB(10, 0, 10, 0), height: 40.h, @@ -212,7 +212,7 @@ class _SelectDoctorPageState extends State { borderColor: bookAppointmentsVM.isSortByClinic ? AppColors.textColor.withOpacity(0.2) : AppColors.primaryRedColor, textColor: bookAppointmentsVM.isSortByClinic ? AppColors.blackColor : AppColors.primaryRedColor, fontSize: 12, - fontWeight: FontWeight.w500, + isBold: true, borderRadius: 10, padding: EdgeInsets.fromLTRB(10, 0, 10, 0), height: 40.h, @@ -220,8 +220,9 @@ class _SelectDoctorPageState extends State { ], ).paddingSymmetrical(0.h, 0.h), if (bookAppointmentsViewModel.isGetDocForHealthCal && bookAppointmentsVM.showSortFilterButtons) SizedBox(height: 16.h), - bookAppointmentsVM.doctorsListGrouped.isNotEmpty - ? Row( + // bookAppointmentsVM.doctorsListGrouped.isNotEmpty + // ? + Row( mainAxisSize: MainAxisSize.max, children: [ Column( @@ -229,7 +230,7 @@ class _SelectDoctorPageState extends State { children: [ LocaleKeys.nearestAppo.tr(context: context).toText13(isBold: true), SizedBox(height: 4.h), - LocaleKeys.viewNearestAppos.tr(context: context).toText11(color: AppColors.textColorLight, weight: FontWeight.w500), + LocaleKeys.viewNearestAppos.tr(context: context).toText11(color: AppColors.textColorLight, isBold: true), ], ), const Spacer(), @@ -239,11 +240,12 @@ class _SelectDoctorPageState extends State { value: bookAppointmentsVM.isNearestAppointmentSelected, onChanged: (newValue) async { bookAppointmentsVM.setIsNearestAppointmentSelected(newValue); - }, - ), - ], - ) - : SizedBox.shrink(), + bookAppointmentsVM.refreshDoctorsList(); + }, + ), + ], + ), + // : SizedBox.shrink(), ListView.separated( padding: EdgeInsets.only(top: 16.h), shrinkWrap: true, @@ -303,7 +305,7 @@ class _SelectDoctorPageState extends State { borderColor: AppColors.greyColor, textColor: AppColors.blackColor, fontSize: 10, - fontWeight: FontWeight.w500, + isBold: true, borderRadius: 8, padding: EdgeInsets.fromLTRB(10, 0, 10, 0), height: 30.h, @@ -317,7 +319,7 @@ class _SelectDoctorPageState extends State { bookAppointmentsVM.isSortByClinic ? (bookAppointmentsVM.doctorsListGrouped[index].first.clinicName ?? 'Unknown') : (bookAppointmentsVM.doctorsListGrouped[index].first.projectName ?? 'Unknown'), - style: TextStyle(fontSize: 16.h, fontWeight: FontWeight.w600), + style: TextStyle(fontSize: 16.h, fontWeight: FontWeight.w600,), overflow: TextOverflow.ellipsis, ), // Expanded content - list of doctors in this group @@ -346,7 +348,7 @@ class _SelectDoctorPageState extends State { LoaderBottomSheet.hideLoader(); Navigator.of(context).push( CustomPageRoute( - page: DoctorProfilePage(), + page: DoctorProfilePage(isDoctorAllowedToBook: true,), ), ); }, onError: (err) { diff --git a/lib/presentation/book_appointment/select_livecare_clinic_page.dart b/lib/presentation/book_appointment/select_livecare_clinic_page.dart index 87ab0eb5..bd2b6462 100644 --- a/lib/presentation/book_appointment/select_livecare_clinic_page.dart +++ b/lib/presentation/book_appointment/select_livecare_clinic_page.dart @@ -55,7 +55,7 @@ class SelectLivecareClinicPage extends StatelessWidget { crossAxisAlignment: CrossAxisAlignment.start, children: [ LocaleKeys.immediateService.tr(context: context).toText18(color: AppColors.textColor, isBold: true), - LocaleKeys.noNeedToWaitGetMedicalConsultation.tr(context: context).toText14(color: AppColors.greyTextColor, weight: FontWeight.w500), + LocaleKeys.noNeedToWaitGetMedicalConsultation.tr(context: context).toText14(color: AppColors.greyTextColor, isBold: true), ], ), ), @@ -71,7 +71,7 @@ class SelectLivecareClinicPage extends StatelessWidget { crossAxisAlignment: CrossAxisAlignment.start, children: [ LocaleKeys.noVisitRequired.tr(context: context).toText18(color: AppColors.textColor, isBold: true), - LocaleKeys.livecarePoint5.tr(context: context).toText14(color: AppColors.greyTextColor, weight: FontWeight.w500), + LocaleKeys.livecarePoint5.tr(context: context).toText14(color: AppColors.greyTextColor, isBold: true), ], ), ), @@ -87,7 +87,7 @@ class SelectLivecareClinicPage extends StatelessWidget { crossAxisAlignment: CrossAxisAlignment.start, children: [ LocaleKeys.doctorWillContact.tr(context: context).toText18(color: AppColors.textColor, isBold: true), - LocaleKeys.specialisedDoctorWillContactYou.tr(context: context).toText14(color: AppColors.greyTextColor, weight: FontWeight.w500), + LocaleKeys.specialisedDoctorWillContactYou.tr(context: context).toText14(color: AppColors.greyTextColor, isBold: true), ], ), ), @@ -103,7 +103,7 @@ class SelectLivecareClinicPage extends StatelessWidget { crossAxisAlignment: CrossAxisAlignment.start, children: [ LocaleKeys.freeMedicineDelivery.tr(context: context).toText18(color: AppColors.textColor, isBold: true), - LocaleKeys.offersFreeMedicineDelivery.tr(context: context).toText14(color: AppColors.greyTextColor, weight: FontWeight.w500), + LocaleKeys.offersFreeMedicineDelivery.tr(context: context).toText14(color: AppColors.greyTextColor, isBold: true), ], ), ), @@ -152,7 +152,7 @@ class SelectLivecareClinicPage extends StatelessWidget { borderColor: AppColors.primaryRedColor, textColor: AppColors.whiteColor, fontSize: 16.f, - fontWeight: FontWeight.w500, + isBold: true, borderRadius: 10.r, padding: EdgeInsets.fromLTRB(10, 0, 10, 0), height: 50.h, @@ -171,7 +171,7 @@ class SelectLivecareClinicPage extends StatelessWidget { borderColor: AppColors.secondaryLightRedColor, textColor: AppColors.primaryRedColor, fontSize: 16.f, - fontWeight: FontWeight.w500, + isBold: true, borderRadius: 10.r, padding: EdgeInsets.fromLTRB(10, 0, 10, 0), height: 50.h, diff --git a/lib/presentation/book_appointment/waiting_appointment/waiting_appointment_info.dart b/lib/presentation/book_appointment/waiting_appointment/waiting_appointment_info.dart index 19d0fb41..500167eb 100644 --- a/lib/presentation/book_appointment/waiting_appointment/waiting_appointment_info.dart +++ b/lib/presentation/book_appointment/waiting_appointment/waiting_appointment_info.dart @@ -98,7 +98,7 @@ class WaitingAppointmentInfo extends StatelessWidget { borderColor: AppColors.primaryRedColor, textColor: AppColors.whiteColor, fontSize: 16, - fontWeight: FontWeight.w500, + fontWeight: FontWeight.w600, borderRadius: 12, padding: EdgeInsets.fromLTRB(10, 0, 10, 0), height: 50.h, diff --git a/lib/presentation/book_appointment/waiting_appointment/waiting_appointment_online_checkin_sheet.dart b/lib/presentation/book_appointment/waiting_appointment/waiting_appointment_online_checkin_sheet.dart index 6f3a773a..11c79c10 100644 --- a/lib/presentation/book_appointment/waiting_appointment/waiting_appointment_online_checkin_sheet.dart +++ b/lib/presentation/book_appointment/waiting_appointment/waiting_appointment_online_checkin_sheet.dart @@ -63,20 +63,20 @@ class WaitingAppointmentOnlineCheckinSheet extends StatelessWidget { }); }), SizedBox(height: 16.h), - checkInOptionCard( - AppAssets.checkin_nfc_icon, - LocaleKeys.nfcNearFieldCommunication.tr(), - LocaleKeys.scanPhoneViaNFC.tr(), - ).onPress(() { - Future.delayed(const Duration(milliseconds: 500), () { - showNfcReader(context, onNcfScan: (String nfcId) { - Future.delayed(const Duration(milliseconds: 100), () { - checkScannedNFCAndQRCode(nfcId, context); - }); - }, onCancel: () {}); - }); - }), - SizedBox(height: 16.h), + // checkInOptionCard( + // AppAssets.checkin_nfc_icon, + // LocaleKeys.nfcNearFieldCommunication.tr(), + // LocaleKeys.scanPhoneViaNFC.tr(), + // ).onPress(() { + // Future.delayed(const Duration(milliseconds: 500), () { + // showNfcReader(context, onNcfScan: (String nfcId) { + // Future.delayed(const Duration(milliseconds: 100), () { + // checkScannedNFCAndQRCode(nfcId, context); + // }); + // }, onCancel: () {}); + // }); + // }), + // SizedBox(height: 16.h), checkInOptionCard( AppAssets.checkin_qr_icon, LocaleKeys.qrCode.tr(), @@ -111,7 +111,7 @@ class WaitingAppointmentOnlineCheckinSheet extends StatelessWidget { crossAxisAlignment: CrossAxisAlignment.start, children: [ title.toText16(isBold: true, color: AppColors.textColor), - subTitle.toText12(fontWeight: FontWeight.w500, color: AppColors.greyTextColor), + subTitle.toText12(isBold: true, color: AppColors.greyTextColor), ], ), ), diff --git a/lib/presentation/book_appointment/waiting_appointment/waiting_appointment_payment_page.dart b/lib/presentation/book_appointment/waiting_appointment/waiting_appointment_payment_page.dart index 663fd5b4..4d136cdd 100644 --- a/lib/presentation/book_appointment/waiting_appointment/waiting_appointment_payment_page.dart +++ b/lib/presentation/book_appointment/waiting_appointment/waiting_appointment_payment_page.dart @@ -244,7 +244,7 @@ class _WaitingAppointmentPaymentPageState extends State { bookAppointmentsViewModel = Provider.of(context, listen: false); authVM = Provider.of(context, listen: false); appState = getIt.get(); + final isArabic = appState.isArabic(); + return Padding( padding: EdgeInsets.symmetric(horizontal: 0.h), child: Column( @@ -82,7 +85,7 @@ class _AppointmentCalendarState extends State { // Row( // crossAxisAlignment: CrossAxisAlignment.center, // children: [ - // "Pick a Date".toText20(weight: FontWeight.w600).expanded, + // "Pick a Date".toText20(isBold: true).expanded, // Utils.buildSvgWithAssets(icon: AppAssets.close_bottom_sheet_icon, iconColor: Color(0xff2B353E)).onPress(() { // Navigator.of(context).pop(); // }), @@ -90,87 +93,93 @@ class _AppointmentCalendarState extends State { // ), SizedBox( height: 350.h, - child: Localizations.override( - context: context, - locale: const Locale('en'), - child: SfCalendar( - controller: _calendarController, - minDate: DateTime.now(), - showNavigationArrow: true, - headerHeight: 60.h, - headerStyle: CalendarHeaderStyle( - backgroundColor: AppColors.transparent, - textAlign: TextAlign.start, - textStyle: TextStyle(fontSize: 18.f, fontWeight: FontWeight.w600, letterSpacing: -0.46, color: AppColors.primaryRedColor, fontFamily: "Poppins"), - ), - viewHeaderStyle: ViewHeaderStyle( - // backgroundColor: AppColors.scaffoldBgColor, - dayTextStyle: TextStyle(fontSize: 14.f, fontWeight: FontWeight.w600, letterSpacing: -0.46, color: AppColors.textColor, fontFamily: "Poppins"), - ), - view: CalendarView.month, - todayHighlightColor: Colors.transparent, - todayTextStyle: TextStyle(color: AppColors.textColor, fontWeight: FontWeight.bold, fontFamily: "Poppins"), - selectionDecoration: ShapeDecoration( - color: AppColors.transparent, - shape: SmoothRectangleBorder( - borderRadius: BorderRadius.circular(10.r), - smoothness: 1, - side: BorderSide(color: AppColors.primaryRedColor, width: 1.5), + child: Directionality( + textDirection: isArabic ? ui.TextDirection.rtl : ui.TextDirection.ltr, + child: Localizations.override( + context: context, + locale: isArabic ? const Locale('ar', 'SA') : const Locale('en'), + child: SfCalendar( + controller: _calendarController, + minDate: DateTime.now(), + showNavigationArrow: true, + headerHeight: 60.h, + headerStyle: CalendarHeaderStyle( + backgroundColor: AppColors.transparent, + textAlign: isArabic ? TextAlign.end : TextAlign.start, + textStyle: TextStyle(fontSize: 18.f, fontWeight: FontWeight.w600, letterSpacing: -0.46, color: AppColors.primaryRedColor), ), - ), - cellBorderColor: AppColors.transparent, - dataSource: MeetingDataSource(_getDataSource()), - monthCellBuilder: (context, details) => Padding( - padding: EdgeInsets.all(12.h), - child: details.date.day.toString().toText14( - isCenter: true, - color: details.date == _calendarController.selectedDate ? AppColors.primaryRedColor : AppColors.textColor, - isEnglishOnly: true, - ), - ), - monthViewSettings: MonthViewSettings( - dayFormat: "EEE", - appointmentDisplayMode: MonthAppointmentDisplayMode.indicator, - showTrailingAndLeadingDates: false, - appointmentDisplayCount: 1, - monthCellStyle: MonthCellStyle( - textStyle: TextStyle(fontSize: 19.f), + viewHeaderStyle: ViewHeaderStyle( + // backgroundColor: AppColors.scaffoldBgColor, + dayTextStyle: TextStyle(fontSize: 14.f, fontWeight: FontWeight.w600, letterSpacing: -0.46, color: AppColors.textColor), ), + view: CalendarView.month, + todayHighlightColor: Colors.transparent, + todayTextStyle: TextStyle(color: AppColors.textColor, fontWeight: FontWeight.bold), + selectionDecoration: ShapeDecoration( + color: AppColors.transparent, + shape: SmoothRectangleBorder( + borderRadius: BorderRadius.circular(10.r), + smoothness: 1, + side: BorderSide(color: AppColors.primaryRedColor, width: 1.5), + ), + ), + cellBorderColor: AppColors.transparent, + dataSource: MeetingDataSource(_getDataSource()), + monthCellBuilder: (context, details) => Padding( + padding: EdgeInsets.all(12.h), + child: details.date.day.toString().toText14( + isCenter: true, + color: details.date == _calendarController.selectedDate ? AppColors.primaryRedColor : AppColors.textColor, + isEnglishOnly: true, + ), + ), + monthViewSettings: MonthViewSettings( + dayFormat: "EEE", + appointmentDisplayMode: MonthAppointmentDisplayMode.indicator, + showTrailingAndLeadingDates: false, + appointmentDisplayCount: 1, + monthCellStyle: MonthCellStyle( + textStyle: TextStyle(fontSize: 19.f), + ), + ), + onTap: (CalendarTapDetails details) { + _calendarController.selectedDate = details.date; + _onDaySelected(details.date!); + }, ), - onTap: (CalendarTapDetails details) { - _calendarController.selectedDate = details.date; - _onDaySelected(details.date!); - }, ), ), ), SizedBox(height: 10.h), Transform.translate( offset: const Offset(0.0, -10.0), - child: selectedDateDisplay.toText16(weight: FontWeight.w500, isEnglishOnly: true), + child: selectedDateDisplay.toText16(isBold: true, isEnglishOnly: !isArabic), ), //TODO: Add Next Day Span here 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( - dayEvents.length, // Generate a large number of items to ensure scrolling - (index) => TimeSlotChip( - label: dayEvents[index].isoTime!, - isSelected: index == selectedButtonIndex, - onTap: () { - setState(() { - selectedButtonIndex = index; - selectedTime = dayEvents[index].isoTime!; - }); - }, + child: Directionality( + textDirection: isArabic ? ui.TextDirection.rtl : ui.TextDirection.ltr, + child: SingleChildScrollView( + scrollDirection: Axis.vertical, + child: Wrap( + direction: Axis.horizontal, + alignment: WrapAlignment.start, + spacing: 6.h, + runSpacing: 6.h, + children: List.generate( + dayEvents.length, // Generate a large number of items to ensure scrolling + (index) => TimeSlotChip( + label: dayEvents[index].isoTime!, + isSelected: index == selectedButtonIndex, + onTap: () { + setState(() { + selectedButtonIndex = index; + selectedTime = dayEvents[index].isoTime!; + }); + }, + ), ), ), ), @@ -269,7 +278,7 @@ class _AppointmentCalendarState extends State { borderColor: AppColors.primaryRedColor, textColor: Colors.white, fontSize: 16.f, - fontWeight: FontWeight.w500, + isBold: true, borderRadius: 10.r, padding: EdgeInsets.symmetric(horizontal: 10.w), height: 50.h, @@ -322,9 +331,12 @@ class _AppointmentCalendarState extends State { void _onDaySelected(DateTime day) { final DateFormat formatter = DateFormat('yyyy-MM-dd', "en-US"); + final isArabic = appState.isArabic(); setState(() { - selectedDateDisplay = DateUtil.getMonthDayYearDateFormatted(day); - selectedNextDate = DateUtil.getWeekDayMonthDayYearDateFormatted(day.add(Duration(days: 1)), "en"); + selectedDateDisplay = isArabic + ? DateUtil.getMonthDayYearDateFormattedAr(day) + : DateUtil.getMonthDayYearDateFormatted(day); + selectedNextDate = DateUtil.getWeekDayMonthDayYearDateFormatted(day.add(Duration(days: 1)), isArabic ? "ar" : "en"); _calendarController.selectedDate = day; openTimeSlotsPickerForDate(day, bookAppointmentsViewModel.docFreeSlots); selectedDate = formatter.format(day); @@ -349,14 +361,14 @@ class TimeSlotChip extends StatelessWidget { ? Container( padding: EdgeInsets.symmetric(horizontal: 14.h, vertical: 8.h), decoration: ShapeDecoration( - color: isSelected ? AppColors.warningColorYellow : AppColors.textColor, + color: isSelected ? AppColors.warningColorYellow : AppColors.whiteColor, shape: SmoothRectangleBorder( borderRadius: BorderRadius.circular(8.h), smoothness: 1, side: BorderSide(color: isSelected ? AppColors.warningColorYellow : AppColors.borderOnlyColor.withOpacity(0.2), width: 1), ), ), - child: label.toText12(color: isSelected ? AppColors.whiteColor : Colors.black87, fontWeight: FontWeight.w500, isEnglishOnly: true), + child: label.toText12(color: isSelected ? AppColors.whiteColor : Colors.black87, isBold: true, isEnglishOnly: true), ) : Container( padding: EdgeInsets.symmetric(horizontal: 14.h, vertical: 8.h), @@ -368,7 +380,7 @@ class TimeSlotChip extends StatelessWidget { side: BorderSide(color: isSelected ? AppColors.primaryRedColor : AppColors.borderOnlyColor.withOpacity(0.2), width: 1), ), ), - child: label.toText12(color: isSelected ? AppColors.primaryRedColor : AppColors.textColor, fontWeight: FontWeight.w500, isEnglishOnly: true), + child: label.toText12(color: isSelected ? AppColors.primaryRedColor : AppColors.textColor, isBold: true, isEnglishOnly: true), ), ); } diff --git a/lib/presentation/book_appointment/widgets/doctor_card.dart b/lib/presentation/book_appointment/widgets/doctor_card.dart index 307b73ad..7b9ce848 100644 --- a/lib/presentation/book_appointment/widgets/doctor_card.dart +++ b/lib/presentation/book_appointment/widgets/doctor_card.dart @@ -1,6 +1,8 @@ import 'package:easy_localization/easy_localization.dart'; import 'package:flutter/material.dart'; import 'package:hmg_patient_app_new/core/app_assets.dart'; +import 'package:hmg_patient_app_new/core/app_state.dart'; +import 'package:hmg_patient_app_new/core/dependencies.dart'; import 'package:hmg_patient_app_new/core/utils/date_util.dart'; import 'package:hmg_patient_app_new/core/utils/size_utils.dart'; import 'package:hmg_patient_app_new/core/utils/utils.dart'; @@ -103,13 +105,20 @@ class DoctorCard extends StatelessWidget { SizedBox( child: (isLoading ? "Consultant Cardiologist" - : doctorsListResponseModel.speciality!.isNotEmpty - ? (doctorsListResponseModel.speciality!.first.length > 32 - ? '${doctorsListResponseModel.speciality!.first.substring(0, 32)}...' - : doctorsListResponseModel.speciality!.first) + : (getIt.get().isArabic() && doctorsListResponseModel.specialityN != null) + ? doctorsListResponseModel.specialityN!.isNotEmpty + ? (doctorsListResponseModel.specialityN!.first.length > 32 + ? '${doctorsListResponseModel.specialityN!.first.substring(0, 32)}...' + : doctorsListResponseModel.specialityN!.first) + : "" + : doctorsListResponseModel.speciality != null ? + doctorsListResponseModel.speciality!.isNotEmpty + ? (doctorsListResponseModel.speciality!.first.length > 32 + ? '${doctorsListResponseModel.speciality!.first.substring(0, 32)}...' + : doctorsListResponseModel.speciality!.first) : "" : "") .toString() - .toText12(fontWeight: FontWeight.w500, color: AppColors.textColor, maxLine: 2, isEnglishOnly: true) + .toText12(isBold: true, color: AppColors.textColor, maxLine: 2, isEnglishOnly: true) .toShimmer2(isShow: isLoading), ), SizedBox(width: 6.w), @@ -150,10 +159,12 @@ class DoctorCard extends StatelessWidget { ], ), ), - Expanded( - flex: 1, - child: Utils.buildSvgWithAssets(icon: AppAssets.doctor_profile_icon, width: 20.h, height: 20.h, fit: BoxFit.scaleDown).toShimmer2(isShow: isLoading), - ), + (doctorsListResponseModel.isLiveCare ?? true) + ? 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), + ), ], ), SizedBox(height: 16.h), @@ -253,7 +264,7 @@ class DoctorCard extends StatelessWidget { borderColor: Color(0xffFEE9EA), textColor: Color(0xffED1C2B), fontSize: 14.f, - fontWeight: FontWeight.w500, + isBold: true, borderRadius: 10.r, padding: EdgeInsets.fromLTRB(10.h, 0, 10.h, 0), height: 40.h, diff --git a/lib/presentation/book_appointment/widgets/doctor_rating_details.dart b/lib/presentation/book_appointment/widgets/doctor_rating_details.dart index c146820d..df88b366 100644 --- a/lib/presentation/book_appointment/widgets/doctor_rating_details.dart +++ b/lib/presentation/book_appointment/widgets/doctor_rating_details.dart @@ -131,6 +131,7 @@ class DoctorRatingDialog extends StatelessWidget { Text( averageRating.toString(), style: TextStyle( + fontFamily: "Poppins", fontSize: 48.f, fontWeight: FontWeight.w700, color: averageRating == 0 || averageRating == 0.0 ? AppColors.textColor : AppColors.whiteColor, @@ -150,12 +151,12 @@ class DoctorRatingDialog extends StatelessWidget { child: Row( children: [ NumberFormat.decimalPattern().format(totalReviews).toText12( - fontWeight: FontWeight.w500, + isBold: true, color: averageRating == 0 || averageRating == 0.0 ? AppColors.textColor : AppColors.whiteColor, isEnglishOnly: true ), ' ${LocaleKeys.reviews.tr(context: context)}'.toText12( - fontWeight: FontWeight.w500, + isBold: true, color: averageRating == 0 || averageRating == 0.0 ? AppColors.textColor : AppColors.whiteColor, ), ], @@ -164,11 +165,11 @@ class DoctorRatingDialog extends StatelessWidget { // '${NumberFormat.decimalPattern().format(totalReviews)} ${LocaleKeys.reviews.tr(context: context)}', // style: TextStyle( // fontSize: 12.f, - // fontWeight: FontWeight.w500, + // isBold: true, // color: averageRating == 0 || averageRating == 0.0 ? AppColors.textColor : AppColors.whiteColor, // ), // ), - ), + ).paddingSymmetrical(12.h, 0.h), ], ), ), @@ -236,6 +237,7 @@ class DoctorRatingDialog extends StatelessWidget { '$percent%', textAlign: TextAlign.right, style: TextStyle( + fontFamily: "Poppins", fontSize: 14, fontWeight: FontWeight.w600, color: AppColors.ratingPercentageTextColor, diff --git a/lib/presentation/child_vaccine/my_child_vaccine_page.dart b/lib/presentation/child_vaccine/my_child_vaccine_page.dart index 6a226ab4..3ec6a09e 100644 --- a/lib/presentation/child_vaccine/my_child_vaccine_page.dart +++ b/lib/presentation/child_vaccine/my_child_vaccine_page.dart @@ -84,11 +84,11 @@ // Column( // crossAxisAlignment: CrossAxisAlignment.start, // children: [ -// LocaleKeys.city.tr().toText16(color: AppColors.textColor, weight: FontWeight.w500), +// LocaleKeys.city.tr().toText16(color: AppColors.textColor, isBold: true), // (appState.isArabic() // ? (bloodDonationVM.selectedCity?.descriptionN ?? LocaleKeys.select.tr()) // : bloodDonationVM.selectedCity?.description ?? LocaleKeys.select.tr(context: context)) -// .toText14(color: AppColors.greyTextColor, weight: FontWeight.w500), +// .toText14(color: AppColors.greyTextColor, isBold: true), // ], // ), // ], @@ -112,11 +112,11 @@ // Column( // crossAxisAlignment: CrossAxisAlignment.start, // children: [ -// LocaleKeys.gender.tr().toText16(color: AppColors.textColor, weight: FontWeight.w500), +// LocaleKeys.gender.tr().toText16(color: AppColors.textColor, isBold: true), // (appState.isArabic() // ? (bloodDonationVM.selectedGender?.typeAr ?? LocaleKeys.select.tr()) // : bloodDonationVM.selectedGender?.type ?? LocaleKeys.select.tr(context: context)) -// .toText14(color: AppColors.greyTextColor, weight: FontWeight.w500), +// .toText14(color: AppColors.greyTextColor, isBold: true), // ], // ), // ], @@ -143,12 +143,12 @@ // Column( // crossAxisAlignment: CrossAxisAlignment.start, // children: [ -// LocaleKeys.bloodType.tr().toText16(color: AppColors.textColor, weight: FontWeight.w500), -// // bloodDonationVM.selectedBloodType?.toText14(color: AppColors.greyTextColor, weight: FontWeight.w500), +// LocaleKeys.bloodType.tr().toText16(color: AppColors.textColor, isBold: true), +// // bloodDonationVM.selectedBloodType?.toText14(color: AppColors.greyTextColor, isBold: true), // (appState.isArabic() // ? (bloodDonationVM.selectedBloodType ?? LocaleKeys.select.tr()) // : bloodDonationVM.selectedBloodType ?? LocaleKeys.select.tr(context: context)) -// .toText14(color: AppColors.greyTextColor, weight: FontWeight.w500) +// .toText14(color: AppColors.greyTextColor, isBold: true) // ], // ), // ], @@ -307,7 +307,7 @@ class _MyChildVaccinePageState extends State { "${children[index]['name']} (${children[index]['gender']})", style: TextStyle( color: isSelected ? AppColors.primaryRedColor : AppColors.textColor, - fontWeight: isSelected ? FontWeight.w600 : FontWeight.w500, + fontWeight: isSelected ? FontWeight.w600 : FontWeight.w600, fontSize: 14.f, ), ), @@ -343,7 +343,7 @@ class _MyChildVaccinePageState extends State { 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), + "Due Date : ${data['dueDate']}".toText12(isBold: true, color: AppColors.textColor), ], ), ), @@ -354,7 +354,7 @@ class _MyChildVaccinePageState extends State { 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), + vaccine.toString().toText14(isBold: true, color: AppColors.textColor), ], ), )), diff --git a/lib/presentation/comprehensive_checkup/cmc_order_detail_page.dart b/lib/presentation/comprehensive_checkup/cmc_order_detail_page.dart index dd836a64..fa0fdcdb 100644 --- a/lib/presentation/comprehensive_checkup/cmc_order_detail_page.dart +++ b/lib/presentation/comprehensive_checkup/cmc_order_detail_page.dart @@ -105,7 +105,7 @@ class _CmcOrderDetailPageState extends State { child: (isLoading ? "Processing" : order.statusText ?? '') .toText12( color: statusColor, - fontWeight: FontWeight.w600, + isBold: true, ) .toShimmer2(isShow: isLoading), ), @@ -113,7 +113,7 @@ class _CmcOrderDetailPageState extends State { (isLoading ? "Jan 15, 2024" : _formatDate(order.created)) .toText12( color: AppColors.textColorLight, - fontWeight: FontWeight.w500, + isBold: true, ) .toShimmer2(isShow: isLoading), ], @@ -127,7 +127,7 @@ class _CmcOrderDetailPageState extends State { if (!isLoading) ...[ LocaleKeys.requestID.tr(context: context).toText14( color: AppColors.textColorLight, - weight: FontWeight.w500, + weight: FontWeight.w600, ), SizedBox(width: 4.w), ], @@ -171,7 +171,7 @@ class _CmcOrderDetailPageState extends State { borderColor: AppColors.primaryRedColor, textColor: AppColors.whiteColor, fontSize: 14.f, - fontWeight: FontWeight.w600, + isBold: true, borderRadius: 10.r, height: 44.h, ).toShimmer2(isShow: isLoading), diff --git a/lib/presentation/comprehensive_checkup/cmc_selection_review_page.dart b/lib/presentation/comprehensive_checkup/cmc_selection_review_page.dart index 8dcf6183..94bc0e13 100644 --- a/lib/presentation/comprehensive_checkup/cmc_selection_review_page.dart +++ b/lib/presentation/comprehensive_checkup/cmc_selection_review_page.dart @@ -356,7 +356,7 @@ class _CmcSelectionReviewPageState extends State { children: [ LocaleKeys.hereIsYourRequestNumber.tr(context: context).toText14( color: AppColors.textColorLight, - weight: FontWeight.w500, + weight: FontWeight.w600, ), SizedBox(width: 4.w), ("$requestId").toText16(isBold: true), diff --git a/lib/presentation/comprehensive_checkup/comprehensive_checkup_page.dart b/lib/presentation/comprehensive_checkup/comprehensive_checkup_page.dart index da926cd2..589764c1 100644 --- a/lib/presentation/comprehensive_checkup/comprehensive_checkup_page.dart +++ b/lib/presentation/comprehensive_checkup/comprehensive_checkup_page.dart @@ -112,14 +112,14 @@ class _ComprehensiveCheckupPageState extends State { ), child: statusDisp.toText12( color: statusColor, - fontWeight: FontWeight.w600, + isBold: true, ), ), SizedBox(width: 8.w), if (order.created != null) DateFormat('MMM dd, yyyy').format(DateTime.parse(order.created!)).toText12( color: AppColors.textColorLight, - fontWeight: FontWeight.w500, + isBold: true, ), ], ), @@ -129,7 +129,7 @@ class _ComprehensiveCheckupPageState extends State { // Request ID Row( children: [ - LocaleKeys.requestID.tr(context: context).toText14(color: AppColors.textColorLight, weight: FontWeight.w500), + LocaleKeys.requestID.tr(context: context).toText14(color: AppColors.textColorLight, isBold: true), SizedBox(width: 4.w), "${order.iD ?? '-'}".toText16(isBold: true), ], @@ -182,7 +182,7 @@ class _ComprehensiveCheckupPageState extends State { Expanded( child: LocaleKeys.pendingOrderWait.tr(context: context).toText12( color: AppColors.infoBannerTextColor, - fontWeight: FontWeight.w500, + isBold: true, ), ), ], @@ -200,7 +200,7 @@ class _ComprehensiveCheckupPageState extends State { borderColor: AppColors.primaryRedColor, textColor: AppColors.whiteColor, fontSize: 14.f, - fontWeight: FontWeight.w600, + isBold: true, borderRadius: 10.r, height: 44.h, ), diff --git a/lib/presentation/comprehensive_checkup/widgets/cmc_hospital_bottom_sheet_body.dart b/lib/presentation/comprehensive_checkup/widgets/cmc_hospital_bottom_sheet_body.dart index 93c8d5fd..6f24e5e4 100644 --- a/lib/presentation/comprehensive_checkup/widgets/cmc_hospital_bottom_sheet_body.dart +++ b/lib/presentation/comprehensive_checkup/widgets/cmc_hospital_bottom_sheet_body.dart @@ -85,7 +85,7 @@ class CmcHospitalBottomSheetBody extends StatelessWidget { ? _buildLoadingShimmer() : hmgServicesViewModel.filteredHospitalsList.isEmpty ? Center( - child: LocaleKeys.noHospitalsFound.tr(context: context).toText16(weight: FontWeight.w500, color: AppColors.greyTextColor), + child: LocaleKeys.noHospitalsFound.tr(context: context).toText16(isBold: true, color: AppColors.greyTextColor), ) : ListView.separated( itemCount: hmgServicesViewModel.filteredHospitalsList.length, diff --git a/lib/presentation/comprehensive_checkup/widgets/cmc_hospital_list_item.dart b/lib/presentation/comprehensive_checkup/widgets/cmc_hospital_list_item.dart index 25cd5068..b8ac54cc 100644 --- a/lib/presentation/comprehensive_checkup/widgets/cmc_hospital_list_item.dart +++ b/lib/presentation/comprehensive_checkup/widgets/cmc_hospital_list_item.dart @@ -1,5 +1,6 @@ import 'dart:developer'; +import 'package:easy_localization/easy_localization.dart'; import 'package:flutter/material.dart'; import 'package:hmg_patient_app_new/core/app_assets.dart'; import 'package:hmg_patient_app_new/core/app_state.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/my_appointments/models/resp_models/hospital_model.dart'; +import 'package:hmg_patient_app_new/generated/locale_keys.g.dart'; import 'package:hmg_patient_app_new/theme/colors.dart'; import 'package:hmg_patient_app_new/widgets/chip/app_custom_chip_widget.dart'; @@ -107,7 +109,7 @@ class CmcHospitalListItem extends StatelessWidget { Visibility( visible: (hospital.distanceInKilometers == null || hospital.distanceInKilometers == 0), child: AppCustomChipWidget( - labelText: " Distance not available", + labelText: LocaleKeys.noDistance.tr(), textColor: AppColors.blackColor, ), ), diff --git a/lib/presentation/contact_us/contact_us.dart b/lib/presentation/contact_us/contact_us.dart index fbbd11bc..f4b16ec4 100644 --- a/lib/presentation/contact_us/contact_us.dart +++ b/lib/presentation/contact_us/contact_us.dart @@ -126,7 +126,7 @@ class ContactUs extends StatelessWidget { crossAxisAlignment: CrossAxisAlignment.start, children: [ title.toText16(isBold: true, color: AppColors.textColor), - subTitle.toText12(fontWeight: FontWeight.w500, color: AppColors.greyTextColor), + subTitle.toText12(isBold: true, color: AppColors.greyTextColor), ], ), ), diff --git a/lib/presentation/contact_us/feedback_page.dart b/lib/presentation/contact_us/feedback_page.dart index 93954af9..cc70f28e 100644 --- a/lib/presentation/contact_us/feedback_page.dart +++ b/lib/presentation/contact_us/feedback_page.dart @@ -125,7 +125,7 @@ class FeedbackPage extends StatelessWidget { borderColor: AppColors.primaryRedColor, textColor: AppColors.whiteColor, fontSize: 16, - fontWeight: FontWeight.w500, + isBold: true, borderRadius: 12, padding: EdgeInsets.fromLTRB(10, 0, 10, 0), height: 50.h, @@ -146,7 +146,7 @@ class FeedbackPage extends StatelessWidget { return Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ - LocaleKeys.likeToHear.tr().toText14(weight: FontWeight.w500), + LocaleKeys.likeToHear.tr().toText14(isBold: true), SizedBox(height: 16.h), Container( decoration: RoundedRectangleBorder().toSmoothCornerDecoration( @@ -167,9 +167,9 @@ class FeedbackPage extends StatelessWidget { Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ - LocaleKeys.feedbackType.tr().toText16(color: AppColors.textColor, weight: FontWeight.w500), + LocaleKeys.feedbackType.tr().toText16(color: AppColors.textColor, isBold: true), (getIt.get().isArabic() ? contactUsViewModel.selectedFeedbackType.nameAR : contactUsViewModel.selectedFeedbackType.nameEN) - .toText14(color: AppColors.greyTextColor, weight: FontWeight.w500), + .toText14(color: AppColors.greyTextColor, isBold: true), ], ), ], @@ -197,7 +197,7 @@ class FeedbackPage extends StatelessWidget { getIt.get().isArabic() ? contactUsViewModel.feedbackTypeList[index].nameAR : contactUsViewModel.feedbackTypeList[index].nameEN, style: TextStyle( fontSize: 16.h, - fontWeight: FontWeight.w500, + fontWeight: FontWeight.w600, ), ), value: contactUsViewModel.feedbackTypeList[index], @@ -286,10 +286,13 @@ class FeedbackPage extends StatelessWidget { children: [ AppCustomChipWidget(labelText: contactUsViewModel.patientFeedbackSelectedAppointment!.clinicName!).toShimmer2(isShow: false), AppCustomChipWidget(labelText: contactUsViewModel.patientFeedbackSelectedAppointment!.projectName!).toShimmer2(isShow: false), - AppCustomChipWidget( - icon: AppAssets.appointment_calendar_icon, - labelText: DateUtil.formatDateToDate(DateUtil.convertStringToDate(contactUsViewModel.patientFeedbackSelectedAppointment!.appointmentDate), false), - ).toShimmer2(isShow: false), + Directionality( + textDirection: ui.TextDirection.ltr, + child: AppCustomChipWidget( + icon: AppAssets.appointment_calendar_icon, + labelText: DateUtil.formatDateToDate(DateUtil.convertStringToDate(contactUsViewModel.patientFeedbackSelectedAppointment!.appointmentDate), false), + isEnglishOnly: true).toShimmer2(isShow: false), + ), ], ), ], @@ -349,7 +352,7 @@ class FeedbackPage extends StatelessWidget { borderColor: AppColors.secondaryLightRedColor, textColor: AppColors.primaryRedColor, fontSize: 14.f, - fontWeight: FontWeight.w500, + isBold: true, borderRadius: 10.r, padding: EdgeInsets.symmetric(horizontal: 10.w), height: 40.h, @@ -475,7 +478,7 @@ class FeedbackPage extends StatelessWidget { .toText16(weight: FontWeight.bold, fontFamily: Utils.isArabicText(contactUsViewModel.cocItemsList[index].cOCTitle ?? "") ? "GESSTwo" : "Poppins"), SizedBox(height: 8.h), "${contactUsViewModel.cocItemsList[index].detail}".toText14( - weight: FontWeight.w500, + weight: FontWeight.w600, color: AppColors.textColorLight, fontFamily: Utils.isArabicText(contactUsViewModel.cocItemsList[index].detail ?? "") ? "GESSTwo" : "Poppins"), ], diff --git a/lib/presentation/contact_us/find_us_page.dart b/lib/presentation/contact_us/find_us_page.dart index 4b272f50..e1dff5e7 100644 --- a/lib/presentation/contact_us/find_us_page.dart +++ b/lib/presentation/contact_us/find_us_page.dart @@ -59,7 +59,7 @@ class FindUsPage extends StatelessWidget { children: [ LocaleKeys.sortByLocation.tr(context: context).toText14(isBold: true), SizedBox(height: 4.h), - LocaleKeys.sortByNearestLocation.tr(context: context).toText11(color: AppColors.textColorLight, weight: FontWeight.w500), + LocaleKeys.sortByNearestLocation.tr(context: context).toText11(color: AppColors.textColorLight, isBold: true), ], ), const Spacer(), diff --git a/lib/presentation/contact_us/live_chat_page.dart b/lib/presentation/contact_us/live_chat_page.dart index 98fc9954..0fd14b6c 100644 --- a/lib/presentation/contact_us/live_chat_page.dart +++ b/lib/presentation/contact_us/live_chat_page.dart @@ -239,7 +239,7 @@ class LiveChatPage extends StatelessWidget { borderColor: contactUsVM.selectedLiveChatProjectIndex == -1 ? AppColors.greyColor : AppColors.primaryRedColor, textColor: Colors.white, fontSize: 16.f, - fontWeight: FontWeight.w500, + fontWeight: FontWeight.w600, borderRadius: 12, padding: EdgeInsets.fromLTRB(10, 0, 10, 0), height: 50.h, 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 1cc21939..1f7f2443 100644 --- a/lib/presentation/contact_us/widgets/find_us_item_card.dart +++ b/lib/presentation/contact_us/widgets/find_us_item_card.dart @@ -123,7 +123,7 @@ class FindUsItemCard extends StatelessWidget { textColor: AppColors.blackColor, borderWidth: 1.h, fontSize: (isFoldable || isTablet) ? 12.f : 14.f, - fontWeight: FontWeight.w500, + fontWeight: FontWeight.w600, borderRadius: 12.r, padding: EdgeInsets.symmetric(horizontal: 10.w), height: 40.h, diff --git a/lib/presentation/covid19test/covid19_landing_page.dart b/lib/presentation/covid19test/covid19_landing_page.dart index a241da60..63ebe8f5 100644 --- a/lib/presentation/covid19test/covid19_landing_page.dart +++ b/lib/presentation/covid19test/covid19_landing_page.dart @@ -102,7 +102,7 @@ class _Covid19LandingPageState extends State { borderColor: AppColors.primaryRedColor, textColor: AppColors.whiteColor, fontSize: 16.f, - fontWeight: FontWeight.w500, + isBold: true, borderRadius: 12.r, padding: EdgeInsets.symmetric(horizontal: 10.w), height: 50.h, @@ -259,7 +259,7 @@ class _Covid19LandingPageState extends State { : AppColors.greyTextColor.withValues(alpha: 0.3), textColor: AppColors.whiteColor, fontSize: 16.f, - fontWeight: FontWeight.w600, + isBold: true, borderRadius: 12.r, height: 56.h, ), diff --git a/lib/presentation/covid19test/covid_19_questionnaire.dart b/lib/presentation/covid19test/covid_19_questionnaire.dart index f7b1f4c0..bb12e448 100644 --- a/lib/presentation/covid19test/covid_19_questionnaire.dart +++ b/lib/presentation/covid19test/covid_19_questionnaire.dart @@ -71,7 +71,7 @@ class _Covid19QuestionnaireState extends State { borderColor: AppColors.primaryRedColor, textColor: AppColors.whiteColor, fontSize: 16.f, - fontWeight: FontWeight.w600, + isBold: true, borderRadius: 12.r, height: 56.h, )), @@ -93,7 +93,7 @@ class _Covid19QuestionnaireState extends State { children: [ "Please answer below questionnaire:".toText14( color: AppColors.textColor, - weight: FontWeight.w500, + weight: FontWeight.w600, ), SizedBox(height: 20.h), // Question list diff --git a/lib/presentation/covid19test/covid_payment_screen.dart b/lib/presentation/covid19test/covid_payment_screen.dart index 40bf2863..a846828f 100644 --- a/lib/presentation/covid19test/covid_payment_screen.dart +++ b/lib/presentation/covid19test/covid_payment_screen.dart @@ -178,7 +178,7 @@ class _CovidPaymentScreenState extends State { // borderColor: AppColors.primaryRedColor, // textColor: AppColors.whiteColor, // fontSize: 16.f, - // fontWeight: FontWeight.w500, + // isBold: true, // borderRadius: 12.r, // padding: EdgeInsets.symmetric(horizontal: 10.w), // icon: AppAssets.add_icon, diff --git a/lib/presentation/covid19test/covid_review_screen.dart b/lib/presentation/covid19test/covid_review_screen.dart index 34401298..be6bdb71 100644 --- a/lib/presentation/covid19test/covid_review_screen.dart +++ b/lib/presentation/covid19test/covid_review_screen.dart @@ -279,7 +279,7 @@ class _CovidReviewScreenState extends State { borderColor: AppColors.primaryRedColor, textColor: AppColors.whiteColor, fontSize: 16.f, - fontWeight: FontWeight.w500, + fontWeight: FontWeight.w600, borderRadius: 12.r, padding: EdgeInsets.symmetric(horizontal: 10.w), // icon: AppAssets.add_icon, @@ -305,7 +305,7 @@ class _CovidReviewScreenState extends State { "Please select the procedure:".toText14( color: AppColors.textColor, - weight: FontWeight.w500, + weight: FontWeight.w600, ), SizedBox(height: 16.h), diff --git a/lib/presentation/e_referral/e_referral_search_result.dart b/lib/presentation/e_referral/e_referral_search_result.dart index 6a4b8318..29c7a294 100644 --- a/lib/presentation/e_referral/e_referral_search_result.dart +++ b/lib/presentation/e_referral/e_referral_search_result.dart @@ -75,7 +75,7 @@ class _SearchResultPageState extends State { // style: TextStyle( // color: Colors.white, // fontSize: 12, - // fontWeight: FontWeight.w500, + // isBold: true, // ), ), ), diff --git a/lib/presentation/e_referral/new_e_referral.dart b/lib/presentation/e_referral/new_e_referral.dart index 844ef5d5..8631fa62 100644 --- a/lib/presentation/e_referral/new_e_referral.dart +++ b/lib/presentation/e_referral/new_e_referral.dart @@ -104,10 +104,9 @@ class _NewReferralPageState extends State { duration: const Duration(milliseconds: 300), curve: Curves.easeInOut, ); - // setState(() { - _currentStep++; - // }); - // widget.onStepChanged(_currentStep); + setState(() { + _currentStep++; + }); } void _submitReferral() { @@ -133,8 +132,10 @@ class _NewReferralPageState extends State { hmgServicesVM.createEReferral( requestModel: createReferralRequestModel, onSuccess: (GenericApiModel response) { - showSuccessBottomSheet(int.parse(response.data), hmgServicesVM); LoaderBottomSheet.hideLoader(); + showSuccessBottomSheet(int.parse(response.data), hmgServicesVM); + + }, onError: (errorMessage) { // Handle error (e.g., show error message) @@ -190,7 +191,8 @@ class _NewReferralPageState extends State { Row( mainAxisAlignment: MainAxisAlignment.center, children: List.generate(3, (index) { - if (_currentStep == index) { + // Fill current step and all previous completed steps + if (index <= _currentStep) { return StepperWidget(widthOfOneState, AppColors.primaryRedColor, true, 4.h); } else { return StepperWidget(widthOfOneState, AppColors.greyLightColor, false, 4.h); @@ -200,8 +202,8 @@ class _NewReferralPageState extends State { child: PageView( controller: _pageController, physics: const NeverScrollableScrollPhysics(), - onPageChanged: (index) => { - // setState(() => _currentStep = index) + onPageChanged: (index) { + setState(() => _currentStep = index); }, children: [ RequesterFormStep(), @@ -227,7 +229,7 @@ class _NewReferralPageState extends State { children: [ LocaleKeys.hereIsYourReferralNumber.tr(context: context).toText14( color: AppColors.textColorLight, - weight: FontWeight.w500, + weight: FontWeight.w600, ), SizedBox(width: 4.w), ("$requestId").toText16(isBold: true), diff --git a/lib/presentation/e_referral/widget/e-referral_otp.dart b/lib/presentation/e_referral/widget/e-referral_otp.dart index 17ae4272..970dedaa 100644 --- a/lib/presentation/e_referral/widget/e-referral_otp.dart +++ b/lib/presentation/e_referral/widget/e-referral_otp.dart @@ -63,28 +63,12 @@ class OTPService { Navigator.pop(context); final hmgServicesViewModel = context.read(); - LoaderBottomSheet.showLoader(); - hmgServicesViewModel.eReferralSendActivationCode( - requestModel: SendActivationCodeForEReferralRequestModel( - patientMobileNumber: int.parse(formManager.formData.requesterPhone), - zipCode: formManager.formData.countryEnum.countryCode, - patientOutSA: formManager.formData.countryEnum.countryCode == '966' ? 0 : 1, - ), - onSuccess: (GenericApiModel response) { - LoaderBottomSheet.hideLoader(); - hmgServicesViewModel.navigateToOTPScreen( - otpTypeEnum: OTPTypeEnum.sms, - phoneNumber: formManager.formData.requesterPhone, - loginToken: response.data, - onSuccess: () { - Navigator.pop(context); - onSuccess(); - }); - }, - onError: (String errorMessage) { - LoaderBottomSheet.hideLoader(); - ScaffoldMessenger.of(context).showSnackBar(SnackBar(content: Text(errorMessage))); - }, + // Start listening for SMS before sending OTP + _startSmsListener( + context: context, + hmgServicesViewModel: hmgServicesViewModel, + formManager: formManager, + onSuccess: onSuccess, ); } }, @@ -100,4 +84,37 @@ class OTPService { )), ); } + + static void _startSmsListener({ + required BuildContext context, + required HmgServicesViewModel hmgServicesViewModel, + required ReferralFormManager formManager, + required Function onSuccess, + }) async { + LoaderBottomSheet.showLoader(); + + // Send OTP after starting listener + hmgServicesViewModel.eReferralSendActivationCode( + requestModel: SendActivationCodeForEReferralRequestModel( + patientMobileNumber: int.parse(formManager.formData.requesterPhone), + zipCode: formManager.formData.countryEnum.countryCode, + patientOutSA: formManager.formData.countryEnum.countryCode == '966' ? 0 : 1, + ), + onSuccess: (GenericApiModel response) { + LoaderBottomSheet.hideLoader(); + hmgServicesViewModel.navigateToOTPScreen( + otpTypeEnum: OTPTypeEnum.sms, + phoneNumber: formManager.formData.requesterPhone, + loginToken: response.data, + onSuccess: () { + Navigator.pop(context); + onSuccess(); + }); + }, + onError: (String errorMessage) { + LoaderBottomSheet.hideLoader(); + ScaffoldMessenger.of(context).showSnackBar(SnackBar(content: Text(errorMessage))); + }, + ); + } } diff --git a/lib/presentation/e_referral/widget/e_referral_other_details.dart b/lib/presentation/e_referral/widget/e_referral_other_details.dart index 114b3b24..579f281f 100644 --- a/lib/presentation/e_referral/widget/e_referral_other_details.dart +++ b/lib/presentation/e_referral/widget/e_referral_other_details.dart @@ -183,7 +183,7 @@ class _OtherDetailsStepState extends State { color: Colors.black, weight: FontWeight.w600, ), - // style: TextStyle(fontSize: 14, fontWeight: FontWeight.w600), + // style: TextStyle(fontSize: 14, isBold: true), //), ), ], diff --git a/lib/presentation/emergency_services/RRT/rrt_map_screen.dart b/lib/presentation/emergency_services/RRT/rrt_map_screen.dart index 530152b1..bd5407b6 100644 --- a/lib/presentation/emergency_services/RRT/rrt_map_screen.dart +++ b/lib/presentation/emergency_services/RRT/rrt_map_screen.dart @@ -31,25 +31,26 @@ class RrtMapScreen extends StatelessWidget { @override Widget build(BuildContext context) { return Scaffold( - floatingActionButton: Visibility( - visible: context.watch().bottomSheetType == - BottomSheetType.FIXED, - child: Padding( - padding: EdgeInsetsDirectional.only(end: 8.h, bottom: 68.h), - child: DecoratedBox( - decoration: RoundedRectangleBorder().toSmoothCornerDecoration( - color: AppColors.whiteColor, borderRadius: 12.h), - child: Utils.buildSvgWithAssets( - icon: AppAssets.locate_me, width: 24.h, height: 24.h) - .paddingAll(12.h) - .onPress(() { - context - .read() - .moveToCurrentLocation(); - }), - ), - ), - ), + // floatingActionButton: Visibility( + // visible: context.watch().bottomSheetType == + // BottomSheetType.FIXED, + // child: Padding( + // padding: EdgeInsetsDirectional.only(end: 8.h, bottom: 68.h), + // child: DecoratedBox( + // decoration: RoundedRectangleBorder().toSmoothCornerDecoration( + // color: AppColors.whiteColor, borderRadius: 12.h), + // child: Container() + // // Utils.buildSvgWithAssets( + // // icon: AppAssets.locate_me, width: 24.h, height: 24.h) + // // .paddingAll(12.h) + // // .onPress(() { + // // context + // // .read() + // // .moveToCurrentLocation(); + // // }), + // ), + // ), + // ), bottomSheet: ExpandableBottomSheet( bottomSheetType: context.watch().bottomSheetType, @@ -73,6 +74,7 @@ class RrtMapScreen extends StatelessWidget { inputController: context.read().gmsController, showCenterMarker: true, + bottomPaddingHeight: MediaQuery.of(context).size.height * 0.3, ) else HMSMap( @@ -139,7 +141,7 @@ class RrtMapScreen extends StatelessWidget { color: AppColors.textColor, ), LocaleKeys.pleaseSelectTheLocation.tr().toText12( - fontWeight: FontWeight.w500, + isBold: true, color: AppColors.greyTextColor, ) ], @@ -189,10 +191,7 @@ class RrtMapScreen extends StatelessWidget { crossAxisAlignment: CrossAxisAlignment.start, spacing: 16.h, children: [ - hospitalAndPickUpSection(context), - - ], ).paddingOnly(top: 24.h, bottom: 32.h,left: 24.h, right: 24.h), @@ -227,9 +226,8 @@ class RrtMapScreen extends StatelessWidget { shrinkWrap: true, itemCount: 3, itemBuilder: (__, index) { - if (index == - 2) // todo means the end of the list so handle as per the viewmodel - { + if (index == 2) // todo means the end of the list so handle as per the viewmodel + { return CustomButton( height: 40.h, backgroundColor: AppColors.lightRedButtonColor, @@ -243,7 +241,7 @@ class RrtMapScreen extends StatelessWidget { return AddressItem( isSelected: index == 0, address: - "Flat No 301, Building No 12, Palm Spring Apartment, Sector 45, Gurugram, Haryana 122003", + "", title: index == 0 ? LocaleKeys.home.tr() : LocaleKeys.work.tr(), onTap: () {}, ); @@ -370,7 +368,7 @@ class RrtMapScreen extends StatelessWidget { children: [ title.toText12( color: AppColors.greyTextColor, - fontWeight: FontWeight.w500, + isBold: true, ), SizedBox(height: 2.h), ], @@ -378,7 +376,7 @@ class RrtMapScreen extends StatelessWidget { ), subTitle.toText14( color: AppColors.textColor, - weight: FontWeight.w500, + weight: FontWeight.w600, ), ], ), @@ -423,7 +421,7 @@ class RrtMapScreen extends StatelessWidget { height: 18.h, width: 18.h), SizedBox(width: 4.h,), LocaleKeys.amountPaidAtHospital.tr().toText12( - fontWeight: FontWeight.w500, + isBold: true, color: AppColors.greyTextColor, ), ], @@ -437,7 +435,7 @@ class RrtMapScreen extends StatelessWidget { .getTotalPrice() ?? 0)) .toText24( - fontWeight: FontWeight.w600, + isBold: true, color: AppColors.textColor, letterSpacing: -2), AppColors.blackColor, diff --git a/lib/presentation/emergency_services/RRT/rrt_request_type_select.dart b/lib/presentation/emergency_services/RRT/rrt_request_type_select.dart index 12996bd6..3b095ecf 100644 --- a/lib/presentation/emergency_services/RRT/rrt_request_type_select.dart +++ b/lib/presentation/emergency_services/RRT/rrt_request_type_select.dart @@ -79,7 +79,7 @@ class RrtRequestTypeSelect extends StatelessWidget { ), Utils.getPaymentAmountWithSymbol( (Utils.formatNumberToInternationalFormat(context.read().selectedRRTProcedure?.patientShare ?? 0)) - .toText24(fontWeight: FontWeight.w600, color: AppColors.textColor, letterSpacing: -2), + .toText24(isBold: true, color: AppColors.textColor, letterSpacing: -2), AppColors.blackColor, 17.h) ], @@ -101,7 +101,7 @@ class RrtRequestTypeSelect extends StatelessWidget { Row( children: [ LocaleKeys.vat15.tr(context: context).toText12( - fontWeight: FontWeight.w500, + isBold: true, color: AppColors.greyTextColor, ), "${emergencyServicesVM.selectedRRTProcedure?.patientTaxAmount})".toText14( @@ -131,7 +131,7 @@ class RrtRequestTypeSelect extends StatelessWidget { ), Utils.getPaymentAmountWithSymbol( (Utils.formatNumberToInternationalFormat(context.read().selectedRRTProcedure?.patientShareWithTax ?? 0)) - .toText24(fontWeight: FontWeight.w600, color: AppColors.textColor, letterSpacing: -2), + .toText24(isBold: true, color: AppColors.textColor, letterSpacing: -2), AppColors.blackColor, 17.h), ], @@ -177,7 +177,7 @@ class RrtRequestTypeSelect extends StatelessWidget { final indexOfSelectedItem = selectedProcedure != null ? procedureList.indexOf(selectedProcedure) : -1; return RadioListTile( - title: (procedure.procedureName ?? "").toText16(color: AppColors.textColor, weight: FontWeight.w500), + title: (procedure.procedureName ?? "").toText16(color: AppColors.textColor, isBold: true), value: index, fillColor: WidgetStateProperty.resolveWith((states) { if (states.contains(WidgetState.selected)) { @@ -243,8 +243,8 @@ class RrtRequestTypeSelect extends StatelessWidget { spacing: 4.w, children: [ SizedBox.shrink(), - LocaleKeys.agreeTo.tr().toText16(color: AppColors.textColor, weight: FontWeight.w500), - LocaleKeys.termsConditoins.tr().toText16(color: AppColors.errorColor, isUnderLine: true, weight: FontWeight.w500).onPress(() { + LocaleKeys.agreeTo.tr().toText16(color: AppColors.textColor, isBold: true), + LocaleKeys.termsConditoins.tr().toText16(color: AppColors.errorColor, isUnderLine: true, isBold: true).onPress(() { emergencyServicesVM.getTermsAndConditions(); }), ], 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 e5578a3e..55958048 100644 --- a/lib/presentation/emergency_services/call_ambulance/call_ambulance_page.dart +++ b/lib/presentation/emergency_services/call_ambulance/call_ambulance_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_assets.dart'; @@ -33,18 +35,18 @@ class CallAmbulancePage extends StatelessWidget { @override Widget build(BuildContext context) { return Scaffold( - floatingActionButton: Visibility( - visible: context.watch().bottomSheetType == BottomSheetType.FIXED, - child: Padding( - padding: EdgeInsetsDirectional.only(end: 8.h, bottom: 68.h), - child: DecoratedBox( - decoration: RoundedRectangleBorder().toSmoothCornerDecoration(color: AppColors.whiteColor, borderRadius: 12.h), - child: Utils.buildSvgWithAssets(icon: AppAssets.locate_me, width: 24.h, height: 24.h).paddingAll(12.h).onPress(() { - context.read().moveToCurrentLocation(); - }), - ), - ), - ), + // floatingActionButton: Visibility( + // visible: context.watch().bottomSheetType == BottomSheetType.FIXED, + // child: Padding( + // padding: EdgeInsetsDirectional.only(end: 8.h, bottom: 68.h), + // child: DecoratedBox( + // decoration: RoundedRectangleBorder().toSmoothCornerDecoration(color: AppColors.whiteColor, borderRadius: 12.h), + // child: Utils.buildSvgWithAssets(icon: AppAssets.locate_me, width: 24.h, height: 24.h).paddingAll(12.h).onPress(() { + // context.read().moveToCurrentLocation(); + // }), + // ), + // ), + // ), bottomSheet: Column( mainAxisSize: MainAxisSize.min, children: [ @@ -67,6 +69,7 @@ class CallAmbulancePage extends StatelessWidget { myLocationEnabled: true, inputController: context.read().gmsController, showCenterMarker: true, + bottomPaddingHeight: 400.h, ) else HMSMap( @@ -384,7 +387,7 @@ class CallAmbulancePage extends StatelessWidget { children: [ title.toText12( color: AppColors.greyTextColor, - fontWeight: FontWeight.w500, + isBold: true, ), SizedBox(height: 2.h), ], @@ -392,7 +395,7 @@ class CallAmbulancePage extends StatelessWidget { ), subTitle.toText14( color: AppColors.textColor, - weight: FontWeight.w500, + weight: FontWeight.w600, ), ], ), @@ -437,7 +440,7 @@ class CallAmbulancePage extends StatelessWidget { width: 4.h, ), LocaleKeys.amountPaidAtHospital.tr(context: context).toText12( - fontWeight: FontWeight.w500, + isBold: true, color: AppColors.greyTextColor, ), ], @@ -447,7 +450,7 @@ class CallAmbulancePage extends StatelessWidget { ), Utils.getPaymentAmountWithSymbol( (Utils.formatNumberToInternationalFormat(context.read().getTotalPrice() ?? 0)) - .toText24(fontWeight: FontWeight.w600, color: AppColors.textColor, letterSpacing: -2), + .toText24(isBold: true, color: AppColors.textColor, letterSpacing: -2), AppColors.blackColor, 17.h) @@ -527,7 +530,7 @@ class CallAmbulancePage extends StatelessWidget { textPlaceInput(context) { return Consumer(builder: (_, vm, __) { // print("the data is ${vm.geocodeResponse?.results.first.formattedAddress ?? vm.selectedPrediction?.description}"); - return (vm.geocodeResponse != null && vm.geocodeResponse!.results.isNotEmpty) ? SizedBox( + return SizedBox( width: MediaQuery.sizeOf(context).width, child: TextInputWidget( labelText: LocaleKeys.enterPickupLocationManually.tr(context: context), @@ -546,10 +549,11 @@ class CallAmbulancePage extends StatelessWidget { vertical: ResponsiveExtension(10).h, horizontal: ResponsiveExtension(15).h, ), + fontFamily: "Poppins", ).onPress(() { openLocationInputBottomSheet(context); }), - ) : SizedBox.shrink(); + ); }); } @@ -561,7 +565,7 @@ class CallAmbulancePage extends StatelessWidget { } openLocationInputBottomSheet(BuildContext context) { - context.read().flushSearchPredictions(); + // context.read().flushSearchPredictions(); showCommonBottomSheetWithoutHeight( title: "", context, diff --git a/lib/presentation/emergency_services/call_ambulance/requesting_services_page.dart b/lib/presentation/emergency_services/call_ambulance/requesting_services_page.dart index bc6d9b03..15d1d552 100644 --- a/lib/presentation/emergency_services/call_ambulance/requesting_services_page.dart +++ b/lib/presentation/emergency_services/call_ambulance/requesting_services_page.dart @@ -30,7 +30,7 @@ class RequestingServicesPage extends StatelessWidget { Positioned( bottom: 1, child: LocaleKeys.submitRequest.tr(context: context) - .toText16(color: AppColors.textColor, weight: FontWeight.w500) + .toText16(color: AppColors.textColor, isBold: true) .paddingOnly(bottom: 100.h, left: 100.h, right: 100.h)) ], ), diff --git a/lib/presentation/emergency_services/call_ambulance/tracking_screen.dart b/lib/presentation/emergency_services/call_ambulance/tracking_screen.dart index 9a49dd06..0f4c587b 100644 --- a/lib/presentation/emergency_services/call_ambulance/tracking_screen.dart +++ b/lib/presentation/emergency_services/call_ambulance/tracking_screen.dart @@ -220,9 +220,9 @@ class TrackingScreen extends StatelessWidget { child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ - "Dr. John Doe".toText16(color: AppColors.textColor, weight: FontWeight.w600), + "Dr. John Doe".toText16(color: AppColors.textColor, isBold: true), SizedBox(height: 4.h), - "MBBS, MD - General Medicine".toText12(color: AppColors.greyTextColor, fontWeight: FontWeight.w500), + "MBBS, MD - General Medicine".toText12(color: AppColors.greyTextColor, isBold: true), ], ), ), @@ -253,7 +253,9 @@ class TrackingScreen extends StatelessWidget { }, currentLocation: context.read().getGMSLocation(), - onCameraMoved: (value) => context.read().handleGMSMapCameraMoved(value)); + onCameraMoved: (value) => context.read().handleGMSMapCameraMoved(value), + bottomPaddingHeight: MediaQuery.of(context).size.height * 0.2, + ); } else { return HMSMap( myLocationEnabled: false, @@ -268,7 +270,7 @@ class TrackingScreen extends StatelessWidget { informationRegardingTrackingSection() { return Column( crossAxisAlignment: CrossAxisAlignment.start, - children: [currentStatus(), SizedBox(height: 4.h), "You will receive a call from HMG for confirmation ".toText12(color: AppColors.greyTextColor, fontWeight: FontWeight.w500)], + children: [currentStatus(), SizedBox(height: 4.h), "You will receive a call from HMG for confirmation ".toText12(color: AppColors.greyTextColor, isBold: true)], ); } @@ -293,7 +295,7 @@ class TrackingScreen extends StatelessWidget { text: "...", style: TextStyle( fontSize: 21.f, - fontWeight: FontWeight.w600, + fontWeight: FontWeight.w600, color: AppColors.errorColor, ), ), @@ -306,7 +308,7 @@ class TrackingScreen extends StatelessWidget { text: "15:30", style: TextStyle( fontSize: 21.f, - fontWeight: FontWeight.w600, + fontWeight: FontWeight.w600, color: AppColors.textColor, ), ), @@ -314,7 +316,7 @@ class TrackingScreen extends StatelessWidget { text: LocaleKeys.mins.tr(), style: TextStyle( fontSize: 21.f, - fontWeight: FontWeight.w600, + fontWeight: FontWeight.w600, color: AppColors.errorColor, ), ), @@ -322,7 +324,7 @@ class TrackingScreen extends StatelessWidget { text: LocaleKeys.toHospitalLower.tr(), style: TextStyle( fontSize: 21.f, - fontWeight: FontWeight.w600, + fontWeight: FontWeight.w600, color: AppColors.textColor, ), ), @@ -330,7 +332,7 @@ class TrackingScreen extends StatelessWidget { ); case OrderTrackingState.ended: - return LocaleKeys.arrived.tr().toText21(color: AppColors.textColor, weight: FontWeight.w600); + return LocaleKeys.arrived.tr().toText21(color: AppColors.textColor, isBold: true); case OrderTrackingState.failed: case OrderTrackingState.cancel: return SizedBox.shrink(); @@ -354,7 +356,7 @@ class TrackingScreen extends StatelessWidget { spacing: 8.h, children: [ - Visibility(visible:(order != null || rrtOrder != null) ,child: "Req ID: ${order?.iD?? rrtOrder?.iD}".toText16(color: AppColors.textColor, weight: FontWeight.w600)), + Visibility(visible:(order != null || rrtOrder != null) ,child: "Req ID: ${order?.iD?? rrtOrder?.iD}".toText16(color: AppColors.textColor, isBold: true)), if(order != null) ambulanceOrderData() // else rrtOrderData() @@ -388,8 +390,8 @@ class TrackingScreen extends StatelessWidget { crossAxisAlignment: CrossAxisAlignment.start, spacing: 4.h, children: [ - LocaleKeys.contact.tr().toText14(color: AppColors.textColor, weight: FontWeight.w600), - "0115259555".toText12(color: AppColors.primaryRedColor, fontWeight: FontWeight.w500).onPress((){ + LocaleKeys.contact.tr().toText14(color: AppColors.textColor, isBold: true), + "0115259555".toText12(color: AppColors.primaryRedColor, isBold: true).onPress((){ launchUrl( Uri.parse("tel://0115259555"), ); @@ -432,23 +434,23 @@ class TrackingScreen extends StatelessWidget { getTitle(OrderTrackingState? state) { if(state == null) { - return LocaleKeys.failed.tr().toText16(color: AppColors.textColor, weight: FontWeight.w600); + return LocaleKeys.failed.tr().toText16(color: AppColors.textColor, isBold: true); } switch (state) { case OrderTrackingState.waitingForCall: - return LocaleKeys.confirmationCall.tr().toText16(color: AppColors.textColor, weight: FontWeight.w600); + return LocaleKeys.confirmationCall.tr().toText16(color: AppColors.textColor, isBold: true); case OrderTrackingState.dispactched: - return LocaleKeys.pickupFromHome.tr().toText16(color: AppColors.textColor, weight: FontWeight.w600); + return LocaleKeys.pickupFromHome.tr().toText16(color: AppColors.textColor, isBold: true); case OrderTrackingState.returning: - return LocaleKeys.onTheWayToHospital.tr().toText16(color: AppColors.textColor, weight: FontWeight.w600); + return LocaleKeys.onTheWayToHospital.tr().toText16(color: AppColors.textColor, isBold: true); case OrderTrackingState.ended: - return LocaleKeys.arrivedAtHospital.tr().toText16(color: AppColors.textColor, weight: FontWeight.w600); + return LocaleKeys.arrivedAtHospital.tr().toText16(color: AppColors.textColor, isBold: true); case OrderTrackingState.failed: - return LocaleKeys.failed.tr().toText16(color: AppColors.textColor, weight: FontWeight.w600); + return LocaleKeys.failed.tr().toText16(color: AppColors.textColor, isBold: true); case OrderTrackingState.cancel: - return LocaleKeys.orderCancel.tr().toText16(color: AppColors.textColor, weight: FontWeight.w600); + return LocaleKeys.orderCancel.tr().toText16(color: AppColors.textColor, isBold: true); } } diff --git a/lib/presentation/emergency_services/call_ambulance/widgets/AddressItem.dart b/lib/presentation/emergency_services/call_ambulance/widgets/AddressItem.dart index 13e32561..f9d39434 100644 --- a/lib/presentation/emergency_services/call_ambulance/widgets/AddressItem.dart +++ b/lib/presentation/emergency_services/call_ambulance/widgets/AddressItem.dart @@ -48,7 +48,7 @@ class AddressItem extends StatelessWidget { children: [ title.toText12( color: AppColors.greyTextColor, - fontWeight: FontWeight.w500, + isBold: true, ), Row( mainAxisSize: MainAxisSize.max, @@ -56,7 +56,7 @@ class AddressItem extends StatelessWidget { Expanded( child: address.toText14( color: AppColors.textColor, - weight: FontWeight.w500, + weight: FontWeight.w600, textOverflow : TextOverflow.ellipsis, maxlines: 2 ), diff --git a/lib/presentation/emergency_services/call_ambulance/widgets/WayPickup.dart b/lib/presentation/emergency_services/call_ambulance/widgets/WayPickup.dart index 039241a8..ad28122c 100644 --- a/lib/presentation/emergency_services/call_ambulance/widgets/WayPickup.dart +++ b/lib/presentation/emergency_services/call_ambulance/widgets/WayPickup.dart @@ -31,7 +31,7 @@ class WayPickup extends StatelessWidget { height: 16.h, ), // LocaleKeys.selectDirection.tr(context: context) - // .toText16(color: AppColors.textColor, weight: FontWeight.w600), + // .toText16(color: AppColors.textColor, isBold: true), // SizedBox( // height: 12.h, // ), @@ -83,7 +83,7 @@ class WayPickup extends StatelessWidget { // children: [ // SizedBox(height: 16.h), // LocaleKeys.selectWay.tr(context: context) - // .toText16(color: AppColors.textColor, weight: FontWeight.w600), + // .toText16(color: AppColors.textColor, isBold: true), // SizedBox(height: 12.h), // Row( // mainAxisAlignment: MainAxisAlignment.start, @@ -105,7 +105,7 @@ class WayPickup extends StatelessWidget { // fillColor: MaterialStateProperty.all(AppColors.primaryRedColor), // ), // LocaleKeys.oneWay.tr(context: context) - // .toText12(color: AppColors.textColor, fontWeight: FontWeight.w500) + // .toText12(color: AppColors.textColor, isBold: true) // ], // ).onPress(() { // context @@ -130,7 +130,7 @@ class WayPickup extends StatelessWidget { // fillColor: MaterialStateProperty.all(AppColors.primaryRedColor), // ), // LocaleKeys.twoWay.tr(context: context) - // .toText14(color: AppColors.textColor, weight: FontWeight.w500) + // .toText14(color: AppColors.textColor, isBold: true) // ], // ).onPress(() { // context diff --git a/lib/presentation/emergency_services/call_ambulance/widgets/pickup_location.dart b/lib/presentation/emergency_services/call_ambulance/widgets/pickup_location.dart index d76e6192..fa4eefe1 100644 --- a/lib/presentation/emergency_services/call_ambulance/widgets/pickup_location.dart +++ b/lib/presentation/emergency_services/call_ambulance/widgets/pickup_location.dart @@ -29,7 +29,7 @@ class PickupLocation extends StatelessWidget { height: 16.h, ), // LocaleKeys.selectDirection.tr(context: context) - // .toText16(color: AppColors.textColor, weight: FontWeight.w600), + // .toText16(color: AppColors.textColor, isBold: true), // SizedBox( // height: 12.h, // ), @@ -83,7 +83,7 @@ class PickupLocation extends StatelessWidget { // fillColor: MaterialStateProperty.all(AppColors.primaryRedColor), // ), // LocaleKeys.toHospital.tr(context: context) - // .toText14(color: AppColors.textColor, weight: FontWeight.w500) + // .toText14(color: AppColors.textColor, isBold: true) // ], // ).onPress(() { // context @@ -108,7 +108,7 @@ class PickupLocation extends StatelessWidget { // fillColor: MaterialStateProperty.all(AppColors.primaryRedColor), // ), // LocaleKeys.fromHospital.tr(context: context) - // .toText14(color: AppColors.textColor, weight: FontWeight.w500) + // .toText14(color: AppColors.textColor, isBold: true) // ], // ).onPress(() { // context @@ -128,7 +128,7 @@ class PickupLocation extends StatelessWidget { // children: [ // SizedBox(height: 16.h), // LocaleKeys.selectWay.tr(context: context) - // .toText16(color: AppColors.textColor, weight: FontWeight.w600), + // .toText16(color: AppColors.textColor, isBold: true), // SizedBox(height: 12.h), // Row( // mainAxisAlignment: MainAxisAlignment.start, @@ -150,7 +150,7 @@ class PickupLocation extends StatelessWidget { // fillColor: MaterialStateProperty.all(AppColors.primaryRedColor), // ), // LocaleKeys.oneWay.tr(context: context) - // .toText12(color: AppColors.textColor, fontWeight: FontWeight.w500) + // .toText12(color: AppColors.textColor, isBold: true) // ], // ).onPress(() { // context @@ -175,7 +175,7 @@ class PickupLocation extends StatelessWidget { // fillColor: MaterialStateProperty.all(AppColors.primaryRedColor), // ), // LocaleKeys.twoWay.tr(context: context) - // .toText14(color: AppColors.textColor, weight: FontWeight.w500) + // .toText14(color: AppColors.textColor, isBold: true) // ], // ).onPress(() { // context diff --git a/lib/presentation/emergency_services/call_ambulance/widgets/transport_option_Item.dart b/lib/presentation/emergency_services/call_ambulance/widgets/transport_option_Item.dart index 13c4c919..81085ed6 100644 --- a/lib/presentation/emergency_services/call_ambulance/widgets/transport_option_Item.dart +++ b/lib/presentation/emergency_services/call_ambulance/widgets/transport_option_Item.dart @@ -59,8 +59,8 @@ class TransportOptionItem extends StatelessWidget { crossAxisAlignment: CrossAxisAlignment.start, children: [ title.toText16( - color: AppColors.textColor, weight: FontWeight.w600), - subTitle.toText12(color: AppColors.greyTextColor, fontWeight: FontWeight.w500), + color: AppColors.textColor, isBold: true), + subTitle.toText12(color: AppColors.greyTextColor, isBold: true), ], ), ), diff --git a/lib/presentation/emergency_services/emergency_services_page.dart b/lib/presentation/emergency_services/emergency_services_page.dart index c8b01d16..3d5470a8 100644 --- a/lib/presentation/emergency_services/emergency_services_page.dart +++ b/lib/presentation/emergency_services/emergency_services_page.dart @@ -90,7 +90,7 @@ class _EmergencyServicesPageState extends State { crossAxisAlignment: CrossAxisAlignment.start, children: [ LocaleKeys.callAmbulance.tr().toText16(isBold: true, color: AppColors.blackColor), - LocaleKeys.requestAmbulanceInEmergency.tr().toText12(color: AppColors.greyTextColor, fontWeight: FontWeight.w500), + LocaleKeys.requestAmbulanceInEmergency.tr().toText12(color: AppColors.greyTextColor, isBold: true), ], ), ), @@ -133,7 +133,7 @@ class _EmergencyServicesPageState extends State { SizedBox(height: 8.h), LocaleKeys.confirmation.tr().toText28(color: Colors.white, isBold: true), SizedBox(height: 8.h), - LocaleKeys.areYouSureYouWantToCallAmbulance.tr().toText14(color: Colors.white, weight: FontWeight.w500), + LocaleKeys.areYouSureYouWantToCallAmbulance.tr().toText14(color: Colors.white, isBold: true), SizedBox(height: 24.h), CustomButton( text: LocaleKeys.confirm.tr(context: context), @@ -189,7 +189,7 @@ class _EmergencyServicesPageState extends State { crossAxisAlignment: CrossAxisAlignment.start, children: [ LocaleKeys.nearester.tr(context: context).toText16(isBold: true, color: AppColors.blackColor), - LocaleKeys.getDetailsOfNearestBranch.tr().toText12(color: AppColors.greyTextColor, fontWeight: FontWeight.w500), + LocaleKeys.getDetailsOfNearestBranch.tr().toText12(color: AppColors.greyTextColor, isBold: true), ], ), ), @@ -220,7 +220,7 @@ class _EmergencyServicesPageState extends State { crossAxisAlignment: CrossAxisAlignment.start, children: [ LocaleKeys.rapidResponseTeam.tr(context: context).toText16(isBold: true, color: AppColors.blackColor), - "Comprehensive medical service for all sorts of urgent and stable cases".toText12(color: AppColors.greyTextColor, fontWeight: FontWeight.w500), + LocaleKeys.RRTDDetails.tr(context: context).toText12(color: AppColors.greyTextColor, isBold: true), ], ), ), @@ -260,7 +260,7 @@ class _EmergencyServicesPageState extends State { SizedBox(height: 8.h), LocaleKeys.confirm.tr().toText28(color: Colors.white, isBold: true), SizedBox(height: 8.h), - LocaleKeys.areYouSureYouWantToCallRRT.tr().toText14(color: Colors.white, weight: FontWeight.w500), + LocaleKeys.areYouSureYouWantToCallRRT.tr().toText14(color: Colors.white, isBold: true), SizedBox(height: 24.h), CustomButton( text: LocaleKeys.confirm.tr(context: context), @@ -342,7 +342,7 @@ class _EmergencyServicesPageState extends State { crossAxisAlignment: CrossAxisAlignment.start, children: [ LocaleKeys.emergencyCheckIn.tr(context: context).toText16(isBold: true, color: AppColors.blackColor), - LocaleKeys.priorERCheckInToSkipLine.tr().toText12(color: AppColors.greyTextColor, fontWeight: FontWeight.w500), + LocaleKeys.priorERCheckInToSkipLine.tr().toText12(color: AppColors.greyTextColor, isBold: true), ], ), ), @@ -382,7 +382,7 @@ class _EmergencyServicesPageState extends State { SizedBox(height: 8.h), LocaleKeys.confirm.tr().toText28(color: Colors.white, isBold: true), SizedBox(height: 8.h), - LocaleKeys.areYouSureYouWantToMakeERCheckIn.tr().toText14(color: Colors.white, weight: FontWeight.w500), + LocaleKeys.areYouSureYouWantToMakeERCheckIn.tr().toText14(color: Colors.white, isBold: true), SizedBox(height: 24.h), CustomButton( text: LocaleKeys.confirm.tr(context: context), @@ -503,7 +503,7 @@ class _EmergencyServicesPageState extends State { title: LocaleKeys.transportOptions.tr(), context, child: SizedBox( - height: 400.h, + height: 500.h, child: AmbulanceOptionSelectionBottomSheet(onTap: () { Navigator.of(context).pop(); openPickupDetailsBottomSheet(context); 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 7f206c17..bb9f3ffd 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 @@ -57,7 +57,7 @@ class ErOnlineCheckinHome extends StatelessWidget { crossAxisAlignment: CrossAxisAlignment.start, children: [ LocaleKeys.onlineCheckIn.tr().toText18(color: AppColors.textColor, isBold: true), - LocaleKeys.erOnlineCheckInDescription.tr().toText14(color: AppColors.greyTextColor, weight: FontWeight.w500), + LocaleKeys.erOnlineCheckInDescription.tr().toText14(color: AppColors.greyTextColor, isBold: true), ], ), ), @@ -136,7 +136,7 @@ class ErOnlineCheckinHome extends StatelessWidget { borderColor: emergencyServicesViewModel.patientHasAdvanceERBalance ? AppColors.alertColor : AppColors.primaryRedColor, textColor: AppColors.whiteColor, fontSize: 16.f, - fontWeight: FontWeight.w500, + isBold: true, borderRadius: 10.r, padding: EdgeInsets.fromLTRB(10, 0, 10, 0), height: 50.h, 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 eee31da7..913fe562 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 @@ -117,7 +117,7 @@ class ErOnlineCheckinPaymentDetailsPage extends StatelessWidget { Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ - Expanded(child: "".toText12(fontWeight: FontWeight.w500, color: AppColors.greyTextColor)), + Expanded(child: "".toText12(isBold: true, color: AppColors.greyTextColor)), LocaleKeys.vatWithAmount .tr(namedArgs: {"amount": emergencyServicesViewModel.erOnlineCheckInPaymentDetailsResponse.patientTaxAmount.toString()}) .toText14(isBold: true, color: AppColors.greyTextColor, letterSpacing: -1), @@ -154,7 +154,7 @@ class ErOnlineCheckinPaymentDetailsPage extends StatelessWidget { borderColor: AppColors.infoColor.withOpacity(0.01), textColor: AppColors.whiteColor, fontSize: 16.f, - fontWeight: FontWeight.w500, + isBold: true, borderRadius: 12.r, padding: EdgeInsets.symmetric(horizontal: 10.w), height: 56.h, 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 bc0e7344..b47ccc33 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 @@ -230,7 +230,7 @@ class _ErOnlineCheckinPaymentPageState extends State child: Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ - LocaleKeys.insuranceExpiredOrInactive.tr(context: context).toText14(color: AppColors.primaryRedColor, weight: FontWeight.w500).paddingSymmetrical(24.h, 0.h), + LocaleKeys.insuranceExpiredOrInactive.tr(context: context).toText14(color: AppColors.primaryRedColor, isBold: true).paddingSymmetrical(24.h, 0.h), CustomButton( text: LocaleKeys.updateInsurance.tr(context: context), onPressed: () { @@ -244,7 +244,7 @@ class _ErOnlineCheckinPaymentPageState extends State borderColor: AppColors.secondaryLightRedBorderColor, textColor: AppColors.whiteColor, fontSize: 10, - fontWeight: FontWeight.w500, + isBold: true, borderRadius: 8, padding: EdgeInsets.fromLTRB(15, 0, 15, 0), height: 30.h, diff --git a/lib/presentation/emergency_services/er_online_checkin/er_online_checkin_select_checkin_bottom_sheet.dart b/lib/presentation/emergency_services/er_online_checkin/er_online_checkin_select_checkin_bottom_sheet.dart index f4bbe3af..aec10522 100644 --- a/lib/presentation/emergency_services/er_online_checkin/er_online_checkin_select_checkin_bottom_sheet.dart +++ b/lib/presentation/emergency_services/er_online_checkin/er_online_checkin_select_checkin_bottom_sheet.dart @@ -109,7 +109,7 @@ class ErOnlineCheckinSelectCheckinBottomSheet extends StatelessWidget { crossAxisAlignment: CrossAxisAlignment.start, children: [ title.toText16(isBold: true, color: AppColors.textColor), - subTitle.toText12(fontWeight: FontWeight.w500, color: AppColors.greyTextColor), + subTitle.toText12(isBold: true, color: AppColors.greyTextColor), ], ), ), diff --git a/lib/presentation/emergency_services/history/widget/ambulance_history_item.dart b/lib/presentation/emergency_services/history/widget/ambulance_history_item.dart index 100c0cfa..67bdcbd8 100644 --- a/lib/presentation/emergency_services/history/widget/ambulance_history_item.dart +++ b/lib/presentation/emergency_services/history/widget/ambulance_history_item.dart @@ -38,7 +38,7 @@ class AmbulanceHistoryItem extends StatelessWidget { spacing: 8.h, children: [ RequestStatus(status: order.statusId ?? 0), - "Req ID: ${order.iD}".toText16(color: AppColors.textColor, weight: FontWeight.w600, isEnglishOnly: true), + "Req ID: ${order.iD}".toText16(color: AppColors.textColor, isBold: true, isEnglishOnly: true), Row( spacing: 4.w, children: [ diff --git a/lib/presentation/emergency_services/history/widget/rrt_item.dart b/lib/presentation/emergency_services/history/widget/rrt_item.dart index 8e910231..c85f12a5 100644 --- a/lib/presentation/emergency_services/history/widget/rrt_item.dart +++ b/lib/presentation/emergency_services/history/widget/rrt_item.dart @@ -38,7 +38,7 @@ class RRTItem extends StatelessWidget { spacing: 8.h, children: [ RequestStatus(status: order.statusId ?? 0), - "Req ID: ${order.iD}".toText16(color: AppColors.textColor, weight: FontWeight.w600, isEnglishOnly: true), + "Req ID: ${order.iD}".toText16(color: AppColors.textColor, isBold: true, isEnglishOnly: true), Row( spacing: 4.w, children: [ diff --git a/lib/presentation/emergency_services/nearest_er_page.dart b/lib/presentation/emergency_services/nearest_er_page.dart index 0ea3ab8f..ea9a1517 100644 --- a/lib/presentation/emergency_services/nearest_er_page.dart +++ b/lib/presentation/emergency_services/nearest_er_page.dart @@ -44,7 +44,7 @@ class _NearestErPageState extends State { children: [ TextInputWidget( labelText: LocaleKeys.search.tr(), - hintText: 'Type any facility name', + hintText: '', controller: searchText, onChange: (value) { debouncer.run(() { diff --git a/lib/presentation/emergency_services/widgets/location_input_bottom_sheet.dart b/lib/presentation/emergency_services/widgets/location_input_bottom_sheet.dart index db45654a..4b66abd9 100644 --- a/lib/presentation/emergency_services/widgets/location_input_bottom_sheet.dart +++ b/lib/presentation/emergency_services/widgets/location_input_bottom_sheet.dart @@ -4,6 +4,8 @@ import 'package:flutter_staggered_animations/flutter_staggered_animations.dart' show AnimationConfiguration, SlideAnimation, FadeInAnimation; import 'package:hmg_patient_app_new/core/app_assets.dart'; import 'package:hmg_patient_app_new/core/app_export.dart'; +import 'package:hmg_patient_app_new/core/app_state.dart'; +import 'package:hmg_patient_app_new/core/dependencies.dart'; import 'package:hmg_patient_app_new/core/enums.dart'; import 'package:hmg_patient_app_new/core/utils/debouncer.dart'; import 'package:hmg_patient_app_new/core/utils/utils.dart'; @@ -49,6 +51,7 @@ class LocationInputBottomSheet extends StatelessWidget { vertical: 10.h, horizontal: 15.h, ), + fontFamily: "Poppins", ), SizedBox(height: 24.h), Selector>( @@ -82,16 +85,18 @@ class LocationInputBottomSheet extends StatelessWidget { Expanded( child: prediction.description.toText14( color: AppColors.textColor, - weight: FontWeight.w500), + weight: FontWeight.w600), ), - - Utils.buildSvgWithAssets( - icon: AppAssets.forward_arrow_icon_small, - iconColor: AppColors.blackColor, + Transform.flip( + flipX: getIt.get().isArabic(), + child: Utils.buildSvgWithAssets( + icon: AppAssets.forward_arrow_icon_small, + iconColor: AppColors.blackColor, width: 20.h, height: 15.h, fit: BoxFit.contain, - ), + ), + ) ], ).paddingAll(24.h),), ), diff --git a/lib/presentation/emergency_services/widgets/nearestERItem.dart b/lib/presentation/emergency_services/widgets/nearestERItem.dart index 423b887e..58e89421 100644 --- a/lib/presentation/emergency_services/widgets/nearestERItem.dart +++ b/lib/presentation/emergency_services/widgets/nearestERItem.dart @@ -59,7 +59,7 @@ class NearestERItem extends StatelessWidget { Expanded( child: (nearestERItem.projectName?.toText16( color: AppColors.textColor, - weight: FontWeight.w600, + isBold: true ) ?? SizedBox.shrink()).toShimmer2(isShow: isLoading), ), @@ -84,7 +84,7 @@ class NearestERItem extends StatelessWidget { Directionality( textDirection: ui.TextDirection.ltr, child: AppCustomChipWidget( - labelText: "Expected waiting time: ${nearestERItem.getTime()} mins", + labelText: "${LocaleKeys.waitingTime.tr(context: context)} ${nearestERItem.getTime()} ${LocaleKeys.mins.tr(context: context)}", icon: AppAssets.waiting_time_clock, iconHasColor: false, labelPadding: EdgeInsetsDirectional.only(start: 4.h, end: 0.h), @@ -109,14 +109,14 @@ class NearestERItem extends StatelessWidget { borderColor: AppColors.secondaryLightRedColor, textColor: AppColors.primaryRedColor, iconColor: AppColors.primaryRedColor, - height: 40.h, + height: 46.h, fontSize: 14.f, - fontWeight: FontWeight.w500, + isBold: true, ).toShimmer2(isShow: isLoading), ), SizedBox(width: 8.h), SizedBox( - height: 40.h, + height: 46.h, width: 40.h, child: CustomButton( text: '', @@ -124,12 +124,11 @@ class NearestERItem extends StatelessWidget { icon: AppAssets.call_fill, onPressed: () { context.read().openDialer( nearestERItem.phonenumber??""); - }, backgroundColor: AppColors.greyColor, iconColor: AppColors.textColor, borderColor: AppColors.greyColor, - height: 40.h, + height: 46.h, ).toShimmer2(isShow: isLoading), ), ], diff --git a/lib/presentation/habib_wallet/habib_wallet_page.dart b/lib/presentation/habib_wallet/habib_wallet_page.dart index 2cdfc8b6..fd5d17a1 100644 --- a/lib/presentation/habib_wallet/habib_wallet_page.dart +++ b/lib/presentation/habib_wallet/habib_wallet_page.dart @@ -74,18 +74,26 @@ class _HabibWalletState extends State { crossAxisAlignment: CrossAxisAlignment.start, children: [ "${_appState.getAuthenticatedUser()!.firstName!} ${_appState.getAuthenticatedUser()!.lastName!}".toText19(isBold: true, color: Colors.white), - "MRN: ${_appState.getAuthenticatedUser()!.patientId!}".toText14(weight: FontWeight.w500, color: AppColors.greyTextColor, isEnglishOnly: true), + "${LocaleKeys.fileno.tr(context: context)}: ${_appState.getAuthenticatedUser()!.patientId!}" + .toText14(isBold: true, color: AppColors.greyTextColor, isEnglishOnly: true), ], ).expanded, Utils.buildSvgWithAssets(icon: AppAssets.habiblogo, width: 24.h, height: 24.h, applyThemeColor: false), ], ), Spacer(), - LocaleKeys.balanceAmount.tr(context: context).toText14(weight: FontWeight.w500, color: Colors.white), + LocaleKeys.balanceAmount.tr(context: context).toText14(isBold: true, color: Colors.white), SizedBox(height: 4.h), Consumer(builder: (context, habibWalletVM, child) { - return Utils.getPaymentAmountWithSymbol2(num.parse(NumberFormat.decimalPattern().format(habibWalletVM.habibWalletAmount)), - textColor: Colors.white, iconColor: Colors.white, iconSize: 16, isExpanded: false) + return Utils.getPaymentAmountWithSymbol2( + num.parse( + NumberFormat.decimalPattern().format(habibWalletVM.habibWalletAmount), + ), + textColor: Colors.white, + iconColor: Colors.white, + iconSize: 16, + isExpanded: false, + letterSpacing: -1) .toShimmer2(isShow: habibWalletVM.isWalletAmountLoading, radius: 12.h, width: 80.h, height: 24.h); }), ], @@ -104,7 +112,7 @@ class _HabibWalletState extends State { textColor: Colors.white, text: LocaleKeys.recharge.tr(context: context), borderWidth: 0.w, - fontWeight: FontWeight.w500, + fontWeight: FontWeight.w600, borderColor: Colors.transparent, padding: EdgeInsets.fromLTRB(4, 0, 12, 0), fontSize: 14.f, @@ -138,9 +146,9 @@ class _HabibWalletState extends State { return Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ - Expanded(child: habibWalletVM.habibWalletBalanceList[index].projectDescription!.toText16(weight: FontWeight.w500, color: AppColors.textColor)), + Expanded(child: habibWalletVM.habibWalletBalanceList[index].projectDescription!.toText16(isBold: true, color: AppColors.textColor)), Utils.getPaymentAmountWithSymbol2(num.parse(NumberFormat.decimalPattern().format(habibWalletVM.habibWalletBalanceList[index].patientAdvanceBalanceAmount!)), - textColor: AppColors.textColor, iconColor: AppColors.textColor, iconSize: 18.h, isExpanded: false, fontSize: 28.f, fontWeight: FontWeight.w500), + textColor: AppColors.textColor, iconColor: AppColors.textColor, iconSize: 18.h, isExpanded: false, fontSize: 28.f, fontWeight: FontWeight.w600, letterSpacing: -1), ], ).paddingSymmetrical(0, 12.h); }, diff --git a/lib/presentation/habib_wallet/recharge_wallet_page.dart b/lib/presentation/habib_wallet/recharge_wallet_page.dart index 74c0a69d..c671152f 100644 --- a/lib/presentation/habib_wallet/recharge_wallet_page.dart +++ b/lib/presentation/habib_wallet/recharge_wallet_page.dart @@ -23,6 +23,8 @@ import 'package:provider/provider.dart'; import 'widgets/select-medical_file.dart'; +import 'dart:ui' as ui; + class RechargeWalletPage extends StatefulWidget { const RechargeWalletPage({super.key}); @@ -81,7 +83,7 @@ class _RechargeWalletPageState extends State { crossAxisAlignment: CrossAxisAlignment.start, children: [ //TODO: Check with hussain to show AED or SAR - LocaleKeys.amount.tr(context: context).toText14(color: AppColors.greyTextColor, weight: FontWeight.w500), + LocaleKeys.amount.tr(context: context).toText14(color: AppColors.greyTextColor, isBold: true), Spacer(), Row( crossAxisAlignment: CrossAxisAlignment.end, @@ -98,7 +100,7 @@ class _RechargeWalletPageState extends State { isBorderAllowed: false, isAllowLeadingIcon: true, autoFocus: true, - fontSize: 40.f, + fontSize: 25.f, padding: EdgeInsets.symmetric(horizontal: 8.h, vertical: 0.h), focusNode: textFocusNode, isWalletAmountInput: true, @@ -108,7 +110,7 @@ class _RechargeWalletPageState extends State { ), ), const Spacer(), - LocaleKeys.sar.tr(context: context).toText20(color: AppColors.greyTextColor, weight: FontWeight.w500), + LocaleKeys.sar.tr(context: context).toText20(color: AppColors.greyTextColor, isBold: true), ], ), ], @@ -138,9 +140,9 @@ class _RechargeWalletPageState extends State { Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ - (habibWalletVM.getSelectedRechargeTypeValue()).toText12(color: AppColors.greyTextColor, fontWeight: FontWeight.w500), + (habibWalletVM.getSelectedRechargeTypeValue()).toText12(color: AppColors.greyTextColor, isBold: true), "${LocaleKeys.medicalFile.tr(context: context)}: ${habibWalletVM.fileNumber}" - .toText14(color: AppColors.textColor, weight: FontWeight.w500, letterSpacing: -0.2, isEnglishOnly: true), + .toText14(color: AppColors.textColor, isBold: true, letterSpacing: -0.2, isEnglishOnly: true), ], ), ], @@ -153,12 +155,12 @@ class _RechargeWalletPageState extends State { titleWidget: Consumer(builder: (context, habibWalletVM, child) { return habibWalletVM.currentIndex != 0 ? IconButton( - icon: Utils.buildSvgWithAssets(icon: AppAssets.arrow_back, width: 24.h, height: 24.h), + icon: Transform.flip(flipX: appState.isArabic(), child: Utils.buildSvgWithAssets(icon: AppAssets.arrow_back, width: 24.h, height: 24.h)), padding: EdgeInsets.zero, onPressed: () => habibWalletVM.setCurrentIndex(0), highlightColor: Colors.transparent, ) - : LocaleKeys.medicalFile.tr(context: context).toText20(weight: FontWeight.w600); + : LocaleKeys.medicalFile.tr(context: context).toText20(isBold: true); }), child: Consumer(builder: (context, habibWalletVM, child) { return MultiPageBottomSheet(); }), callBackFunc: () {}, isFullScreen: false, isCloseButtonVisible: true); @@ -176,11 +178,11 @@ class _RechargeWalletPageState extends State { Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ - LocaleKeys.hospital.tr(context: context).toText12(color: AppColors.greyTextColor, fontWeight: FontWeight.w500), + LocaleKeys.hospital.tr(context: context).toText12(color: AppColors.greyTextColor, isBold: true), SizedBox( width: MediaQuery.of(context).size.width * 0.55, child: (habibWalletVM.selectedHospital != null ? habibWalletVM.selectedHospital!.name : LocaleKeys.selectHospital.tr(context: context))! - .toText14(color: AppColors.textColor, weight: FontWeight.w500), + .toText14(color: AppColors.textColor, isBold: true), ), ], ), @@ -202,8 +204,8 @@ class _RechargeWalletPageState extends State { Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ - 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, isEnglishOnly: true), + LocaleKeys.email.tr(context: context).toText12(color: AppColors.greyTextColor, isBold: true), + "${appState.getAuthenticatedUser()!.emailAddress}".toText14(color: AppColors.textColor, isBold: true, letterSpacing: -0.2, isEnglishOnly: true), ], ), ], @@ -250,7 +252,7 @@ class _RechargeWalletPageState extends State { if (amountTextController.text.isEmpty) { showCommonBottomSheetWithoutHeight( context, - child: Utils.getErrorWidget(loadingText: "Please enter amount to continue."), + child: Utils.getErrorWidget(loadingText: LocaleKeys.enterAmount.tr(context: context)), callBackFunc: () { textFocusNode.requestFocus(); }, @@ -260,7 +262,7 @@ class _RechargeWalletPageState extends State { } else if (habibWalletVM.selectedHospital == null) { showCommonBottomSheetWithoutHeight( context, - child: Utils.getErrorWidget(loadingText: "Please select hospital to continue."), + child: Utils.getErrorWidget(loadingText: LocaleKeys.selectHospitalForAdvancePayment.tr(context: context)), callBackFunc: () { textFocusNode.requestFocus(); }, @@ -268,7 +270,7 @@ class _RechargeWalletPageState extends State { isCloseButtonVisible: true, ); } else { - habibWalletVM.setWalletRechargeAmount(num.parse(amountTextController.text)); + habibWalletVM.setWalletRechargeAmount(num.parse(amountTextController.text.replaceAll(',', ''))); habibWalletVM.setNotesText(notesTextController.text); // habibWalletVM.setDepositorDetails(appState.getAuthenticatedUser()!.patientId.toString(), "${appState.getAuthenticatedUser()!.firstName} ${appState.getAuthenticatedUser()!.lastName}", // appState.getAuthenticatedUser()!.mobileNumber!); @@ -283,7 +285,7 @@ class _RechargeWalletPageState extends State { borderColor: AppColors.primaryRedColor, textColor: Colors.white, fontSize: 16, - fontWeight: FontWeight.w500, + fontWeight: FontWeight.w600, borderRadius: 12, padding: EdgeInsets.fromLTRB(10, 0, 10, 0), height: 50.h, diff --git a/lib/presentation/habib_wallet/widgets/hospital_list_item.dart b/lib/presentation/habib_wallet/widgets/hospital_list_item.dart index 67e174a3..fe5172e6 100644 --- a/lib/presentation/habib_wallet/widgets/hospital_list_item.dart +++ b/lib/presentation/habib_wallet/widgets/hospital_list_item.dart @@ -1,12 +1,18 @@ +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/string_extensions.dart'; import 'package:hmg_patient_app_new/extensions/widget_extensions.dart'; import 'package:hmg_patient_app_new/features/my_appointments/models/resp_models/hospital_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'; + +import 'dart:ui' as ui; class HospitalListItemAdvancePayment extends StatelessWidget { final HospitalsModel hospitalModel; @@ -32,7 +38,7 @@ class HospitalListItemAdvancePayment extends StatelessWidget { child: Column( crossAxisAlignment: CrossAxisAlignment.start, spacing: 8.h, - children: [hospitalName], + children: [hospitalName, distanceInfo], ), ), Transform.flip( @@ -68,4 +74,45 @@ class HospitalListItemAdvancePayment extends StatelessWidget { ) ], ); + + Widget get distanceInfo => Row( + spacing: 4.w, + children: [ + Visibility( + visible: (hospitalModel.distanceInKilometers != 0), + child: Directionality( + textDirection: ui.TextDirection.ltr, + child: AppCustomChipWidget( + labelText: "${hospitalModel.distanceInKilometers ?? ""} KMs", + deleteIcon: AppAssets.location_red, + deleteIconSize: Size(9, 12), + backgroundColor: AppColors.secondaryLightRedColor, + textColor: AppColors.errorColor, + isEnglishOnly: true, + ), + ), + ), + Visibility( + visible: (hospitalModel.distanceInKilometers == 0), + child: Row( + children: [ + AppCustomChipWidget( + labelText: LocaleKeys.noDistance.tr(), + textColor: AppColors.blackColor, + ), + // SizedBox( + // width: 8.h, + // ) + ], + )), + // Visibility( + // visible: !isLocationEnabled, + // child: AppCustomChipWidget( + // labelText: "Location turned off", + // deleteIcon: AppAssets.location_unavailable, + // deleteIconSize: Size(9.w, 12.h), + // textColor: AppColors.blackColor, + // )), + ], + ); } diff --git a/lib/presentation/habib_wallet/widgets/select-medical_file.dart b/lib/presentation/habib_wallet/widgets/select-medical_file.dart index 6fed6522..0a97988c 100644 --- a/lib/presentation/habib_wallet/widgets/select-medical_file.dart +++ b/lib/presentation/habib_wallet/widgets/select-medical_file.dart @@ -78,9 +78,10 @@ class _MultiPageBottomSheetState extends State { return Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ - "Enter File Number".toText20(weight: FontWeight.w600), + LocaleKeys.pleaseEnterAFileNumber.tr(context: context).toText20(isBold: true), SizedBox(height: 12.h), TextInputWidget( + fontFamily: "Poppins", labelText: LocaleKeys.fileNumber.tr(), hintText: "xxxxxxxxx", controller: fileNumberEditingController, @@ -139,11 +140,16 @@ class _MultiPageBottomSheetState extends State { Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ - LocaleKeys.myMedicalFile.tr(context: context).toText16(color: AppColors.textColor, weight: FontWeight.w500), - "${LocaleKeys.fileno.tr(context: context)}: ${appState.getAuthenticatedUser()!.patientId}".toText12(color: AppColors.greyTextColor, fontWeight: FontWeight.w500), + LocaleKeys.myMedicalFile.tr(context: context).toText16(color: AppColors.textColor, isBold: true), + Row( + children: [ + "${LocaleKeys.fileno.tr(context: context)}: ".toText12(color: AppColors.greyTextColor, isBold: true), + appState.getAuthenticatedUser()!.patientId.toString().toText12(color: AppColors.greyTextColor, isBold: true, isEnglishOnly: true), + ], + ), ], ), - Utils.buildSvgWithAssets(icon: AppAssets.forward_chevron_icon, iconColor: AppColors.textColor, width: 15.h, height: 15.h), + Transform.flip(flipX: appState.isArabic(), child: Utils.buildSvgWithAssets(icon: AppAssets.forward_chevron_icon, iconColor: AppColors.textColor, width: 15.h, height: 15.h)), ], ).paddingAll(16.h), ).onPress(() { @@ -166,11 +172,11 @@ class _MultiPageBottomSheetState extends State { Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ - LocaleKeys.familyTitle.tr(context: context).toText16(color: AppColors.textColor, weight: FontWeight.w500), - "Select a medical file from your family".toText14(color: AppColors.greyTextColor, weight: FontWeight.w500), + LocaleKeys.familyTitle.tr(context: context).toText16(color: AppColors.textColor, isBold: true), + LocaleKeys.selectMedicalFileFromFamily.tr(context: context).toText14(color: AppColors.greyTextColor, isBold: true), ], ), - Utils.buildSvgWithAssets(icon: AppAssets.forward_chevron_icon, iconColor: AppColors.textColor, width: 15.h, height: 15.h), + Transform.flip(flipX: appState.isArabic(), child: Utils.buildSvgWithAssets(icon: AppAssets.forward_chevron_icon, iconColor: AppColors.textColor, width: 15.h, height: 15.h)), ], ).paddingAll(16.h), ).onPress(() { @@ -204,11 +210,11 @@ class _MultiPageBottomSheetState extends State { Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ - LocaleKeys.otherAccount.tr(context: context).toText16(color: AppColors.textColor, weight: FontWeight.w500), - "Any active medical file from HMG".toText14(color: AppColors.greyTextColor, weight: FontWeight.w500), + LocaleKeys.otherAccount.tr(context: context).toText16(color: AppColors.textColor, isBold: true), + LocaleKeys.anyFileFromHMG.tr(context: context).toText14(color: AppColors.greyTextColor, isBold: true), ], ), - Utils.buildSvgWithAssets(icon: AppAssets.forward_chevron_icon, iconColor: AppColors.textColor, width: 15.h, height: 15.h), + Transform.flip(flipX: appState.isArabic(), child: Utils.buildSvgWithAssets(icon: AppAssets.forward_chevron_icon, iconColor: AppColors.textColor, width: 15.h, height: 15.h)), ], ).paddingAll(16.h), ).onPress(() { diff --git a/lib/presentation/habib_wallet/widgets/select_hospital_bottom_sheet.dart b/lib/presentation/habib_wallet/widgets/select_hospital_bottom_sheet.dart index da374a04..e93b9cef 100644 --- a/lib/presentation/habib_wallet/widgets/select_hospital_bottom_sheet.dart +++ b/lib/presentation/habib_wallet/widgets/select_hospital_bottom_sheet.dart @@ -1,85 +1,215 @@ 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/location_util.dart'; +import 'package:hmg_patient_app_new/core/utils/doctor_response_mapper.dart'; import 'package:hmg_patient_app_new/core/utils/size_utils.dart'; +import 'package:hmg_patient_app_new/core/utils/utils.dart'; import 'package:hmg_patient_app_new/extensions/string_extensions.dart'; import 'package:hmg_patient_app_new/extensions/widget_extensions.dart'; import 'package:hmg_patient_app_new/features/habib_wallet/habib_wallet_view_model.dart'; +import 'package:hmg_patient_app_new/features/my_appointments/models/resp_models/hospital_model.dart'; import 'package:hmg_patient_app_new/generated/locale_keys.g.dart'; import 'package:hmg_patient_app_new/presentation/habib_wallet/widgets/hospital_list_item.dart'; import 'package:hmg_patient_app_new/theme/colors.dart' show AppColors; +import 'package:hmg_patient_app_new/widgets/common_bottom_sheet.dart'; +import 'package:permission_handler/permission_handler.dart'; import 'package:provider/provider.dart'; -class SelectHospitalBottomSheet extends StatelessWidget { +class SelectHospitalBottomSheet extends StatefulWidget { + const SelectHospitalBottomSheet({super.key}); + + @override + State createState() => _SelectHospitalBottomSheetState(); +} + +class _SelectHospitalBottomSheetState extends State { late HabibWalletViewModel habibWalletVM; final TextEditingController searchText = TextEditingController(); + bool sortByLocation = false; + bool isLoading = false; + List displayList = []; - SelectHospitalBottomSheet({super.key}); + @override + void initState() { + super.initState(); + final appState = getIt.get(); + sortByLocation = (appState.userLat != 0.0) && (appState.userLong != 0.0); + WidgetsBinding.instance.addPostFrameCallback((_) { + _updateDisplayList(); + }); + } + + void _updateDisplayList() { + final appState = getIt.get(); + final hospitals = List.from(habibWalletVM.advancePaymentHospitals); + + if (sortByLocation && appState.userLat != 0.0 && appState.userLong != 0.0) { + hospitals.sort((a, b) { + final distA = (a.latitude != null && a.longitude != null) + ? DoctorMapper.calculateDistance(appState.userLat, appState.userLong, double.parse(a.latitude!), double.parse(a.longitude!)) + : double.infinity; + final distB = (b.latitude != null && b.longitude != null) + ? DoctorMapper.calculateDistance(appState.userLat, appState.userLong, double.parse(b.latitude!), double.parse(b.longitude!)) + : double.infinity; + return distA.compareTo(distB); + }); + } + + setState(() { + displayList = hospitals; + isLoading = false; + }); + } + + void _refreshHospitalListAfterApi() { + void listener() { + if (habibWalletVM.advancePaymentHospitals.isNotEmpty) { + habibWalletVM.removeListener(listener); + _updateDisplayList(); + } + } + habibWalletVM.addListener(listener); + habibWalletVM.getProjectsList(); + } + + void _handleSortByLocationToggle(bool value) { + if (value) { + final locationUtils = getIt.get(); + locationUtils.getLocation( + isShowConfirmDialog: true, + onSuccess: (latLng) { + setState(() { + sortByLocation = true; + isLoading = true; + }); + _refreshHospitalListAfterApi(); + }, + onFailure: () { + showCommonBottomSheetWithoutHeight( + title: LocaleKeys.notice.tr(context: context), + context, + child: Utils.getWarningWidget( + loadingText: LocaleKeys.giveLocationPermissionForNearestList.tr(context: context), + isShowActionButtons: true, + onCancelTap: () { + Navigator.of(context).pop(); + }, + onConfirmTap: () async { + Navigator.of(context).pop(); + openAppSettings(); + }), + callBackFunc: () {}, + isFullScreen: false, + isCloseButtonVisible: true, + ); + setState(() { + sortByLocation = false; + }); + }, + onLocationDeniedForever: () { + showCommonBottomSheetWithoutHeight( + title: LocaleKeys.notice.tr(context: context), + context, + child: Utils.getWarningWidget( + loadingText: LocaleKeys.giveLocationPermissionForNearestList.tr(context: context), + isShowActionButtons: true, + onCancelTap: () { + Navigator.of(context).pop(); + }, + onConfirmTap: () async { + Navigator.of(context).pop(); + openAppSettings(); + }), + callBackFunc: () {}, + isFullScreen: false, + isCloseButtonVisible: true, + ); + setState(() { + sortByLocation = false; + }); + }, + ); + } else { + final appState = getIt.get(); + appState.resetLocation(); + setState(() { + sortByLocation = false; + isLoading = true; + }); + _refreshHospitalListAfterApi(); + } + } @override Widget build(BuildContext context) { habibWalletVM = Provider.of(context, listen: false); + if (displayList.isEmpty && habibWalletVM.advancePaymentHospitals.isNotEmpty) { + displayList = List.from(habibWalletVM.advancePaymentHospitals); + } return Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ - // Text( - // LocaleKeys.selectHospital.tr(), - // style: TextStyle( - // fontSize: 21, - // fontWeight: FontWeight.w600, - // color: AppColors.blackColor, - // ), - // ), Text( LocaleKeys.selectHospitalForAdvancePayment.tr(context: context), style: TextStyle( fontSize: 16, - fontWeight: FontWeight.w500, + fontWeight: FontWeight.w600, color: AppColors.greyTextColor, ), ), SizedBox(height: 16.h), - // TextInputWidget( - // labelText: LocaleKeys.search.tr(), - // hintText: "Search Hospital".tr(), - // controller: searchText, - // onChange: (value) { - // // appointmentsViewModel.filterHospitalListByString(value, regionalViewModel.selectedRegionId , regionalViewModel.selectedFacilityType == - // // FacilitySelection.HMG.name); - // }, - // isEnable: true, - // prefix: null, - // autoFocus: false, - // isBorderAllowed: false, - // keyboardType: TextInputType.text, - // isAllowLeadingIcon: true, - // selectionType: SelectionTypeEnum.search, - // padding: EdgeInsets.symmetric( - // vertical: ResponsiveExtension(10).h, - // horizontal: ResponsiveExtension(15).h, - // ), - // ), - // SizedBox(height: 24.h), - // TypeSelectionWidget( - // hmcCount: "0", - // hmgCount: "0", - // ), - // SizedBox(height: 21.h), - SizedBox( + Padding( + padding: EdgeInsets.symmetric(horizontal: 4.w), + child: Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Row( + children: [ + Utils.buildSvgWithAssets(icon: AppAssets.location, iconColor: AppColors.greyTextColor, width: 18.h, height: 18.h), + SizedBox(width: 8.w), + Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + LocaleKeys.sortByLocation.tr(context: context).toText14(isBold: true), + LocaleKeys.sortByNearestLocation.tr(context: context).toText11(color: AppColors.textColorLight, isBold: true), + ], + ), + ], + ), + Switch( + value: sortByLocation, + onChanged: _handleSortByLocationToggle, + activeThumbColor: AppColors.successColor, + activeTrackColor: AppColors.successColor.withValues(alpha: 0.15), + ), + ], + ), + ), + SizedBox(height: 8.h), + isLoading + ? SizedBox( + height: MediaQuery.sizeOf(context).height * .4, + child: Center(child: Utils.getLoadingWidget()), + ) + : SizedBox( height: MediaQuery.sizeOf(context).height * .4, child: ListView.separated( itemBuilder: (_, index) { return HospitalListItemAdvancePayment( - hospitalModel: habibWalletVM.advancePaymentHospitals[index], - isLocationEnabled: false, + hospitalModel: displayList[index], + isLocationEnabled: sortByLocation, ).onPress(() { - habibWalletVM.setSelectedHospital(habibWalletVM.advancePaymentHospitals[index]); + habibWalletVM.setSelectedHospital(displayList[index]); Navigator.of(context).pop(); }); }, separatorBuilder: (_, __) => SizedBox( height: 16.h, ), - itemCount: habibWalletVM.advancePaymentHospitals.length), + itemCount: displayList.length), ) ], ); 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 8cccf031..3d8a60ee 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 @@ -72,7 +72,7 @@ class _HealthCalculatorDetailedPageState extends State { crossAxisAlignment: CrossAxisAlignment.start, mainAxisAlignment: MainAxisAlignment.center, children: [ - LocaleKeys.generalHealth.tr().toText16(weight: FontWeight.w600), - LocaleKeys.relatedToBMICalories.tr().toText12(fontWeight: FontWeight.w500, color: Color(0xFF8F9AA3)) + LocaleKeys.generalHealth.tr().toText16(isBold: true), + LocaleKeys.relatedToBMICalories.tr().toText12(isBold: true, color: Color(0xFF8F9AA3)) ], ), ), @@ -83,8 +83,8 @@ class _HealthCalculatorsPageState extends State { crossAxisAlignment: CrossAxisAlignment.start, mainAxisAlignment: MainAxisAlignment.center, children: [ - LocaleKeys.womensHealth.tr().toText16(weight: FontWeight.w600), - LocaleKeys.relatedToPeriodsOvulation.tr().toText12(fontWeight: FontWeight.w500, color: Color(0xFF8F9AA3)) + LocaleKeys.womensHealth.tr().toText16(isBold: true), + LocaleKeys.relatedToPeriodsOvulation.tr().toText12(isBold: true, color: Color(0xFF8F9AA3)) ], ), ), @@ -118,9 +118,9 @@ class _HealthCalculatorsPageState extends State { crossAxisAlignment: CrossAxisAlignment.start, mainAxisAlignment: MainAxisAlignment.center, children: [ - LocaleKeys.bloodSugar.tr().toText16(weight: FontWeight.w600), + LocaleKeys.bloodSugar.tr().toText16(isBold: true), LocaleKeys.trackYourGlucoseLevels.tr().toText12( - fontWeight: FontWeight.w500, + isBold: true, color: Color(0xFF8F9AA3), ) ], @@ -152,9 +152,9 @@ class _HealthCalculatorsPageState extends State { crossAxisAlignment: CrossAxisAlignment.start, mainAxisAlignment: MainAxisAlignment.center, children: [ - LocaleKeys.bloodCholesterol.tr().toText16(weight: FontWeight.w600), + LocaleKeys.bloodCholesterol.tr().toText16(isBold: true), LocaleKeys.monitorCholesterolLevels.tr() - .toText12(fontWeight: FontWeight.w500, color: Color(0xFF8F9AA3)) + .toText12(isBold: true, color: Color(0xFF8F9AA3)) ], ), ), @@ -182,9 +182,9 @@ class _HealthCalculatorsPageState extends State { crossAxisAlignment: CrossAxisAlignment.start, mainAxisAlignment: MainAxisAlignment.center, children: [ - LocaleKeys.triglyceridesFatBlood.tr().toText16(weight: FontWeight.w600), + LocaleKeys.triglyceridesFatBlood.tr().toText16(isBold: true), LocaleKeys.understandTriglyceridesImpact.tr() - .toText12(fontWeight: FontWeight.w500, color: Color(0xFF8F9AA3)) + .toText12(isBold: true, color: Color(0xFF8F9AA3)) ], ), ), diff --git a/lib/presentation/health_calculators_and_converts/widgets/bf.dart b/lib/presentation/health_calculators_and_converts/widgets/bf.dart index 481bfcbf..d71566a2 100644 --- a/lib/presentation/health_calculators_and_converts/widgets/bf.dart +++ b/lib/presentation/health_calculators_and_converts/widgets/bf.dart @@ -94,8 +94,8 @@ class _BodyFatWidgetState extends State { crossAxisAlignment: CrossAxisAlignment.start, mainAxisAlignment: MainAxisAlignment.start, children: [ - LocaleKeys.selectGender.tr(context: context).toText12(fontWeight: FontWeight.w500, color: AppColors.inputLabelTextColor), - selectedGender.toText12(fontWeight: FontWeight.w500, color: AppColors.textColor), + LocaleKeys.selectGender.tr(context: context).toText12(isBold: true, color: AppColors.inputLabelTextColor), + selectedGender.toText12(isBold: true, color: AppColors.textColor), ], ), ], @@ -140,7 +140,7 @@ class _BodyFatWidgetState extends State { }, materialTapTargetSize: MaterialTapTargetSize.shrinkWrap, ), - unit.toCamelCase.toText16(weight: FontWeight.w500, textOverflow: TextOverflow.ellipsis, maxlines: 1).expanded, + unit.toCamelCase.toText16(isBold: true, textOverflow: TextOverflow.ellipsis, maxlines: 1).expanded, ], ).onPress(() { setState(() { @@ -173,7 +173,7 @@ class _BodyFatWidgetState extends State { crossAxisAlignment: CrossAxisAlignment.start, mainAxisAlignment: MainAxisAlignment.start, children: [ - "Height".toText12(fontWeight: FontWeight.w500, color: AppColors.inputLabelTextColor), + "Height".toText12(isBold: true, color: AppColors.inputLabelTextColor), Container( height: 20.w, alignment: Alignment.centerLeft, @@ -208,8 +208,8 @@ class _BodyFatWidgetState extends State { crossAxisAlignment: CrossAxisAlignment.start, mainAxisAlignment: MainAxisAlignment.start, children: [ - "Unit".toText12(fontWeight: FontWeight.w500, color: AppColors.inputLabelTextColor), - selectedHeightUnit.toText12(fontWeight: FontWeight.w500, color: AppColors.textColor), + "Unit".toText12(isBold: true, color: AppColors.inputLabelTextColor), + selectedHeightUnit.toText12(isBold: true, color: AppColors.textColor), ], ), SizedBox(width: 12.w), @@ -249,7 +249,7 @@ class _BodyFatWidgetState extends State { }, materialTapTargetSize: MaterialTapTargetSize.shrinkWrap, ), - unit.toUpperCase().toText16(weight: FontWeight.w500, textOverflow: TextOverflow.ellipsis, maxlines: 1).expanded, + unit.toUpperCase().toText16(isBold: true, textOverflow: TextOverflow.ellipsis, maxlines: 1).expanded, ], ).onPress(() { setState(() { @@ -285,7 +285,7 @@ class _BodyFatWidgetState extends State { crossAxisAlignment: CrossAxisAlignment.start, mainAxisAlignment: MainAxisAlignment.start, children: [ - "Neck".toText12(fontWeight: FontWeight.w500, color: AppColors.inputLabelTextColor), + "Neck".toText12(isBold: true, color: AppColors.inputLabelTextColor), Container( height: 20.w, alignment: Alignment.centerLeft, @@ -325,8 +325,8 @@ class _BodyFatWidgetState extends State { crossAxisAlignment: CrossAxisAlignment.start, mainAxisAlignment: MainAxisAlignment.start, children: [ - LocaleKeys.unit.tr(context: context).toText12(fontWeight: FontWeight.w500, color: AppColors.inputLabelTextColor), - selectedNeckUnit.toText12(fontWeight: FontWeight.w500, color: AppColors.textColor), + LocaleKeys.unit.tr(context: context).toText12(isBold: true, color: AppColors.inputLabelTextColor), + selectedNeckUnit.toText12(isBold: true, color: AppColors.textColor), ], ), SizedBox(width: 12.w), @@ -366,7 +366,7 @@ class _BodyFatWidgetState extends State { }, materialTapTargetSize: MaterialTapTargetSize.shrinkWrap, ), - unit.toUpperCase().toText16(weight: FontWeight.w500, textOverflow: TextOverflow.ellipsis, maxlines: 1).expanded, + unit.toUpperCase().toText16(isBold: true, textOverflow: TextOverflow.ellipsis, maxlines: 1).expanded, ], ).onPress(() { setState(() { @@ -402,7 +402,7 @@ class _BodyFatWidgetState extends State { crossAxisAlignment: CrossAxisAlignment.start, mainAxisAlignment: MainAxisAlignment.start, children: [ - "Waist".toText12(fontWeight: FontWeight.w500, color: AppColors.inputLabelTextColor), + "Waist".toText12(isBold: true, color: AppColors.inputLabelTextColor), Container( height: 20.w, alignment: Alignment.centerLeft, @@ -442,8 +442,8 @@ class _BodyFatWidgetState extends State { crossAxisAlignment: CrossAxisAlignment.start, mainAxisAlignment: MainAxisAlignment.start, children: [ - "Unit".toText12(fontWeight: FontWeight.w500, color: AppColors.inputLabelTextColor), - selectedWaistUnit.toText12(fontWeight: FontWeight.w500, color: AppColors.textColor), + "Unit".toText12(isBold: true, color: AppColors.inputLabelTextColor), + selectedWaistUnit.toText12(isBold: true, color: AppColors.textColor), ], ), SizedBox(width: 12.w), @@ -483,7 +483,7 @@ class _BodyFatWidgetState extends State { }, materialTapTargetSize: MaterialTapTargetSize.shrinkWrap, ), - unit.toUpperCase().toText16(weight: FontWeight.w500, textOverflow: TextOverflow.ellipsis, maxlines: 1).expanded, + unit.toUpperCase().toText16(isBold: true, textOverflow: TextOverflow.ellipsis, maxlines: 1).expanded, ], ).onPress(() { setState(() { @@ -519,7 +519,7 @@ class _BodyFatWidgetState extends State { crossAxisAlignment: CrossAxisAlignment.start, mainAxisAlignment: MainAxisAlignment.start, children: [ - "Hip".toText12(fontWeight: FontWeight.w500, color: AppColors.inputLabelTextColor), + "Hip".toText12(isBold: true, color: AppColors.inputLabelTextColor), Container( height: 20.w, alignment: Alignment.centerLeft, @@ -559,8 +559,8 @@ class _BodyFatWidgetState extends State { crossAxisAlignment: CrossAxisAlignment.start, mainAxisAlignment: MainAxisAlignment.start, children: [ - LocaleKeys.unit.tr(context: context).toText12(fontWeight: FontWeight.w500, color: AppColors.inputLabelTextColor), - selectedHipUnit.toText12(fontWeight: FontWeight.w500, color: AppColors.textColor), + LocaleKeys.unit.tr(context: context).toText12(isBold: true, color: AppColors.inputLabelTextColor), + selectedHipUnit.toText12(isBold: true, color: AppColors.textColor), ], ), SizedBox(width: 12.w), @@ -600,7 +600,7 @@ class _BodyFatWidgetState extends State { }, materialTapTargetSize: MaterialTapTargetSize.shrinkWrap, ), - unit.toUpperCase().toText16(weight: FontWeight.w500, textOverflow: TextOverflow.ellipsis, maxlines: 1).expanded, + unit.toUpperCase().toText16(isBold: true, textOverflow: TextOverflow.ellipsis, maxlines: 1).expanded, ], ).onPress(() { setState(() { @@ -631,7 +631,7 @@ class _BodyFatWidgetState extends State { Utils.buildSvgWithAssets(icon: AppAssets.globe, width: 18.w, height: 18.w), SizedBox(width: 12.w), Expanded( - child: "Estimate the total body fat based on the size.".toText12(fontWeight: FontWeight.w500, color: AppColors.inputLabelTextColor), + child: "Estimate the total body fat based on the size.".toText12(isBold: true, color: AppColors.inputLabelTextColor), ), ], ).paddingSymmetrical(0.w, 16.w), diff --git a/lib/presentation/health_calculators_and_converts/widgets/blood_cholesterol.dart b/lib/presentation/health_calculators_and_converts/widgets/blood_cholesterol.dart index e3bc7ff7..53bdf1bf 100644 --- a/lib/presentation/health_calculators_and_converts/widgets/blood_cholesterol.dart +++ b/lib/presentation/health_calculators_and_converts/widgets/blood_cholesterol.dart @@ -121,7 +121,7 @@ class _BloodCholesterolWidgetState extends State { Utils.buildSvgWithAssets(icon: AppAssets.globe, width: 18.w, height: 18.w), SizedBox(width: 12.w), Expanded( - child: "Convert blood cholesterol values between mg/dL and mmol/L. ".toText12(fontWeight: FontWeight.w500, color: AppColors.inputLabelTextColor), + child: "Convert blood cholesterol values between mg/dL and mmol/L. ".toText12(isBold: true, color: AppColors.inputLabelTextColor), ), ], ).paddingSymmetrical(0.w, 16.w), @@ -144,7 +144,7 @@ class _BloodCholesterolWidgetState extends State { crossAxisAlignment: CrossAxisAlignment.start, children: [ label.toText12( - fontWeight: FontWeight.w500, + isBold: true, color: AppColors.inputLabelTextColor, ), SizedBox( diff --git a/lib/presentation/health_calculators_and_converts/widgets/blood_sugar.dart b/lib/presentation/health_calculators_and_converts/widgets/blood_sugar.dart index cf7cc61b..f2110646 100644 --- a/lib/presentation/health_calculators_and_converts/widgets/blood_sugar.dart +++ b/lib/presentation/health_calculators_and_converts/widgets/blood_sugar.dart @@ -130,7 +130,7 @@ class _BloodSugarWidgetState extends State { SizedBox(width: 12.w), Expanded( child: "Convert blood glucose values between mg/dL and mmol/L. (1 mmol/L ≈ 18.0182 mg/dL)".toText12( - fontWeight: FontWeight.w500, + isBold: true, color: AppColors.inputLabelTextColor, ), ), @@ -155,7 +155,7 @@ class _BloodSugarWidgetState extends State { crossAxisAlignment: CrossAxisAlignment.start, children: [ label.toText12( - fontWeight: FontWeight.w500, + isBold: true, color: AppColors.inputLabelTextColor, ), SizedBox( diff --git a/lib/presentation/health_calculators_and_converts/widgets/bmi.dart b/lib/presentation/health_calculators_and_converts/widgets/bmi.dart index a5ea06d1..9e9d1bed 100644 --- a/lib/presentation/health_calculators_and_converts/widgets/bmi.dart +++ b/lib/presentation/health_calculators_and_converts/widgets/bmi.dart @@ -75,7 +75,7 @@ class _BMIWidgetState extends State { crossAxisAlignment: CrossAxisAlignment.start, mainAxisAlignment: MainAxisAlignment.start, children: [ - "Height".toText12(fontWeight: FontWeight.w500, color: AppColors.inputLabelTextColor), + "Height".toText12(isBold: true, color: AppColors.inputLabelTextColor), Container( height: 20.w, alignment: Alignment.centerLeft, @@ -110,8 +110,8 @@ class _BMIWidgetState extends State { crossAxisAlignment: CrossAxisAlignment.start, mainAxisAlignment: MainAxisAlignment.start, children: [ - "Unit".toText12(fontWeight: FontWeight.w500, color: AppColors.inputLabelTextColor), - selectedHeightUnit.toUpperCase().toText12(fontWeight: FontWeight.w600, color: AppColors.textColor), + "Unit".toText12(isBold: true, color: AppColors.inputLabelTextColor), + selectedHeightUnit.toUpperCase().toText12(isBold: true, color: AppColors.textColor), ], ), SizedBox(width: 12.w), @@ -152,7 +152,7 @@ class _BMIWidgetState extends State { }, materialTapTargetSize: MaterialTapTargetSize.shrinkWrap, ), - unit.toUpperCase().toText16(weight: FontWeight.w500, textOverflow: TextOverflow.ellipsis, maxlines: 1).expanded, + unit.toUpperCase().toText16(isBold: true, textOverflow: TextOverflow.ellipsis, maxlines: 1).expanded, ], ).onPress(() { setState(() { @@ -187,7 +187,7 @@ class _BMIWidgetState extends State { crossAxisAlignment: CrossAxisAlignment.start, mainAxisAlignment: MainAxisAlignment.start, children: [ - "Weight".toText12(fontWeight: FontWeight.w500, color: AppColors.inputLabelTextColor), + "Weight".toText12(isBold: true, color: AppColors.inputLabelTextColor), Container( height: 20.w, alignment: Alignment.centerLeft, @@ -227,8 +227,8 @@ class _BMIWidgetState extends State { crossAxisAlignment: CrossAxisAlignment.start, mainAxisAlignment: MainAxisAlignment.start, children: [ - "Unit".toText12(fontWeight: FontWeight.w500, color: AppColors.inputLabelTextColor), - selectedWeightUnit.toUpperCase().toText12(fontWeight: FontWeight.w600, color: AppColors.textColor), + "Unit".toText12(isBold: true, color: AppColors.inputLabelTextColor), + selectedWeightUnit.toUpperCase().toText12(isBold: true, color: AppColors.textColor), ], ), SizedBox(width: 12.w), @@ -268,7 +268,7 @@ class _BMIWidgetState extends State { }, materialTapTargetSize: MaterialTapTargetSize.shrinkWrap, ), - unit.toUpperCase().toText16(weight: FontWeight.w500, textOverflow: TextOverflow.ellipsis, maxlines: 1).expanded, + unit.toUpperCase().toText16(isBold: true, textOverflow: TextOverflow.ellipsis, maxlines: 1).expanded, ], ).onPress(() { setState(() { @@ -300,7 +300,7 @@ class _BMIWidgetState extends State { SizedBox(width: 12.w), Expanded( child: "Calculate the BMI value and weight status to identify the healthy weight. NOt appropriate for children and women who are pregnant or breastfeeding." - .toText12(fontWeight: FontWeight.w500, color: AppColors.inputLabelTextColor), + .toText12(isBold: true, color: AppColors.inputLabelTextColor), ), ], ).paddingSymmetrical(0.w, 16.w), diff --git a/lib/presentation/health_calculators_and_converts/widgets/bmr.dart b/lib/presentation/health_calculators_and_converts/widgets/bmr.dart index d1cb2c3a..21a5df61 100644 --- a/lib/presentation/health_calculators_and_converts/widgets/bmr.dart +++ b/lib/presentation/health_calculators_and_converts/widgets/bmr.dart @@ -89,8 +89,8 @@ class _BMRWidgetState extends State { crossAxisAlignment: CrossAxisAlignment.start, mainAxisAlignment: MainAxisAlignment.start, children: [ - LocaleKeys.selectGender.tr(context: context).toText12(fontWeight: FontWeight.w500, color: AppColors.inputLabelTextColor), - selectedGender.toCamelCase.toText12(fontWeight: FontWeight.w500, color: AppColors.textColor), + LocaleKeys.selectGender.tr(context: context).toText12(isBold: true, color: AppColors.inputLabelTextColor), + selectedGender.toCamelCase.toText12(isBold: true, color: AppColors.textColor), ], ), ], @@ -137,7 +137,7 @@ class _BMRWidgetState extends State { }, materialTapTargetSize: MaterialTapTargetSize.shrinkWrap, ), - unit.toCamelCase.toText16(weight: FontWeight.w500, textOverflow: TextOverflow.ellipsis, maxlines: 1).expanded, + unit.toCamelCase.toText16(isBold: true, textOverflow: TextOverflow.ellipsis, maxlines: 1).expanded, ], ).onPress(() { setState(() { @@ -170,7 +170,7 @@ class _BMRWidgetState extends State { crossAxisAlignment: CrossAxisAlignment.start, mainAxisAlignment: MainAxisAlignment.start, children: [ - "Age (11-120) yrs".toText12(fontWeight: FontWeight.w500, color: AppColors.inputLabelTextColor), + "Age (11-120) yrs".toText12(isBold: true, color: AppColors.inputLabelTextColor), Container( height: 20.w, alignment: Alignment.centerLeft, @@ -207,7 +207,7 @@ class _BMRWidgetState extends State { crossAxisAlignment: CrossAxisAlignment.start, mainAxisAlignment: MainAxisAlignment.start, children: [ - "Height".toText12(fontWeight: FontWeight.w500, color: AppColors.inputLabelTextColor), + "Height".toText12(isBold: true, color: AppColors.inputLabelTextColor), Container( height: 20.w, alignment: Alignment.centerLeft, @@ -242,8 +242,8 @@ class _BMRWidgetState extends State { crossAxisAlignment: CrossAxisAlignment.start, mainAxisAlignment: MainAxisAlignment.start, children: [ - LocaleKeys.unit.tr(context: context).toText12(fontWeight: FontWeight.w500, color: AppColors.inputLabelTextColor), - selectedHeightUnit.toText12(fontWeight: FontWeight.w500, color: AppColors.textColor), + LocaleKeys.unit.tr(context: context).toText12(isBold: true, color: AppColors.inputLabelTextColor), + selectedHeightUnit.toText12(isBold: true, color: AppColors.textColor), ], ), SizedBox(width: 12.w), @@ -284,7 +284,7 @@ class _BMRWidgetState extends State { }, materialTapTargetSize: MaterialTapTargetSize.shrinkWrap, ), - unit.toUpperCase().toText16(weight: FontWeight.w500, textOverflow: TextOverflow.ellipsis, maxlines: 1).expanded, + unit.toUpperCase().toText16(isBold: true, textOverflow: TextOverflow.ellipsis, maxlines: 1).expanded, ], ).onPress(() { setState(() { @@ -320,7 +320,7 @@ class _BMRWidgetState extends State { crossAxisAlignment: CrossAxisAlignment.start, mainAxisAlignment: MainAxisAlignment.start, children: [ - "Weight".toText12(fontWeight: FontWeight.w500, color: AppColors.inputLabelTextColor), + "Weight".toText12(isBold: true, color: AppColors.inputLabelTextColor), Container( height: 20.w, alignment: Alignment.centerLeft, @@ -360,8 +360,8 @@ class _BMRWidgetState extends State { crossAxisAlignment: CrossAxisAlignment.start, mainAxisAlignment: MainAxisAlignment.start, children: [ - "Unit".toText12(fontWeight: FontWeight.w500, color: AppColors.inputLabelTextColor), - selectedWeightUnit.toText12(fontWeight: FontWeight.w500, color: AppColors.textColor), + "Unit".toText12(isBold: true, color: AppColors.inputLabelTextColor), + selectedWeightUnit.toText12(isBold: true, color: AppColors.textColor), ], ), SizedBox(width: 12.w), @@ -402,7 +402,7 @@ class _BMRWidgetState extends State { }, materialTapTargetSize: MaterialTapTargetSize.shrinkWrap, ), - unit.toUpperCase().toText16(weight: FontWeight.w500, textOverflow: TextOverflow.ellipsis, maxlines: 1).expanded, + unit.toUpperCase().toText16(isBold: true, textOverflow: TextOverflow.ellipsis, maxlines: 1).expanded, ], ).onPress(() { setState(() { @@ -439,8 +439,8 @@ class _BMRWidgetState extends State { crossAxisAlignment: CrossAxisAlignment.start, mainAxisAlignment: MainAxisAlignment.start, children: [ - LocaleKeys.activityLevel.tr(context: context).toText12(fontWeight: FontWeight.w500, color: AppColors.inputLabelTextColor), - selectedActivityLevel.toText12(fontWeight: FontWeight.w500, color: AppColors.textColor), + LocaleKeys.activityLevel.tr(context: context).toText12(isBold: true, color: AppColors.inputLabelTextColor), + selectedActivityLevel.toText12(isBold: true, color: AppColors.textColor), ], ), ], @@ -485,7 +485,7 @@ class _BMRWidgetState extends State { }, materialTapTargetSize: MaterialTapTargetSize.shrinkWrap, ), - unit.toCamelCase.toText16(weight: FontWeight.w500, textOverflow: TextOverflow.ellipsis, maxlines: 1).expanded, + unit.toCamelCase.toText16(isBold: true, textOverflow: TextOverflow.ellipsis, maxlines: 1).expanded, ], ).onPress(() { setState(() { @@ -514,7 +514,7 @@ class _BMRWidgetState extends State { SizedBox(width: 12.w), Expanded( child: "Calculates daily calorie intake based on server factors, like height, weight, age, gender and daily physical activity." - .toText12(fontWeight: FontWeight.w500, color: AppColors.inputLabelTextColor), + .toText12(isBold: true, color: AppColors.inputLabelTextColor), ), ], ).paddingSymmetrical(0.w, 16.w), diff --git a/lib/presentation/health_calculators_and_converts/widgets/calories.dart b/lib/presentation/health_calculators_and_converts/widgets/calories.dart index 1609c056..8e584e96 100644 --- a/lib/presentation/health_calculators_and_converts/widgets/calories.dart +++ b/lib/presentation/health_calculators_and_converts/widgets/calories.dart @@ -90,8 +90,8 @@ class _CaloriesWidgetState extends State { crossAxisAlignment: CrossAxisAlignment.start, mainAxisAlignment: MainAxisAlignment.start, children: [ - "Select Gender".toText12(fontWeight: FontWeight.w500, color: AppColors.inputLabelTextColor), - selectedGender.toCamelCase.toText12(fontWeight: FontWeight.w500, color: AppColors.textColor), + "Select Gender".toText12(isBold: true, color: AppColors.inputLabelTextColor), + selectedGender.toCamelCase.toText12(isBold: true, color: AppColors.textColor), ], ), ], @@ -137,7 +137,7 @@ class _CaloriesWidgetState extends State { }, materialTapTargetSize: MaterialTapTargetSize.shrinkWrap, ), - unit.toCamelCase.toText16(weight: FontWeight.w500, textOverflow: TextOverflow.ellipsis, maxlines: 1).expanded, + unit.toCamelCase.toText16(isBold: true, textOverflow: TextOverflow.ellipsis, maxlines: 1).expanded, ], ).onPress(() { setState(() { @@ -170,7 +170,7 @@ class _CaloriesWidgetState extends State { crossAxisAlignment: CrossAxisAlignment.start, mainAxisAlignment: MainAxisAlignment.start, children: [ - "Age (11-120) yrs".toText12(fontWeight: FontWeight.w500, color: AppColors.inputLabelTextColor), + "Age (11-120) yrs".toText12(isBold: true, color: AppColors.inputLabelTextColor), Container( height: 20.w, alignment: Alignment.centerLeft, @@ -207,7 +207,7 @@ class _CaloriesWidgetState extends State { crossAxisAlignment: CrossAxisAlignment.start, mainAxisAlignment: MainAxisAlignment.start, children: [ - "Height".toText12(fontWeight: FontWeight.w500, color: AppColors.inputLabelTextColor), + "Height".toText12(isBold: true, color: AppColors.inputLabelTextColor), Container( height: 20.w, alignment: Alignment.centerLeft, @@ -242,8 +242,8 @@ class _CaloriesWidgetState extends State { crossAxisAlignment: CrossAxisAlignment.start, mainAxisAlignment: MainAxisAlignment.start, children: [ - "Unit".toText12(fontWeight: FontWeight.w500, color: AppColors.inputLabelTextColor), - selectedHeightUnit.toText12(fontWeight: FontWeight.w500, color: AppColors.textColor), + "Unit".toText12(isBold: true, color: AppColors.inputLabelTextColor), + selectedHeightUnit.toText12(isBold: true, color: AppColors.textColor), ], ), SizedBox(width: 12.w), @@ -283,7 +283,7 @@ class _CaloriesWidgetState extends State { }, materialTapTargetSize: MaterialTapTargetSize.shrinkWrap, ), - unit.toUpperCase().toText16(weight: FontWeight.w500, textOverflow: TextOverflow.ellipsis, maxlines: 1).expanded, + unit.toUpperCase().toText16(isBold: true, textOverflow: TextOverflow.ellipsis, maxlines: 1).expanded, ], ).onPress(() { setState(() { @@ -319,7 +319,7 @@ class _CaloriesWidgetState extends State { crossAxisAlignment: CrossAxisAlignment.start, mainAxisAlignment: MainAxisAlignment.start, children: [ - "Weight".toText12(fontWeight: FontWeight.w500, color: AppColors.inputLabelTextColor), + "Weight".toText12(isBold: true, color: AppColors.inputLabelTextColor), Container( height: 20.w, alignment: Alignment.centerLeft, @@ -359,8 +359,8 @@ class _CaloriesWidgetState extends State { crossAxisAlignment: CrossAxisAlignment.start, mainAxisAlignment: MainAxisAlignment.start, children: [ - "Unit".toText12(fontWeight: FontWeight.w500, color: AppColors.inputLabelTextColor), - selectedWeightUnit.toText12(fontWeight: FontWeight.w500, color: AppColors.textColor), + "Unit".toText12(isBold: true, color: AppColors.inputLabelTextColor), + selectedWeightUnit.toText12(isBold: true, color: AppColors.textColor), ], ), SizedBox(width: 12.w), @@ -401,7 +401,7 @@ class _CaloriesWidgetState extends State { }, materialTapTargetSize: MaterialTapTargetSize.shrinkWrap, ), - unit.toUpperCase().toText16(weight: FontWeight.w500, textOverflow: TextOverflow.ellipsis, maxlines: 1).expanded, + unit.toUpperCase().toText16(isBold: true, textOverflow: TextOverflow.ellipsis, maxlines: 1).expanded, ], ).onPress(() { setState(() { @@ -438,8 +438,8 @@ class _CaloriesWidgetState extends State { crossAxisAlignment: CrossAxisAlignment.start, mainAxisAlignment: MainAxisAlignment.start, children: [ - LocaleKeys.activityLevel.tr(context: context).toText12(fontWeight: FontWeight.w500, color: AppColors.inputLabelTextColor), - selectedActivityLevel.toText12(fontWeight: FontWeight.w500, color: AppColors.textColor), + LocaleKeys.activityLevel.tr(context: context).toText12(isBold: true, color: AppColors.inputLabelTextColor), + selectedActivityLevel.toText12(isBold: true, color: AppColors.textColor), ], ), ], @@ -484,7 +484,7 @@ class _CaloriesWidgetState extends State { }, materialTapTargetSize: MaterialTapTargetSize.shrinkWrap, ), - unit.toCamelCase.toText16(weight: FontWeight.w500, textOverflow: TextOverflow.ellipsis, maxlines: 1).expanded, + unit.toCamelCase.toText16(isBold: true, textOverflow: TextOverflow.ellipsis, maxlines: 1).expanded, ], ).onPress(() { setState(() { @@ -513,7 +513,7 @@ class _CaloriesWidgetState extends State { SizedBox(width: 12.w), Expanded( child: "Calculates daily calorie intake based on server factors, like height, weight, age, gender and daily physical activity." - .toText12(fontWeight: FontWeight.w500, color: AppColors.inputLabelTextColor), + .toText12(isBold: true, color: AppColors.inputLabelTextColor), ), ], ).paddingSymmetrical(0.w, 16.w), diff --git a/lib/presentation/health_calculators_and_converts/widgets/crabs.dart b/lib/presentation/health_calculators_and_converts/widgets/crabs.dart index c241e688..e3e7c1e3 100644 --- a/lib/presentation/health_calculators_and_converts/widgets/crabs.dart +++ b/lib/presentation/health_calculators_and_converts/widgets/crabs.dart @@ -70,7 +70,7 @@ class _CrabsWidgetState extends State { crossAxisAlignment: CrossAxisAlignment.start, mainAxisAlignment: MainAxisAlignment.start, children: [ - LocaleKeys.caloriesPerDay.tr(context: context).toText12(fontWeight: FontWeight.w500, color: AppColors.inputLabelTextColor), + LocaleKeys.caloriesPerDay.tr(context: context).toText12(isBold: true, color: AppColors.inputLabelTextColor), Container( height: 20.w, alignment: Alignment.centerLeft, @@ -113,8 +113,8 @@ class _CrabsWidgetState extends State { crossAxisAlignment: CrossAxisAlignment.start, mainAxisAlignment: MainAxisAlignment.start, children: [ - LocaleKeys.dietType.tr(context: context).toText12(fontWeight: FontWeight.w500, color: AppColors.inputLabelTextColor), - selectedDietType.toText12(fontWeight: FontWeight.w500, color: AppColors.textColor), + LocaleKeys.dietType.tr(context: context).toText12(isBold: true, color: AppColors.inputLabelTextColor), + selectedDietType.toText12(isBold: true, color: AppColors.textColor), ], ), ], @@ -159,7 +159,7 @@ class _CrabsWidgetState extends State { }, materialTapTargetSize: MaterialTapTargetSize.shrinkWrap, ), - unit.toCamelCase.toText16(weight: FontWeight.w500, textOverflow: TextOverflow.ellipsis, maxlines: 1).expanded, + unit.toCamelCase.toText16(isBold: true, textOverflow: TextOverflow.ellipsis, maxlines: 1).expanded, ], ).onPress(() { setState(() { @@ -188,7 +188,7 @@ class _CrabsWidgetState extends State { SizedBox(width: 12.w), Expanded( child: "Calculate carbohydrate based protein and fat ration in calories and grams according to a pre set ratio." - .toText12(fontWeight: FontWeight.w500, color: AppColors.inputLabelTextColor), + .toText12(isBold: true, color: AppColors.inputLabelTextColor), ), ], ).paddingSymmetrical(0.w, 16.w), diff --git a/lib/presentation/health_calculators_and_converts/widgets/health_card.dart b/lib/presentation/health_calculators_and_converts/widgets/health_card.dart index d54d0378..21482e7c 100644 --- a/lib/presentation/health_calculators_and_converts/widgets/health_card.dart +++ b/lib/presentation/health_calculators_and_converts/widgets/health_card.dart @@ -40,7 +40,7 @@ class HealthCard extends StatelessWidget { children: [ Flexible( child: labelText.toText12( - fontWeight: FontWeight.w600, + isBold: true, maxLine: 2, )), ], diff --git a/lib/presentation/health_calculators_and_converts/widgets/ibw.dart b/lib/presentation/health_calculators_and_converts/widgets/ibw.dart index a783d2c8..070e373e 100644 --- a/lib/presentation/health_calculators_and_converts/widgets/ibw.dart +++ b/lib/presentation/health_calculators_and_converts/widgets/ibw.dart @@ -81,7 +81,7 @@ class _IdealBodyWeightWidgetState extends State { crossAxisAlignment: CrossAxisAlignment.start, mainAxisAlignment: MainAxisAlignment.start, children: [ - "Height".toText12(fontWeight: FontWeight.w500, color: AppColors.inputLabelTextColor), + "Height".toText12(isBold: true, color: AppColors.inputLabelTextColor), Container( height: 20.w, alignment: Alignment.centerLeft, @@ -116,8 +116,8 @@ class _IdealBodyWeightWidgetState extends State { crossAxisAlignment: CrossAxisAlignment.start, mainAxisAlignment: MainAxisAlignment.start, children: [ - LocaleKeys.unit.tr(context: context).toText12(fontWeight: FontWeight.w500, color: AppColors.inputLabelTextColor), - selectedHeightUnit.toText12(fontWeight: FontWeight.w500, color: AppColors.textColor), + LocaleKeys.unit.tr(context: context).toText12(isBold: true, color: AppColors.inputLabelTextColor), + selectedHeightUnit.toText12(isBold: true, color: AppColors.textColor), ], ), SizedBox(width: 12.w), @@ -157,7 +157,7 @@ class _IdealBodyWeightWidgetState extends State { }, materialTapTargetSize: MaterialTapTargetSize.shrinkWrap, ), - unit.toUpperCase().toText16(weight: FontWeight.w500, textOverflow: TextOverflow.ellipsis, maxlines: 1).expanded, + unit.toUpperCase().toText16(isBold: true, textOverflow: TextOverflow.ellipsis, maxlines: 1).expanded, ], ).onPress(() { setState(() { @@ -193,7 +193,7 @@ class _IdealBodyWeightWidgetState extends State { crossAxisAlignment: CrossAxisAlignment.start, mainAxisAlignment: MainAxisAlignment.start, children: [ - "Weight".toText12(fontWeight: FontWeight.w500, color: AppColors.inputLabelTextColor), + "Weight".toText12(isBold: true, color: AppColors.inputLabelTextColor), Container( height: 20.w, alignment: Alignment.centerLeft, @@ -233,8 +233,8 @@ class _IdealBodyWeightWidgetState extends State { crossAxisAlignment: CrossAxisAlignment.start, mainAxisAlignment: MainAxisAlignment.start, children: [ - LocaleKeys.unit.tr(context: context).toText12(fontWeight: FontWeight.w500, color: AppColors.inputLabelTextColor), - selectedWeightUnit.toText12(fontWeight: FontWeight.w500, color: AppColors.textColor), + LocaleKeys.unit.tr(context: context).toText12(isBold: true, color: AppColors.inputLabelTextColor), + selectedWeightUnit.toText12(isBold: true, color: AppColors.textColor), ], ), SizedBox(width: 12.w), @@ -273,7 +273,7 @@ class _IdealBodyWeightWidgetState extends State { }, materialTapTargetSize: MaterialTapTargetSize.shrinkWrap, ), - unit.toUpperCase().toText16(weight: FontWeight.w500, textOverflow: TextOverflow.ellipsis, maxlines: 1).expanded, + unit.toUpperCase().toText16(isBold: true, textOverflow: TextOverflow.ellipsis, maxlines: 1).expanded, ], ).onPress(() { setState(() { @@ -310,8 +310,8 @@ class _IdealBodyWeightWidgetState extends State { crossAxisAlignment: CrossAxisAlignment.start, mainAxisAlignment: MainAxisAlignment.start, children: [ - LocaleKeys.bodyFrameSize.tr(context: context).toText12(fontWeight: FontWeight.w500, color: AppColors.inputLabelTextColor), - selectedBodyFrameSize.toText12(fontWeight: FontWeight.w500, color: AppColors.textColor), + LocaleKeys.bodyFrameSize.tr(context: context).toText12(isBold: true, color: AppColors.inputLabelTextColor), + selectedBodyFrameSize.toText12(isBold: true, color: AppColors.textColor), ], ), ], @@ -355,7 +355,7 @@ class _IdealBodyWeightWidgetState extends State { }, materialTapTargetSize: MaterialTapTargetSize.shrinkWrap, ), - unit.toCamelCase.toText16(weight: FontWeight.w500, textOverflow: TextOverflow.ellipsis, maxlines: 1).expanded, + unit.toCamelCase.toText16(isBold: true, textOverflow: TextOverflow.ellipsis, maxlines: 1).expanded, ], ).onPress(() { setState(() { @@ -383,7 +383,7 @@ class _IdealBodyWeightWidgetState extends State { Utils.buildSvgWithAssets(icon: AppAssets.globe, width: 18.w, height: 18.w), SizedBox(width: 12.w), Expanded( - child: "Calculates the ideal body weight based on height, weight and body size.".toText12(fontWeight: FontWeight.w500, color: AppColors.inputLabelTextColor), + child: "Calculates the ideal body weight based on height, weight and body size.".toText12(isBold: true, color: AppColors.inputLabelTextColor), ), ], ).paddingSymmetrical(0.w, 16.w), diff --git a/lib/presentation/health_calculators_and_converts/widgets/ovulation.dart b/lib/presentation/health_calculators_and_converts/widgets/ovulation.dart index 556880d4..3cfd72f8 100644 --- a/lib/presentation/health_calculators_and_converts/widgets/ovulation.dart +++ b/lib/presentation/health_calculators_and_converts/widgets/ovulation.dart @@ -97,7 +97,7 @@ class _OvulationWidgetState extends State { crossAxisAlignment: CrossAxisAlignment.start, mainAxisAlignment: MainAxisAlignment.start, children: [ - LocaleKeys.averageCycleLength.tr(context: context).toText12(fontWeight: FontWeight.w500, color: AppColors.inputLabelTextColor), + LocaleKeys.averageCycleLength.tr(context: context).toText12(isBold: true, color: AppColors.inputLabelTextColor), Container( height: 20.w, alignment: Alignment.centerLeft, @@ -134,7 +134,7 @@ class _OvulationWidgetState extends State { crossAxisAlignment: CrossAxisAlignment.start, mainAxisAlignment: MainAxisAlignment.start, children: [ - LocaleKeys.averageLutealPhase.tr(context: context).toText12(fontWeight: FontWeight.w500, color: AppColors.inputLabelTextColor), + LocaleKeys.averageLutealPhase.tr(context: context).toText12(isBold: true, color: AppColors.inputLabelTextColor), Container( height: 20.w, alignment: Alignment.centerLeft, @@ -165,7 +165,7 @@ class _OvulationWidgetState extends State { Utils.buildSvgWithAssets(icon: AppAssets.globe, width: 18.w, height: 18.w), SizedBox(width: 12.w), Expanded( - child: "Calculate ovulation and fertile window based on last period, cycle length and luteal phase.".toText12(fontWeight: FontWeight.w500, color: AppColors.inputLabelTextColor), + child: "Calculate ovulation and fertile window based on last period, cycle length and luteal phase.".toText12(isBold: true, color: AppColors.inputLabelTextColor), ), ], ).paddingSymmetrical(0.w, 16.w), diff --git a/lib/presentation/health_calculators_and_converts/widgets/triglycerides.dart b/lib/presentation/health_calculators_and_converts/widgets/triglycerides.dart index 1c8c99ed..b61eaaed 100644 --- a/lib/presentation/health_calculators_and_converts/widgets/triglycerides.dart +++ b/lib/presentation/health_calculators_and_converts/widgets/triglycerides.dart @@ -136,7 +136,7 @@ class _TriglyceridesWidgetState extends State { child: "Convert triglyceride values between mg/dL and mmol/L." .toText12( - fontWeight: FontWeight.w500, + isBold: true, color: AppColors.inputLabelTextColor, ), ), @@ -160,7 +160,7 @@ class _TriglyceridesWidgetState extends State { crossAxisAlignment: CrossAxisAlignment.start, children: [ label.toText12( - fontWeight: FontWeight.w500, + isBold: true, color: AppColors.inputLabelTextColor, ), SizedBox( 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 8320972e..2d0356c3 100644 --- a/lib/presentation/health_trackers/add_health_tracker_entry_page.dart +++ b/lib/presentation/health_trackers/add_health_tracker_entry_page.dart @@ -159,14 +159,12 @@ class _AddHealthTrackerEntryPageState extends State { context, child: Utils.getSuccessWidget( loadingText: _getSuccessMessage(), - ), - callBackFunc: () { - Navigator.pop(context); - }, + ).paddingSymmetrical(0.h, 24.h), callBackFunc: () { + // Navigator.pop(context); + }, isCloseButtonVisible: false, isDismissible: true, - isFullScreen: false, - ); + isFullScreen: false, isAutoDismiss: true); } // Reusable method to build selection row widget @@ -189,7 +187,7 @@ class _AddHealthTrackerEntryPageState extends State { materialTapTargetSize: MaterialTapTargetSize.shrinkWrap, ), (useUpperCase ? value.toUpperCase() : value.toCamelCase) - .toText16(weight: FontWeight.w500, textOverflow: TextOverflow.ellipsis, maxlines: 1) + .toText16(isBold: true, textOverflow: TextOverflow.ellipsis, maxlines: 1) .expanded, ], ).onPress(onTap), @@ -303,7 +301,7 @@ class _AddHealthTrackerEntryPageState extends State { ), style: TextStyle( fontSize: 14.f, - fontWeight: FontWeight.w500, + fontWeight: FontWeight.w600, color: AppColors.textColor, ), ); @@ -343,7 +341,7 @@ class _AddHealthTrackerEntryPageState extends State { crossAxisAlignment: CrossAxisAlignment.start, mainAxisAlignment: MainAxisAlignment.start, children: [ - label.toText12(fontWeight: FontWeight.w500, color: AppColors.inputLabelTextColor), + label.toText12(isBold: true, color: AppColors.inputLabelTextColor), if (inputField != null) Container( height: 20.w, @@ -351,7 +349,7 @@ class _AddHealthTrackerEntryPageState extends State { child: inputField, ) else if (value != null && value.isNotEmpty) - value.toCamelCase.toText12(fontWeight: FontWeight.w500, color: AppColors.textColor), + value.toCamelCase.toText12(isBold: true, color: AppColors.textColor), ], ), ), @@ -368,8 +366,8 @@ class _AddHealthTrackerEntryPageState extends State { crossAxisAlignment: CrossAxisAlignment.start, mainAxisAlignment: MainAxisAlignment.start, children: [ - "Unit".toText12(fontWeight: FontWeight.w500, color: AppColors.inputLabelTextColor), - unit.toText12(fontWeight: FontWeight.w500, color: AppColors.textColor), + "Unit".toText12(isBold: true, color: AppColors.inputLabelTextColor), + unit.toText12(isBold: true, color: AppColors.textColor), ], ), SizedBox(width: 12.w), diff --git a/lib/presentation/health_trackers/health_tracker_detail_page.dart b/lib/presentation/health_trackers/health_tracker_detail_page.dart index b5f41dae..737a150f 100644 --- a/lib/presentation/health_trackers/health_tracker_detail_page.dart +++ b/lib/presentation/health_trackers/health_tracker_detail_page.dart @@ -137,7 +137,7 @@ class _HealthTrackerDetailPageState extends State { materialTapTargetSize: MaterialTapTargetSize.shrinkWrap, ), (useUpperCase ? value.toUpperCase() : value.toCamelCase) - .toText16(weight: FontWeight.w500, textOverflow: TextOverflow.ellipsis, maxlines: 1) + .toText16(isBold: true, textOverflow: TextOverflow.ellipsis, maxlines: 1) .expanded, ], ).onPress(onTap), @@ -254,7 +254,7 @@ class _HealthTrackerDetailPageState extends State { ], ), SizedBox(height: 4.h), - subTitle.toText16(weight: FontWeight.w600, color: AppColors.textColor), + subTitle.toText16(isBold: true, color: AppColors.textColor), ], ), Transform.rotate( @@ -303,7 +303,7 @@ class _HealthTrackerDetailPageState extends State { child: Row( crossAxisAlignment: CrossAxisAlignment.end, children: [ - viewModel.selectedDurationFilter.toText12(fontWeight: FontWeight.w500), + viewModel.selectedDurationFilter.toText12(isBold: true), SizedBox(width: 4.w), Utils.buildSvgWithAssets(icon: AppAssets.arrow_down, height: 16.h), ], @@ -587,7 +587,7 @@ class _HealthTrackerDetailPageState extends State { ), SizedBox(height: 16.h), "You do not have any data available.".toText14( - weight: FontWeight.w500, + weight: FontWeight.w600, color: AppColors.textColor, isCenter: true, ), @@ -702,7 +702,7 @@ class _HealthTrackerDetailPageState extends State { final positions = [0.0, interval, interval * 2, interval * 3, maxY]; for (var position in positions) { if ((value - position).abs() < 1) { - return '${value.toInt()}'.toText10(weight: FontWeight.w600); + return '${value.toInt()}'.toText10(isBold: true); } } return SizedBox.shrink(); @@ -716,19 +716,19 @@ class _HealthTrackerDetailPageState extends State { if (selectedDuration == 'Week' && index < 7) { return Padding( padding: EdgeInsets.only(top: 10.h), - child: data[index].label.toText10(weight: FontWeight.w600, color: AppColors.labelTextColor), + child: data[index].label.toText10(isBold: true, color: AppColors.labelTextColor), ); } if (selectedDuration == 'Month' && index < 6) { return Padding( padding: EdgeInsets.only(top: 10.h), - child: data[index].label.toText10(weight: FontWeight.w600, color: AppColors.labelTextColor), + child: data[index].label.toText10(isBold: true, color: AppColors.labelTextColor), ); } if (selectedDuration == 'Year' && index < 12) { return Padding( padding: EdgeInsets.only(top: 10.h), - child: data[index].label.toText8(fontWeight: FontWeight.w600, color: AppColors.labelTextColor), + child: data[index].label.toText8(isBold: true, color: AppColors.labelTextColor), ); } return SizedBox.shrink(); @@ -757,7 +757,7 @@ class _HealthTrackerDetailPageState extends State { ), ), SizedBox(width: 6.w), - label.toText12(fontWeight: FontWeight.w500, color: AppColors.textColor), + label.toText12(isBold: true, color: AppColors.textColor), ], ); } diff --git a/lib/presentation/health_trackers/health_trackers_page.dart b/lib/presentation/health_trackers/health_trackers_page.dart index ae57f7db..e974773c 100644 --- a/lib/presentation/health_trackers/health_trackers_page.dart +++ b/lib/presentation/health_trackers/health_trackers_page.dart @@ -49,9 +49,9 @@ Widget buildHealthTrackerCard({ crossAxisAlignment: CrossAxisAlignment.start, mainAxisAlignment: MainAxisAlignment.center, children: [ - title.toText16(weight: FontWeight.w600), + title.toText16(isBold: true), description.toText12( - fontWeight: FontWeight.w500, + isBold: true, color: Color(0xFF8F9AA3), ), ], 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 39465b0f..e12f16c2 100644 --- a/lib/presentation/health_trackers/widgets/tracker_last_value_card.dart +++ b/lib/presentation/health_trackers/widgets/tracker_last_value_card.dart @@ -225,7 +225,7 @@ class TrackerLastValueCard extends StatelessWidget { children: [ "--".toText32(isBold: true, color: AppColors.greyTextColor), SizedBox(width: 6.w), - unit.toText12(color: AppColors.greyTextColor, fontWeight: FontWeight.w500).paddingOnly(top: 8.h), + unit.toText12(color: AppColors.greyTextColor, isBold: true).paddingOnly(top: 8.h), ], ), SizedBox(height: 8.h), @@ -253,7 +253,7 @@ class TrackerLastValueCard extends StatelessWidget { children: [ displayValue.toText32(isBold: true, color: statusColor), SizedBox(width: 6.w), - unit.toText12(color: AppColors.greyTextColor, fontWeight: FontWeight.w500).paddingOnly(top: 8.h), + unit.toText12(color: AppColors.greyTextColor, isBold: true).paddingOnly(top: 8.h), ], ), SizedBox(height: 8.h), diff --git a/lib/presentation/hmg_services/services_page.dart b/lib/presentation/hmg_services/services_page.dart index d2f42f52..770a5cbe 100644 --- a/lib/presentation/hmg_services/services_page.dart +++ b/lib/presentation/hmg_services/services_page.dart @@ -391,7 +391,7 @@ class ServicesPage extends StatelessWidget { crossAxisAlignment: CrossAxisAlignment.start, children: [ SizedBox(height: 16.h), - LocaleKeys.medicalAndCareServices.tr().toText18(weight: FontWeight.w600).paddingSymmetrical(24.w, 0), + LocaleKeys.medicalAndCareServices.tr().toText18(isBold: true).paddingSymmetrical(24.w, 0), SizedBox(height: 16.h), GridView.builder( gridDelegate: SliverGridDelegateWithFixedCrossAxisCount( @@ -408,7 +408,7 @@ class ServicesPage extends StatelessWidget { }, ).paddingSymmetrical(24.w, 0), SizedBox(height: 24.h), - LocaleKeys.hmgServices.tr().toText18(weight: FontWeight.w600).paddingSymmetrical(24.w, 0), + LocaleKeys.hmgServices.tr().toText18(isBold: true).paddingSymmetrical(24.w, 0), SizedBox(height: 16.h), SizedBox( height: 350.h, @@ -430,6 +430,7 @@ class ServicesPage extends StatelessWidget { title: LandingPageData.getServiceCardsList[index].title, subtitle: LandingPageData.getServiceCardsList[index].subtitle, icon: LandingPageData.getServiceCardsList[index].largeCardIcon, + isPNG: LandingPageData.getServiceCardsList[index].isPNG, ), ), ), @@ -442,7 +443,7 @@ class ServicesPage extends StatelessWidget { getIt.get().isAuthenticated ? Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ - LocaleKeys.personalServices.tr().toText18(weight: FontWeight.w600).paddingSymmetrical(24.w, 0), + LocaleKeys.personalServices.tr().toText18(isBold: true).paddingSymmetrical(24.w, 0), SizedBox(height: 16.h), Row( children: [ @@ -464,17 +465,17 @@ class ServicesPage extends StatelessWidget { crossAxisAlignment: CrossAxisAlignment.center, children: [ Utils.buildSvgWithAssets(icon: AppAssets.wallet, width: 40.w, height: 40.h, applyThemeColor: false), - LocaleKeys.habibWallet.tr().toText14(weight: FontWeight.w600, maxlines: 2).expanded, + LocaleKeys.habibWallet.tr().toText14(isBold: true, maxlines: 2).expanded, Utils.buildSvgWithAssets(icon: getIt.get().isArabic() ? AppAssets.arrow_back : AppAssets.arrow_forward), ], ), Spacer(), getIt.get().isAuthenticated ? Consumer(builder: (context, habibWalletVM, child) { - return Utils.getPaymentAmountWithSymbol2(num.parse(NumberFormat.decimalPattern().format(habibWalletVM.habibWalletAmount)), isExpanded: false) + return Utils.getPaymentAmountWithSymbol2(num.parse(NumberFormat.decimalPattern().format(habibWalletVM.habibWalletAmount)), isExpanded: false, letterSpacing: -1) .toShimmer2(isShow: habibWalletVM.isWalletAmountLoading, radius: 12.r, width: 80.w, height: 24.h); }) - : LocaleKeys.loginToViewWalletBalance.tr().toText12(fontWeight: FontWeight.w500, maxLine: 2), + : LocaleKeys.loginToViewWalletBalance.tr().toText12(isBold: true, maxLine: 2), Spacer(), getIt.get().isAuthenticated ? CustomButton( @@ -485,7 +486,7 @@ class ServicesPage extends StatelessWidget { textColor: AppColors.infoColor, text: LocaleKeys.recharge.tr(), borderWidth: 0.w, - fontWeight: FontWeight.w500, + isBold: true, borderColor: Colors.transparent, backgroundColor: Color(0xff45A2F8).withValues(alpha: 0.08), padding: EdgeInsets.all(8.w), @@ -524,7 +525,7 @@ class ServicesPage extends StatelessWidget { crossAxisAlignment: CrossAxisAlignment.center, children: [ Utils.buildSvgWithAssets(icon: AppAssets.services_medical_file_icon, width: 40.w, height: 40.h, applyThemeColor: false), - LocaleKeys.medicalFile.tr().toText16(weight: FontWeight.w600, maxlines: 2).expanded, + LocaleKeys.medicalFile.tr().toText16(isBold: true, maxlines: 2).expanded, Utils.buildSvgWithAssets(icon: getIt.get().isArabic() ? AppAssets.arrow_back : AppAssets.arrow_forward), ], ), @@ -560,7 +561,7 @@ class ServicesPage extends StatelessWidget { ), ], ) - : LocaleKeys.loginToViewMedicalFile.tr().toText12(fontWeight: FontWeight.w500, maxLine: 2), + : LocaleKeys.loginToViewMedicalFile.tr().toText12(isBold: true, maxLine: 2), Spacer(), getIt.get().isAuthenticated ? CustomButton( @@ -571,7 +572,7 @@ class ServicesPage extends StatelessWidget { textColor: AppColors.primaryRedColor, text: LocaleKeys.addMember.tr(), borderWidth: 0.w, - fontWeight: FontWeight.w500, + isBold: true, borderColor: Colors.transparent, backgroundColor: AppColors.primaryRedColor.withValues(alpha: 0.08), padding: EdgeInsets.all(8.w), @@ -607,7 +608,7 @@ class ServicesPage extends StatelessWidget { ], ) : SizedBox(), SizedBox(height: 24.h), - LocaleKeys.healthTools.tr().toText18(weight: FontWeight.w600).paddingSymmetrical(24.w, 0), + LocaleKeys.healthTools.tr().toText18(isBold: true).paddingSymmetrical(24.w, 0), SizedBox(height: 16.h), GridView.builder( gridDelegate: SliverGridDelegateWithFixedCrossAxisCount( @@ -630,78 +631,78 @@ class ServicesPage extends StatelessWidget { }, ).paddingSymmetrical(24.w, 0), SizedBox(height: 24.h), - LocaleKeys.supportServices.tr().toText18(weight: FontWeight.w600).paddingSymmetrical(24.w, 0), + LocaleKeys.supportServices.tr().toText18(isBold: true).paddingSymmetrical(24.w, 0), SizedBox(height: 16.h), Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ - Row( - children: [ - Expanded( - child: Container( - decoration: RoundedRectangleBorder().toSmoothCornerDecoration( - color: AppColors.whiteColor, - borderRadius: 12.h, - hasShadow: false, - ), - child: Padding( - padding: EdgeInsets.all(16.h), - child: Row( - children: [ - Utils.buildSvgWithAssets( - icon: AppAssets.virtual_tour_icon, - width: 32.w, - height: 32.h, - fit: BoxFit.contain, - ), - SizedBox(width: 8.w), - LocaleKeys.virtualTour.tr().toText14(weight: FontWeight.w500) - ], - ), - ), - ).onPress(() { - Utils.openWebView( - url: 'https://hmgwebservices.com/vt_mobile/html/index.html', - ); - }), - ), - SizedBox(width: 16.w), - Expanded( - child: Container( - decoration: RoundedRectangleBorder().toSmoothCornerDecoration( - color: AppColors.whiteColor, - borderRadius: 12.h, - hasShadow: false, - ), - child: Padding( - padding: EdgeInsets.all(16.h), - child: Row( - children: [ - Utils.buildSvgWithAssets( - icon: AppAssets.car_parking_icon, - width: 32.w, - height: 32.h, - fit: BoxFit.contain, - ), - SizedBox(width: 8.w), - LocaleKeys.carParking.tr().toText14(weight: FontWeight.w500) - ], - ).onPress(() { - Navigator.push( - context, - MaterialPageRoute( - builder: (_) => ChangeNotifierProvider( - create: (_) => getIt(), - child: const ParkingPage(), - ), - ), - ); - }), - ), - ), - ), - ], - ), + // Row( + // children: [ + // // Expanded( + // // child: Container( + // // decoration: RoundedRectangleBorder().toSmoothCornerDecoration( + // // color: AppColors.whiteColor, + // // borderRadius: 12.h, + // // hasShadow: false, + // // ), + // // child: Padding( + // // padding: EdgeInsets.all(16.h), + // // child: Row( + // // children: [ + // // Utils.buildSvgWithAssets( + // // icon: AppAssets.virtual_tour_icon, + // // width: 32.w, + // // height: 32.h, + // // fit: BoxFit.contain, + // // ), + // // SizedBox(width: 8.w), + // // LocaleKeys.virtualTour.tr().toText14(isBold: true) + // // ], + // // ), + // // ), + // // ).onPress(() { + // // Utils.openWebView( + // // url: 'https://hmgwebservices.com/vt_mobile/html/index.html', + // // ); + // // }), + // // ), + // SizedBox(width: 16.w), + // Expanded( + // child: Container( + // decoration: RoundedRectangleBorder().toSmoothCornerDecoration( + // color: AppColors.whiteColor, + // borderRadius: 12.h, + // hasShadow: false, + // ), + // child: Padding( + // padding: EdgeInsets.all(16.h), + // child: Row( + // children: [ + // Utils.buildSvgWithAssets( + // icon: AppAssets.car_parking_icon, + // width: 32.w, + // height: 32.h, + // fit: BoxFit.contain, + // ), + // SizedBox(width: 8.w), + // LocaleKeys.carParking.tr().toText14(isBold: true) + // ], + // ).onPress(() { + // Navigator.push( + // context, + // MaterialPageRoute( + // builder: (_) => ChangeNotifierProvider( + // create: (_) => getIt(), + // child: const ParkingPage(), + // ), + // ), + // ); + // }), + // ), + // ), + // ), + // ], + // ), SizedBox(height: 16.h), Row( children: [ @@ -723,7 +724,7 @@ class ServicesPage extends StatelessWidget { fit: BoxFit.contain, ), SizedBox(width: 8.w), - LocaleKeys.latestNews.tr().toText14(weight: FontWeight.w500) + LocaleKeys.latestNews.tr().toText14(isBold: true) ], ), ), @@ -752,7 +753,7 @@ class ServicesPage extends StatelessWidget { fit: BoxFit.contain, ), SizedBox(width: 8.w), - Expanded(child: LocaleKeys.hmgContact.tr().toText14(weight: FontWeight.w500)) + Expanded(child: LocaleKeys.hmgContact.tr().toText14(isBold: true)) ], ), ), @@ -786,7 +787,7 @@ class ServicesPage extends StatelessWidget { children: [ Utils.buildSvgWithAssets(icon: AppAssets.privacy_terms, width: 32.w, height: 32.h, fit: BoxFit.contain, iconColor: AppColors.blackColor), SizedBox(width: 8.w), - Expanded(child: LocaleKeys.termsConditoins.tr().toText14(weight: FontWeight.w500)) + Expanded(child: LocaleKeys.termsConditoins.tr().toText14(isBold: true)) ], ), ), @@ -810,7 +811,7 @@ class ServicesPage extends StatelessWidget { children: [ Utils.buildSvgWithAssets(icon: AppAssets.privacy_terms, width: 32.w, height: 32.h, fit: BoxFit.contain, iconColor: AppColors.blackColor), SizedBox(width: 8.w), - Expanded(child: LocaleKeys.privacyPolicy.tr().toText14(weight: FontWeight.w500)) + Expanded(child: LocaleKeys.privacyPolicy.tr().toText14(isBold: true)) ], ), ), diff --git a/lib/presentation/hmg_services/services_view.dart b/lib/presentation/hmg_services/services_view.dart index dd70c413..7192ce23 100644 --- a/lib/presentation/hmg_services/services_view.dart +++ b/lib/presentation/hmg_services/services_view.dart @@ -54,7 +54,7 @@ class ServiceGridViewItem extends StatelessWidget { ), SizedBox(height: 4.h), hmgServiceComponentModel.title.toText12( - fontWeight: FontWeight.w500, + isBold: true, color: AppColors.textColor, maxLine: 2, ), diff --git a/lib/presentation/home/app_update_page.dart b/lib/presentation/home/app_update_page.dart index 18382838..8750fcb7 100644 --- a/lib/presentation/home/app_update_page.dart +++ b/lib/presentation/home/app_update_page.dart @@ -41,7 +41,7 @@ class AppUpdatePage extends StatelessWidget { ), ), SizedBox(height: 50.h), - appUpdateMessage.toText16(weight: FontWeight.w500), + appUpdateMessage.toText16(isBold: true), ], ), ), diff --git a/lib/presentation/home/data/landing_page_data.dart b/lib/presentation/home/data/landing_page_data.dart index 33b12855..a60a7c18 100644 --- a/lib/presentation/home/data/landing_page_data.dart +++ b/lib/presentation/home/data/landing_page_data.dart @@ -142,6 +142,16 @@ class LandingPageData { textColor: AppColors.blackColor, isBold: false, ), + ServiceCardData( + serviceName: "my_invoice", + icon: AppAssets.invoiceListIcon, + title: LocaleKeys.invoiceList, + subtitle: LocaleKeys.myDoctorSubtitle, + backgroundColor: AppColors.whiteColor, + iconColor: AppColors.blackColor, + textColor: AppColors.blackColor, + isBold: false, + ), ]; static List get getServiceCardsList => [ @@ -154,6 +164,7 @@ class LandingPageData { backgroundColor: AppColors.successColor, iconColor: AppColors.whiteColor, isBold: false, + isPNG: false ), // ServiceCardData( // icon: AppAssets.homeBottom, @@ -172,16 +183,18 @@ class LandingPageData { backgroundColor: AppColors.primaryRedColor, iconColor: AppColors.whiteColor, isBold: false, + isPNG: false ), ServiceCardData( serviceName: "pharmacy", - icon: AppAssets.pharmacy_icon, //359846 + icon: AppAssets.defaultMEP, //359846 title: LocaleKeys.hmgPharmacy, subtitle: LocaleKeys.hmgPharmacyText, largeCardIcon: AppAssets.pharmacyService, backgroundColor: AppColors.pharmacyBGColor, iconColor: null, isBold: true, + isPNG: true ), ]; } diff --git a/lib/presentation/home/data/service_card_data.dart b/lib/presentation/home/data/service_card_data.dart index c16147b1..f2f1d121 100644 --- a/lib/presentation/home/data/service_card_data.dart +++ b/lib/presentation/home/data/service_card_data.dart @@ -12,6 +12,7 @@ class ServiceCardData { final Color? backgroundColor; final bool isBold; final String largeCardIcon; + final bool isPNG; ServiceCardData({ this.serviceName = "", @@ -22,6 +23,7 @@ class ServiceCardData { this.backgroundColor, this.iconColor, this.textColor, - this.isBold = false + this.isBold = false, + this.isPNG = false, }); } diff --git a/lib/presentation/home/landing_page.dart b/lib/presentation/home/landing_page.dart index 32525234..f103bd3f 100644 --- a/lib/presentation/home/landing_page.dart +++ b/lib/presentation/home/landing_page.dart @@ -48,6 +48,7 @@ import 'package:hmg_patient_app_new/presentation/home/widgets/large_service_card import 'package:hmg_patient_app_new/presentation/home/widgets/small_service_card.dart'; import 'package:hmg_patient_app_new/presentation/home/widgets/welcome_widget.dart'; import 'package:hmg_patient_app_new/presentation/insurance/insurance_home_page.dart'; +import 'package:hmg_patient_app_new/widgets/user_avatar_widget.dart'; import 'package:hmg_patient_app_new/presentation/insurance/widgets/insurance_update_details_card.dart'; import 'package:hmg_patient_app_new/presentation/medical_file/medical_file_page.dart'; import 'package:hmg_patient_app_new/presentation/my_family/my_family.dart'; @@ -55,6 +56,7 @@ import 'package:hmg_patient_app_new/presentation/notifications/notifications_lis import 'package:hmg_patient_app_new/presentation/profile_settings/profile_settings.dart'; import 'package:hmg_patient_app_new/presentation/rate_appointment/rate_appointment_doctor.dart'; import 'package:hmg_patient_app_new/presentation/todo_section/ancillary_procedures_details_page.dart'; +import 'package:hmg_patient_app_new/presentation/todo_section/todo_page.dart'; 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'; @@ -65,13 +67,17 @@ 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/countdown_timer.dart'; +import 'package:hmg_patient_app_new/widgets/loader/bottomsheet_loader.dart'; import 'package:hmg_patient_app_new/widgets/routes/custom_page_route.dart'; import 'package:hmg_patient_app_new/widgets/routes/spring_page_route_builder.dart'; +import 'package:lottie/lottie.dart'; import 'package:provider/provider.dart'; import 'package:smooth_corner/smooth_corner.dart'; import '../emergency_services/call_ambulance/widgets/HospitalBottomSheetBody.dart'; +import 'dart:ui' as ui; + class LandingPage extends StatefulWidget { const LandingPage({super.key}); @@ -99,9 +105,13 @@ class _LandingPageState extends State { final ScrollController _horizontalScrollController = ScrollController(); + final ScrollController _scrollController = ScrollController(); + double _scrollOpacity = 0.0; + @override void dispose() { _horizontalScrollController.dispose(); + _scrollController.dispose(); super.dispose(); } @@ -117,7 +127,23 @@ class _LandingPageState extends State { showQuickLogin(context); }); } - scheduleMicrotask(() { + + _scrollController.addListener(() { + final scrollOffset = _scrollController.offset; + final newOpacity = (scrollOffset / 50).clamp(0.0, 1.0); + if (_scrollOpacity != newOpacity) { + setState(() { + _scrollOpacity = newOpacity; + }); + } + }); + + scheduleMicrotask(() async { + if (!appState.isAuthenticated) { + // LoaderBottomSheet.showLoader(); + await authVM.handleNewImeiRegistration(isNavigate: false); + } + if (appState.isAuthenticated) { habibWalletVM.initHabibWalletProvider(); habibWalletVM.getPatientBalanceAmount(); @@ -171,563 +197,662 @@ class _LandingPageState extends State { body: Consumer(builder: (context, insuranceVM, child) { return Stack( children: [ - SingleChildScrollView( - padding: EdgeInsets.only( - top: (appState.isAuthenticated && !insuranceVM.isInsuranceLoading && insuranceVM.isInsuranceExpired && insuranceVM.isInsuranceExpiryBannerShown) - ? (MediaQuery.paddingOf(context).top + 70.h) - : kToolbarHeight + 0.h, - bottom: 24), - child: Column( - spacing: 16.h, - children: [ - Row( - spacing: 8.h, - mainAxisAlignment: MainAxisAlignment.spaceBetween, - children: [ - appState.isAuthenticated - ? WelcomeWidget( - onTap: () { - // DialogService dialogService = getIt.get(); - // dialogService.showFamilyBottomSheetWithoutH( - // label: LocaleKeys.familyTitle.tr(context: context), - // message: "", - // isShowManageButton: true, - // onSwitchPress: (FamilyFileResponseModelLists profile) { - // getIt.get().switchFamilyFiles(responseID: profile.responseId, patientID: profile.patientId, phoneNumber: profile.mobileNumber); - // }, - // profiles: getIt.get().patientFamilyFiles); + RefreshIndicator( + color: AppColors.primaryRedColor, + onRefresh: () async { + if (appState.isAuthenticated) { + // Refresh Appointments Data + myAppointmentsViewModel.setIsAppointmentDataToBeLoaded(true); + myAppointmentsViewModel.initAppointmentsViewModel(); + myAppointmentsViewModel.getPatientAppointments(true, false); + + // Refresh Appointments Data + habibWalletVM.initHabibWalletProvider(); + habibWalletVM.getPatientBalanceAmount(); + + // Refresh Ancillary Orders + todoSectionViewModel.initializeTodoSectionViewModel(); + + // Refresh Immediate LiveCare Data + immediateLiveCareViewModel.initImmediateLiveCare(); + immediateLiveCareViewModel.getPatientLiveCareHistory(); + } + }, + child: SingleChildScrollView( + controller: _scrollController, + physics: const AlwaysScrollableScrollPhysics(), + padding: EdgeInsets.only( + top: (appState.isAuthenticated && !insuranceVM.isInsuranceLoading && insuranceVM.isInsuranceExpired && insuranceVM.isInsuranceExpiryBannerShown) + ? (MediaQuery.paddingOf(context).top + 70.h) + : kToolbarHeight + 0.h, + bottom: 24), + child: Column( + spacing: 16.h, + children: [ + + + Row( + spacing: 8.h, + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + appState.isAuthenticated + ? WelcomeWidget( + onTap: () { + // ...existing navigation code... + + Navigator.of(context).push( + CustomPageRoute( + direction: AxisDirection.down, + page: FamilyMedicalScreen(), + ), + ); + }, + name: ('${appState.getAuthenticatedUser()!.firstName!} ${appState.getAuthenticatedUser()!.lastName!}'), + imageWidget: UserAvatarWidget( + width: 42.w, + height: 42.h, + fit: BoxFit.cover, + isCircular: true, + ), + ).expanded + : CustomButton( + text: LocaleKeys.loginOrRegister.tr(context: context), + onPressed: () async { + await authVM.onLoginPressed(); + // ...existing commented code... + }, + backgroundColor: AppColors.secondaryLightRedColor, + borderColor: AppColors.secondaryLightRedColor, + textColor: AppColors.primaryRedColor, + fontSize: 14.f, + fontWeight: FontWeight.w700, + borderRadius: 12.r, + padding: EdgeInsets.fromLTRB(12.h, 0, 12.h, 0), + height: 40.h, + ), + Consumer(builder: (context, todoSectionVM, child) { + return Row( + mainAxisSize: MainAxisSize.min, + // spacing: 18.h, + children: [ + - Navigator.of(context).push( - CustomPageRoute( - direction: AxisDirection.down, - page: FamilyMedicalScreen(), + Stack(clipBehavior: Clip.none, children: [ + 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: appState.isArabic() ? 8.w : -8.w, + top: -8.h, + // left: 4.h, + // bottom: 4.h, + child: Container( + width: 18.w, + height: 18.h, + padding: EdgeInsets.all(2), + decoration: BoxDecoration( + color: AppColors.primaryRedColor, + borderRadius: BorderRadius.circular(20.r), + ), + child: Text( + todoSectionVM.notificationsCount.toString(), + style: TextStyle( + color: Colors.white, + fontFamily: "Poppins", + fontSize: 10.f, + fontWeight: FontWeight.w600, + ), + textAlign: TextAlign.center, + ), ), + ) + : SizedBox.shrink(), + ]), + SizedBox(width: 24.w,), + Utils.buildSvgWithAssets(icon: AppAssets.location, height: 24.h, width: 24.w).onPress(() { + // openIndoorNavigationBottomSheet(context); + showCommonBottomSheetWithoutHeight( + context, + title: LocaleKeys.contactUs.tr(), + child: ContactUs(), + callBackFunc: () {}, + isFullScreen: false, ); - }, - name: ('${appState.getAuthenticatedUser()!.firstName!} ${appState.getAuthenticatedUser()!.lastName!}'), - imageUrl: appState.getAuthenticatedUser()?.gender == 1 ? AppAssets.maleImg : AppAssets.femaleImg, - ).expanded - : CustomButton( - text: LocaleKeys.loginOrRegister.tr(context: context), - onPressed: () async { - await authVM.onLoginPressed(); + }), - // Navigator.pushReplacementNamed( - // // context, - // context, - // AppRoutes.zoomCallPage, - // // arguments: CallArguments(appointmentID, "111", "Patient", "40", "1", true, 1), - // arguments: CallArguments("test123", "123", "Patient", "40", "0", true, 1), - // // arguments: CallArguments("SmallDailyStandup9875", "123", "Patient", "40", "0", false, int.parse(widget.incomingCallData!.appointmentNo!)), - // ); - }, - backgroundColor: AppColors.secondaryLightRedColor, - borderColor: AppColors.secondaryLightRedColor, - textColor: AppColors.primaryRedColor, - fontSize: 14.f, - fontWeight: FontWeight.w500, - borderRadius: 12.r, - padding: EdgeInsets.fromLTRB(12.h, 0, 12.h, 0), - height: 40.h, + // Utils.buildSvgWithAssets(icon: AppAssets.contact_icon, height: 24.h, width: 24.h).onPress(() { + // showCommonBottomSheetWithoutHeight( + // context, + // title: LocaleKeys.contactUs.tr(), + // child: ContactUs(), + // callBackFunc: () {}, + // isFullScreen: false, + // ); + // }), + !appState.isAuthenticated + ?Row(children: [ SizedBox(width: 24.w,), Utils.buildSvgWithAssets(icon: appState.isArabic() ? AppAssets.enLangIcon : AppAssets.arLangIcon, height: 24.h, width: 24.h).onPress(() { + context.setLocale(appState.isArabic() ? Locale('en', 'US') : Locale('ar', 'SA')); + })]) + : SizedBox() + ], + ); + }), + ], + ).paddingSymmetrical(24.h, 0.h), + !appState.isAuthenticated + ? Container( + decoration: RoundedRectangleBorder().toSmoothCornerDecoration( + color: AppColors.whiteColor, + borderRadius: 24.r, + hasShadow: false, ), - Consumer(builder: (context, todoSectionVM, child) { - return Row( - mainAxisSize: MainAxisSize.min, - spacing: 18.h, - children: [ - Stack(clipBehavior: Clip.none, children: [ - 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: appState.isArabic() ? 8.w : -8.w, - top: -8.h, - // left: 4.h, - // bottom: 4.h, - child: Container( - width: 18.w, - height: 18.h, - padding: EdgeInsets.all(2), - decoration: BoxDecoration( - color: AppColors.primaryRedColor, - borderRadius: BorderRadius.circular(20.r), - ), - child: Text( - todoSectionVM.notificationsCount.toString(), - style: TextStyle( - color: Colors.white, fontFamily: "Poppins", fontSize: 10.f, fontWeight: FontWeight.w500), - textAlign: TextAlign.center, - ), - ), - ) - : SizedBox.shrink(), - ]), - Utils.buildSvgWithAssets(icon: AppAssets.location, height: 24.h, width: 24.w).onPress(() { - // openIndoorNavigationBottomSheet(context); - showCommonBottomSheetWithoutHeight( - context, - title: LocaleKeys.contactUs.tr(), - child: ContactUs(), - callBackFunc: () {}, - isFullScreen: false, - ); - }), - // Utils.buildSvgWithAssets(icon: AppAssets.contact_icon, height: 24.h, width: 24.h).onPress(() { - // showCommonBottomSheetWithoutHeight( - // context, - // title: LocaleKeys.contactUs.tr(), - // child: ContactUs(), - // callBackFunc: () {}, - // isFullScreen: false, - // ); - // }), - !appState.isAuthenticated - ? Utils.buildSvgWithAssets(icon: appState.isArabic() ? AppAssets.enLangIcon : AppAssets.arLangIcon, height: 24.h, width: 24.h).onPress(() { - context.setLocale(appState.isArabic() ? Locale('en', 'US') : Locale('ar', 'SA')); - }) - : SizedBox.shrink() - ], - ); - }), - ], - ).paddingSymmetrical(24.h, 0.h), - !appState.isAuthenticated - ? Container( - decoration: RoundedRectangleBorder().toSmoothCornerDecoration( - color: AppColors.whiteColor, - borderRadius: 24.r, - hasShadow: false, - ), - child: Padding( - padding: EdgeInsets.all(16.h), - child: Row( + child: Padding( + padding: EdgeInsets.all(16.h), + child: Row( + children: [ + Utils.buildSvgWithAssets( + width: 50.w, + height: 60.h, + icon: AppAssets.symptomCheckerIcon, + fit: BoxFit.contain, + ), + SizedBox(width: 12.w), + Column( + crossAxisAlignment: CrossAxisAlignment.start, children: [ - Utils.buildSvgWithAssets( - width: 50.w, - height: 60.h, - icon: AppAssets.symptomCheckerIcon, - fit: BoxFit.contain, + LocaleKeys.howAreYouFeelingToday.tr(context: context).toText14(isBold: true), + LocaleKeys.checkYourSymptomsWithScale.tr(context: context).toText12(isBold: true), + SizedBox(height: 14.h), + CustomButton( + text: LocaleKeys.checkYourSymptoms.tr(context: context), + onPressed: () async { + context.navigateWithName(AppRoutes.userInfoSelection); + }, + padding: EdgeInsetsGeometry.zero, + backgroundColor: AppColors.primaryRedColor, + borderColor: AppColors.primaryRedColor, + textColor: Colors.white, + fontSize: 14.f, + fontWeight: FontWeight.w600, + borderRadius: 12.r, + height: 40.h, ), - SizedBox(width: 12.w), - Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - LocaleKeys.howAreYouFeelingToday.tr(context: context).toText14(weight: FontWeight.w600), - LocaleKeys.checkYourSymptomsWithScale.tr(context: context).toText12(fontWeight: FontWeight.w500), - SizedBox(height: 14.h), - CustomButton( - text: LocaleKeys.checkYourSymptoms.tr(context: context), - onPressed: () async { - context.navigateWithName(AppRoutes.userInfoSelection); - }, - padding: EdgeInsetsGeometry.zero, - backgroundColor: AppColors.primaryRedColor, - borderColor: AppColors.primaryRedColor, - textColor: Colors.white, - fontSize: 14.f, - fontWeight: FontWeight.w600, - borderRadius: 12.r, - height: 40.h, - ), - ], - ).expanded ], - ), - ), - ).paddingSymmetrical(24.w, 0.h) - : SizedBox.shrink(), - appState.isAuthenticated - ? Column( + ).expanded + ], + ), + ), + ).paddingSymmetrical(24.w, 0.h) + : SizedBox.shrink(), + appState.isAuthenticated + ? Column( + children: [ + SizedBox(height: 12.h), + Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ - SizedBox(height: 12.h), + LocaleKeys.appointmentsAndVisits.tr(context: context).toText16(isBold: true), Row( - mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ - LocaleKeys.appointmentsAndVisits.tr(context: context).toText16(weight: FontWeight.w600), - Row( - children: [ - 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: 14.h), - ], - ), + LocaleKeys.viewAll.tr(context: context).toText14(color: AppColors.primaryRedColor, isBold: true), + SizedBox(width: 2.h), + Icon(Icons.arrow_forward_ios, color: AppColors.primaryRedColor, size: 14.h), ], - ).paddingSymmetrical(24.h, 0.h).onPress(() { - myAppointmentsViewModel.onTabChange(0); - myAppointmentsViewModel.updateListWRTTab(0); - Navigator.of(context).push(CustomPageRoute(page: MyAppointmentsPage())); - }), - Consumer3( - builder: (context, myAppointmentsVM, immediateLiveCareVM, todoSectionVM, child) { - return myAppointmentsVM.isMyAppointmentsLoading - ? Container( - decoration: RoundedRectangleBorder().toSmoothCornerDecoration( - color: AppColors.whiteColor, - borderRadius: 24.r, - hasShadow: true, - ), - child: AppointmentCard( - patientAppointmentHistoryResponseModel: PatientAppointmentHistoryResponseModel(), - myAppointmentsViewModel: myAppointmentsViewModel, - bookAppointmentsViewModel: bookAppointmentsViewModel, - isLoading: true, - isFromHomePage: true, - ), - ).paddingSymmetrical(24.h, 16.h) - : myAppointmentsVM.patientAppointmentsHistoryList.isNotEmpty - ? myAppointmentsVM.patientAppointmentsHistoryList.length == 1 - ? Container( - decoration: RoundedRectangleBorder().toSmoothCornerDecoration( - color: AppColors.whiteColor, - borderRadius: 24.r, - hasShadow: true, - ), - child: AppointmentCard( - patientAppointmentHistoryResponseModel: myAppointmentsVM.patientAppointmentsHistoryList.first, - myAppointmentsViewModel: myAppointmentsViewModel, - bookAppointmentsViewModel: bookAppointmentsViewModel, - isLoading: false, - isFromHomePage: true, - ), - ).paddingSymmetrical(24.h, 0.h) - : isTablet - ? SizedBox( - height: isFoldable ? 290.h : 255.h, - child: ListView.separated( - scrollDirection: Axis.horizontal, - itemCount: 3, - shrinkWrap: true, - padding: EdgeInsets.only(left: 16.h, right: 16.h), - itemBuilder: (context, index) { - return SizedBox( - height: 255.h, - width: 250.w, - child: getIndexSwiperCard(index), - ); - // return AnimationConfiguration.staggeredList( - // position: index, - // duration: const Duration(milliseconds: 1000), - // child: SlideAnimation( - // horizontalOffset: 100.0, - // child: FadeInAnimation( - // child: SizedBox( - // height: 255.h, - // width: 250.w, - // child: getIndexSwiperCard(index), - // ), - // ), - // ), - // ); - }, - separatorBuilder: (BuildContext cxt, int index) => SizedBox( - width: 10.w, - ), - ), - ) - : SizedBox( - height: 255.h + 20 + 30, // itemHeight + shadow padding (10 top + 10 bottom) + pagination dots space - child: Swiper( - itemCount: myAppointmentsVM.isMyAppointmentsLoading - ? 3 - : myAppointmentsVM.patientAppointmentsHistoryList.length < 3 - ? myAppointmentsVM.patientAppointmentsHistoryList.length - : 3, - layout: SwiperLayout.STACK, - loop: false, - itemWidth: MediaQuery.of(context).size.width - 48.h, - indicatorLayout: PageIndicatorLayout.COLOR, - axisDirection: getIt.get().isArabic() ? AxisDirection.left : AxisDirection.right, - controller: _controller, - itemHeight: 255.h + 20, - // extra space for shadow - pagination: SwiperPagination( - alignment: Alignment.bottomCenter, - margin: EdgeInsets.only(top: 220.h + 20 + 8 + 24), - builder: DotSwiperPaginationBuilder(color: Color(0xffD9D9D9), activeColor: AppColors.blackBgColor), + ), + ], + ).paddingSymmetrical(24.h, 0.h).onPress(() { + myAppointmentsViewModel.onTabChange(0); + myAppointmentsViewModel.updateListWRTTab(0); + Navigator.of(context).push(CustomPageRoute(page: MyAppointmentsPage())); + }), + Consumer3( + builder: (context, myAppointmentsVM, immediateLiveCareVM, todoSectionVM, child) { + return myAppointmentsVM.isMyAppointmentsLoading + ? Container( + decoration: RoundedRectangleBorder().toSmoothCornerDecoration( + color: AppColors.whiteColor, + borderRadius: 24.r, + hasShadow: true, + ), + child: AppointmentCard( + patientAppointmentHistoryResponseModel: PatientAppointmentHistoryResponseModel(), + myAppointmentsViewModel: myAppointmentsViewModel, + bookAppointmentsViewModel: bookAppointmentsViewModel, + isLoading: true, + isFromHomePage: true, + ), + ).paddingSymmetrical(24.h, 16.h) + : myAppointmentsVM.patientAppointmentsHistoryList.isNotEmpty + ? myAppointmentsVM.patientAppointmentsHistoryList.length == 1 + ? Container( + decoration: RoundedRectangleBorder().toSmoothCornerDecoration( + color: AppColors.whiteColor, + borderRadius: 24.r, + hasShadow: true, + ), + child: AppointmentCard( + patientAppointmentHistoryResponseModel: myAppointmentsVM.patientAppointmentsHistoryList.first, + myAppointmentsViewModel: myAppointmentsViewModel, + bookAppointmentsViewModel: bookAppointmentsViewModel, + isLoading: false, + isFromHomePage: true, + ), + ).paddingSymmetrical(24.h, 0.h) + : isTablet + ? SizedBox( + height: isFoldable ? 290.h : 255.h, + child: ListView.separated( + scrollDirection: Axis.horizontal, + itemCount: 3, + shrinkWrap: true, + padding: EdgeInsets.only(left: 16.h, right: 16.h), + itemBuilder: (context, index) { + return SizedBox( + height: 255.h, + width: 250.w, + child: getIndexSwiperCard(index), + ); + // return AnimationConfiguration.staggeredList( + // position: index, + // duration: const Duration(milliseconds: 1000), + // child: SlideAnimation( + // horizontalOffset: 100.0, + // child: FadeInAnimation( + // child: SizedBox( + // height: 255.h, + // width: 250.w, + // child: getIndexSwiperCard(index), + // ), + // ), + // ), + // ); + }, + separatorBuilder: (BuildContext cxt, int index) => + SizedBox( + width: 10.w, + ), + ), + ) + : SizedBox( + height: 255.h + 20 + 30, // itemHeight + shadow padding (10 top + 10 bottom) + pagination dots space + child: Builder( + builder: (context) { + final int swiperItemCount = myAppointmentsVM.isMyAppointmentsLoading + ? 3 + : myAppointmentsVM.patientAppointmentsHistoryList.length < 3 + ? myAppointmentsVM.patientAppointmentsHistoryList.length + : 3; + final bool isArabic = appState.isArabic(); + return Swiper( + itemCount: swiperItemCount, + layout: SwiperLayout.STACK, + loop: false, + itemWidth: MediaQuery.of(context).size.width - 48.h, + indicatorLayout: PageIndicatorLayout.COLOR, + axisDirection: isArabic ? AxisDirection.right : AxisDirection.left, + controller: _controller, + index: isArabic ? swiperItemCount - 1 : 0, + itemHeight: 255.h + 20, + // extra space for shadow + pagination: SwiperPagination( + alignment: Alignment.bottomCenter, + margin: EdgeInsets.only(top: 220.h + 20 + 8 + 24), + builder: DotSwiperPaginationBuilder(color: Color(0xffD9D9D9), activeColor: AppColors.blackBgColor), + ), + itemBuilder: (BuildContext context, int index) { + final int actualIndex = isArabic ? (swiperItemCount - 1 - index) : index; + return Padding( + padding: const EdgeInsets.symmetric(vertical: 10), + child: getIndexSwiperCard(actualIndex), + ); + }, + ); + }, ), - itemBuilder: (BuildContext context, int index) { - return Padding( - padding: const EdgeInsets.symmetric(vertical: 10), - child: getIndexSwiperCard(index), - ); + ) + : Container( + width: double.infinity, + decoration: RoundedRectangleBorder().toSmoothCornerDecoration(color: AppColors.whiteColor, borderRadius: 24.r, hasShadow: true), + child: Padding( + padding: EdgeInsets.all(16.h), + child: Column( + children: [ + Utils.buildSvgWithAssets(icon: AppAssets.home_calendar_icon, width: 32.h, height: 32.h), + SizedBox(height: 12.h), + LocaleKeys.noUpcomingAppointmentPleaseBook.tr(context: context).toText12(isCenter: true), + SizedBox(height: 12.h), + CustomButton( + text: LocaleKeys.bookAppo.tr(context: context), + onPressed: () { + getIt.get().onTabChanged(0); + Navigator.of(context).push(CustomPageRoute(page: BookAppointmentPage())); }, + backgroundColor: Color(0xffFEE9EA), + borderColor: Color(0xffFEE9EA), + textColor: Color(0xffED1C2B), + fontSize: 14.f, + fontWeight: FontWeight.w600, + padding: EdgeInsets.fromLTRB(10.h, 0, 10.h, 0), + icon: AppAssets.add_icon, + iconColor: AppColors.primaryRedColor, + height: 40.h, ), - ) - : Container( - width: double.infinity, - decoration: RoundedRectangleBorder().toSmoothCornerDecoration(color: AppColors.whiteColor, borderRadius: 24.r, hasShadow: true), - child: Padding( - padding: EdgeInsets.all(16.h), - child: Column( - children: [ - Utils.buildSvgWithAssets(icon: AppAssets.home_calendar_icon, width: 32.h, height: 32.h), - SizedBox(height: 12.h), - LocaleKeys.noUpcomingAppointmentPleaseBook.tr(context: context).toText12(isCenter: true), - SizedBox(height: 12.h), - CustomButton( - text: LocaleKeys.bookAppo.tr(context: context), - onPressed: () { - getIt.get().onTabChanged(0); - Navigator.of(context).push(CustomPageRoute(page: BookAppointmentPage())); - }, - backgroundColor: Color(0xffFEE9EA), - borderColor: Color(0xffFEE9EA), - textColor: Color(0xffED1C2B), - fontSize: 14.f, - fontWeight: FontWeight.w500, - padding: EdgeInsets.fromLTRB(10.h, 0, 10.h, 0), - icon: AppAssets.add_icon, - iconColor: AppColors.primaryRedColor, - height: 40.h, - ), - ], - ), - ), - ).paddingSymmetrical(24.h, 16.h); - }, - ), - - // Consumer for ER Online Check-In pending request - // Consumer( - // builder: (context, emergencyServicesVM, child) { - // return emergencyServicesVM.patientHasAdvanceERBalance - // ? Column( - // children: [ - // SizedBox(height: 16.h), - // Container( - // decoration: RoundedRectangleBorder().toSmoothCornerDecoration( - // color: AppColors.whiteColor, - // borderRadius: 20.r, - // hasShadow: false, - // side: BorderSide(color: AppColors.primaryRedColor, width: 3.h), - // ), - // width: double.infinity, - // child: Padding( - // padding: EdgeInsets.all(16.h), - // child: Column( - // crossAxisAlignment: CrossAxisAlignment.start, - // children: [ - // // Row( - // // mainAxisAlignment: MainAxisAlignment.spaceBetween, - // // children: [ - // // AppCustomChipWidget( - // // labelText: LocaleKeys.erOnlineCheckInRequest.tr(context: context), - // // backgroundColor: AppColors.primaryRedColor.withValues(alpha: 0.10), - // // textColor: AppColors.primaryRedColor, - // // ), - // // Utils.buildSvgWithAssets(icon: AppAssets.appointment_checkin_icon, width: 24.h, height: 24.h, iconColor: AppColors.primaryRedColor), - // // ], - // // ), - // SizedBox(height: 8.h), - // Row( - // mainAxisAlignment: MainAxisAlignment.spaceBetween, - // children: [ - // LocaleKeys.youHaveEROnlineCheckInRequest.tr(context: context).toText12(isBold: true), - // Transform.flip( - // flipX: getIt.get().isArabic(), - // child: Utils.buildSvgWithAssets( - // icon: AppAssets.forward_arrow_icon_small, - // iconColor: AppColors.blackColor, - // width: 20.h, - // height: 15.h, - // fit: BoxFit.contain, - // ), - // ), - // ], - // ), - // ], - // ), - // ), - // ).paddingSymmetrical(24.h, 0.h).onPress(() { - // Navigator.of(context).push(CustomPageRoute(page: ErOnlineCheckinHome())); - // // context.read().navigateToEROnlineCheckIn(); - // }), - // SizedBox(height: 12.h), - // ], - // ) - // : SizedBox(height: 0.h); - // }, - // ), - SizedBox(height: 16.h), - Row( - mainAxisAlignment: MainAxisAlignment.spaceBetween, - children: [ - LocaleKeys.quickLinks.tr(context: context).toText16(weight: FontWeight.w600), - Row( - children: [ - 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: 14.h), - ], - ), - ], - ).paddingSymmetrical(24.h, 0.h).onPress(() { - Navigator.of(context).push(CustomPageRoute(page: MedicalFilePage())); - }), - SizedBox(height: 16.h), - Container( - // height: 121.h, - decoration: RoundedRectangleBorder().toSmoothCornerDecoration(color: AppColors.whiteColor, borderRadius: 24.r), - child: Column( - children: [ - 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.5 - 10, right: MediaQuery.sizeOf(context).width / 2.5 - 10), - child: ListView.separated( - scrollDirection: Axis.horizontal, - itemCount: LandingPageData.getLoggedInServiceCardsList.length, - shrinkWrap: true, - controller: _horizontalScrollController, - padding: EdgeInsets.only(left: 0.h, right: 0.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, + ], ), ), + ).paddingSymmetrical(24.h, 16.h); + }, + ), + + // Consumer for ER Online Check-In pending request + // Consumer( + // builder: (context, emergencyServicesVM, child) { + // return emergencyServicesVM.patientHasAdvanceERBalance + // ? Column( + // children: [ + // SizedBox(height: 16.h), + // Container( + // decoration: RoundedRectangleBorder().toSmoothCornerDecoration( + // color: AppColors.whiteColor, + // borderRadius: 20.r, + // hasShadow: false, + // side: BorderSide(color: AppColors.primaryRedColor, width: 3.h), + // ), + // width: double.infinity, + // child: Padding( + // padding: EdgeInsets.all(16.h), + // child: Column( + // crossAxisAlignment: CrossAxisAlignment.start, + // children: [ + // // Row( + // // mainAxisAlignment: MainAxisAlignment.spaceBetween, + // // children: [ + // // AppCustomChipWidget( + // // labelText: LocaleKeys.erOnlineCheckInRequest.tr(context: context), + // // backgroundColor: AppColors.primaryRedColor.withValues(alpha: 0.10), + // // textColor: AppColors.primaryRedColor, + // // ), + // // Utils.buildSvgWithAssets(icon: AppAssets.appointment_checkin_icon, width: 24.h, height: 24.h, iconColor: AppColors.primaryRedColor), + // // ], + // // ), + // SizedBox(height: 8.h), + // Row( + // mainAxisAlignment: MainAxisAlignment.spaceBetween, + // children: [ + // LocaleKeys.youHaveEROnlineCheckInRequest.tr(context: context).toText12(isBold: true), + // Transform.flip( + // flipX: getIt.get().isArabic(), + // child: Utils.buildSvgWithAssets( + // icon: AppAssets.forward_arrow_icon_small, + // iconColor: AppColors.blackColor, + // width: 20.h, + // height: 15.h, + // fit: BoxFit.contain, + // ), + // ), + // ], + // ), + // ], + // ), + // ), + // ).paddingSymmetrical(24.h, 0.h).onPress(() { + // Navigator.of(context).push(CustomPageRoute(page: ErOnlineCheckinHome())); + // // context.read().navigateToEROnlineCheckIn(); + // }), + // SizedBox(height: 12.h), + // ], + // ) + // : SizedBox(height: 0.h); + // }, + // ), + SizedBox(height: 16.h), + Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + LocaleKeys.quickLinks.tr(context: context).toText16(isBold: true), + Row( + children: [ + LocaleKeys.viewMedicalFile.tr(context: context).toText14(color: AppColors.primaryRedColor, isBold: true), + SizedBox(width: 2.h), + Icon(Icons.arrow_forward_ios, color: AppColors.primaryRedColor, size: 14.h), + ], + ), + ], + ).paddingSymmetrical(24.h, 0.h).onPress(() { + Navigator.of(context).push(CustomPageRoute(page: MedicalFilePage())); + }), + SizedBox(height: 16.h), + Consumer(builder: (BuildContext context, TodoSectionViewModel todoSectionVM, Widget? child) { + return Container( + // height: 121.h, + decoration: RoundedRectangleBorder().toSmoothCornerDecoration(color: AppColors.whiteColor, borderRadius: 24.r), + child: Column( + children: [ + todoSectionVM.patientAncillaryOrdersList.isNotEmpty + ? Container( + height: 50.h, + decoration: ShapeDecoration( + color: AppColors.eReferralCardColor.withAlpha(50), + shape: SmoothRectangleBorder( + borderRadius: BorderRadius.only(topLeft: Radius.circular(24), topRight: Radius.circular(24)), + smoothness: 1, + ), + ), + child: Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + LocaleKeys.pendingAncillaryOrders.tr(context: context).toText14(color: AppColors.eReferralCardColor, isBold: true).paddingSymmetrical(24.h, 0.h), + CustomButton( + text: LocaleKeys.view.tr(context: context), + onPressed: () { + todoSectionVM.setIsAncillaryOrdersNeedReloading(true); + Navigator.of(context).push( + CustomPageRoute( + page: ToDoPage(), ), ); }, - separatorBuilder: (BuildContext cxt, int index) => 10.width, - ).paddingSymmetrical(16.h, 0.h), - ), + backgroundColor: AppColors.eReferralCardColor, + borderColor: AppColors.eReferralCardColor, + textColor: AppColors.whiteColor, + fontSize: 10.f, + fontWeight: FontWeight.w600, + borderRadius: 8, + padding: EdgeInsets.fromLTRB(15, 0, 15, 0), + height: 30.h, + ).paddingSymmetrical(24.h, 0.h), + ], ), - SizedBox(height: 16.h), - ], - ), - ).paddingSymmetrical(24.h, 0.h), - ], - ) - : Container( - decoration: RoundedRectangleBorder().toSmoothCornerDecoration(color: AppColors.whiteColor, borderRadius: 24.r), - child: Column( - children: [ - 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.5 - 10, right: MediaQuery.sizeOf(context).width / 2.5 - 10), - child: ListView.separated( - scrollDirection: Axis.horizontal, - itemCount: LandingPageData.getNotLoggedInServiceCardsList.length, - shrinkWrap: true, + ) + : SizedBox.shrink(), + SizedBox( + height: 92.h + 32.h - 4.h, + child: RawScrollbar( controller: _horizontalScrollController, - padding: EdgeInsets.only(left: 0.h, right: 0.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( - 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, + 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.5 - 10, right: MediaQuery + .sizeOf(context) + .width / 2.5 - 10), + child: ListView.separated( + scrollDirection: Axis.horizontal, + itemCount: LandingPageData.getLoggedInServiceCardsList.length, + shrinkWrap: true, + controller: _horizontalScrollController, + padding: EdgeInsets.only(left: 0.h, right: 0.h, top: 16.h, bottom: 12.h), + itemBuilder: (context, index) { + return AnimationConfiguration.staggeredList( + position: index, + duration: const Duration(milliseconds: 200), + 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) => 0.width, - ).paddingSymmetrical(16.h, 0.h), + ); + }, + separatorBuilder: (BuildContext cxt, int index) => 10.width, + ).paddingSymmetrical(16.h, 0.h), + ), ), - ), - SizedBox(height: 16.h), - ], - ), - ).paddingSymmetrical(24.h, 0.h), - Row( - mainAxisAlignment: MainAxisAlignment.spaceBetween, - children: [ - LocaleKeys.services2.tr(context: context).toText18(weight: FontWeight.w600), - Row( + SizedBox(height: 16.h), + ], + ), + ).paddingSymmetrical(24.h, 0.h); + }), + ], + ) + : Container( + decoration: RoundedRectangleBorder().toSmoothCornerDecoration(color: AppColors.whiteColor, borderRadius: 24.r), + child: Column( children: [ - 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: 14.h), + 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.5 - 10, right: MediaQuery + .sizeOf(context) + .width / 2.5 - 10), + child: ListView.separated( + scrollDirection: Axis.horizontal, + itemCount: LandingPageData.getNotLoggedInServiceCardsList.length, + shrinkWrap: true, + controller: _horizontalScrollController, + padding: EdgeInsets.only(left: 0.h, right: 0.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( + 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, + ).paddingSymmetrical(16.h, 0.h), + ), + ), + SizedBox(height: 16.h), ], - ).onPress(() { - Navigator.of(context).push(CustomPageRoute(page: ServicesPage())); - }), - ], - ).paddingSymmetrical(24.w, 0.h), - SizedBox( - height: 431.h, - child: ListView.separated( - scrollDirection: Axis.horizontal, - itemCount: LandingPageData.getServiceCardsList.length, - shrinkWrap: true, - padding: EdgeInsets.only(left: 24.w, right: 24.w), - itemBuilder: (context, index) { - return AnimationConfiguration.staggeredList( - position: index, - duration: const Duration(milliseconds: 1000), - child: SlideAnimation( - horizontalOffset: 100.0, - child: FadeInAnimation( - child: FadedLargeServiceCard( - serviceCardData: LandingPageData.getServiceCardsList[index], - image: LandingPageData.getServiceCardsList[index].icon, - title: LandingPageData.getServiceCardsList[index].title, - subtitle: LandingPageData.getServiceCardsList[index].subtitle, - icon: LandingPageData.getServiceCardsList[index].largeCardIcon, + ), + ).paddingSymmetrical(24.h, 0.h), + Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + LocaleKeys.services2.tr(context: context).toText18(isBold: true), + Row( + children: [ + LocaleKeys.viewAllServices.tr(context: context).toText14(color: AppColors.primaryRedColor, isBold: true), + SizedBox(width: 2.h), + Icon(Icons.arrow_forward_ios, color: AppColors.primaryRedColor, size: 14.h), + ], + ).onPress(() { + Navigator.of(context).push(CustomPageRoute(page: ServicesPage())); + }), + ], + ).paddingSymmetrical(24.w, 0.h), + SizedBox( + height: 431.h, + child: ListView.separated( + scrollDirection: Axis.horizontal, + itemCount: LandingPageData.getServiceCardsList.length, + shrinkWrap: true, + padding: EdgeInsets.only(left: 24.w, right: 24.w), + itemBuilder: (context, index) { + return AnimationConfiguration.staggeredList( + position: index, + duration: const Duration(milliseconds: 1000), + child: SlideAnimation( + horizontalOffset: 100.0, + child: FadeInAnimation( + child: FadedLargeServiceCard( + serviceCardData: LandingPageData.getServiceCardsList[index], + image: LandingPageData.getServiceCardsList[index].icon, + title: LandingPageData.getServiceCardsList[index].title, + subtitle: LandingPageData.getServiceCardsList[index].subtitle, + icon: LandingPageData.getServiceCardsList[index].largeCardIcon, + isPNG: LandingPageData.getServiceCardsList[index].isPNG, + ), ), ), - ), - ); - }, - separatorBuilder: (BuildContext cxt, int index) => SizedBox(width: 16.w), + ); + }, + separatorBuilder: (BuildContext cxt, int index) => SizedBox(width: 16.w), + ), ), + appState.isAuthenticated ? HabibWalletCard() : SizedBox(), + ], + ), + ), + ), + AnimatedOpacity( + opacity: _scrollOpacity, + duration: Duration(milliseconds: 200), + child: Container( + height: MediaQuery.paddingOf(context).top + 10.h, + decoration: BoxDecoration( + gradient: LinearGradient( + begin: Alignment.topCenter, + end: Alignment.bottomCenter, + colors: [ + AppColors.scaffoldBgColor, + AppColors.scaffoldBgColor.withValues(alpha: 0.9), + AppColors.scaffoldBgColor.withValues(alpha: 0.0), + ], + stops: [0.0, 0.7, 1.0], ), - appState.isAuthenticated ? HabibWalletCard() : SizedBox(), - ], + ), ), ), (appState.isAuthenticated && !insuranceVM.isInsuranceLoading && insuranceVM.isInsuranceExpired && insuranceVM.isInsuranceExpiryBannerShown) @@ -747,7 +872,7 @@ class _LandingPageState extends State { Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ - LocaleKeys.insuranceExpiredOrInactive.tr(context: context).toText14(color: AppColors.primaryRedColor, weight: FontWeight.w500).paddingSymmetrical(0.h, 0.h), + LocaleKeys.insuranceExpiredOrInactive.tr(context: context).toText14(color: AppColors.primaryRedColor, isBold: true).paddingSymmetrical(0.h, 0.h), Row( children: [ CustomButton( @@ -837,11 +962,11 @@ class _LandingPageState extends State { ); } - if (todoSectionViewModel.patientAncillaryOrdersList.isNotEmpty) { - return _buildAncillaryOrderCardWrapper( - todoSectionViewModel.patientAncillaryOrdersList.first, - ); - } + // if (todoSectionViewModel.patientAncillaryOrdersList.isNotEmpty) { + // return _buildAncillaryOrderCardWrapper( + // todoSectionViewModel.patientAncillaryOrdersList.first, + // ); + // } return _buildAppointmentCardWrapper( myAppointmentsViewModel.patientAppointmentsHistoryList[1], @@ -854,9 +979,9 @@ class _LandingPageState extends State { final hasAncillaryOrders = todoSectionViewModel.patientAncillaryOrdersList.isNotEmpty; if (hasQueueOrLiveCare && hasAncillaryOrders) { - return _buildAncillaryOrderCardWrapper( - todoSectionViewModel.patientAncillaryOrdersList.first, - ); + // return _buildAncillaryOrderCardWrapper( + // todoSectionViewModel.patientAncillaryOrdersList.first, + // ); } return _buildAppointmentCardWrapper( @@ -889,15 +1014,15 @@ class _LandingPageState extends State { _buildQueueGreeting(), SizedBox(height: 2.h), LocaleKeys.thankYouForPatience.tr().toText12( - fontWeight: FontWeight.w500, + isBold: true, color: AppColors.textColorLight, ), SizedBox(height: 8.h), - currentQueue.queueNo!.toText28(isBold: true), + (currentQueue.queueNo ?? "IM-123").toText28(isBold: true).toShimmer2(isShow: myAppointmentsViewModel.patientQueueDetailsList.isEmpty), SizedBox(height: 6.h), _buildServingNowSection(), SizedBox(height: 5.h), - _buildQueueActionButton(currentStatus, currentQueue.roomNo ?? ""), + _buildQueueActionButton(currentStatus, currentQueue.roomNo ?? "").toShimmer2(isShow: myAppointmentsViewModel.patientQueueDetailsList.isEmpty), ], ), ), @@ -912,17 +1037,21 @@ class _LandingPageState extends State { Widget _buildQueueHeader(int currentStatus) { return Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, + crossAxisAlignment: CrossAxisAlignment.start, 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, - ), + ).toShimmer2(isShow: myAppointmentsViewModel.patientQueueDetailsList.isEmpty), + Container( + decoration: RoundedRectangleBorder().toSmoothCornerDecoration( + color: AppColors.greyColor, + borderRadius: 10.h, + hasShadow: false, + ), + padding: EdgeInsets.all(6.h), + child: Lottie.asset(AppAnimations.hourGlass, repeat: true, reverse: false, frameRate: FrameRate(60), width: 40.h, height: 40.h, fit: BoxFit.fill)), ], ); } @@ -935,7 +1064,7 @@ class _LandingPageState extends State { // Queue Card: Currently serving section Widget _buildServingNowSection() { if (myAppointmentsViewModel.patientQueueDetailsList.isEmpty) { - return SizedBox(height: 12.h); + return SizedBox(height: 24.h); } final servingQueue = myAppointmentsViewModel.patientQueueDetailsList.first; @@ -999,22 +1128,44 @@ class _LandingPageState extends State { color: AppColors.whiteColor, borderRadius: 20.r, hasShadow: true, - side: BorderSide(color: AppColors.ratingColorYellow, width: 3.h), + // side: BorderSide(color: AppColors.ratingColorYellow, width: 3.h), ), child: Padding( padding: EdgeInsets.all(16.h), child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ - LocaleKeys.immediateLiveCareRequest.tr(context: context).toText16(isBold: true), - SizedBox(height: 10.h), - _buildLiveCareHeader(liveCareData), - SizedBox(height: 10.h), - _buildLiveCareGreeting(), - SizedBox(height: 8.h), - _buildLiveCareQueueInfo(liveCareData.patCount), - SizedBox(height: 8.h), - _buildLiveCareWaitingTime(), + Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + LocaleKeys.immediateLiveCareRequest.tr(context: context).toText16(isBold: true), + SizedBox(height: 10.h), + _buildLiveCareHeader(liveCareData), + SizedBox(height: 10.h), + SizedBox(height: 8.h), + _buildLiveCareQueueInfo(liveCareData.patCount), + SizedBox(height: 25.h), + ], + ), + CustomButton( + text: LocaleKeys.viewDetails.tr(context: context), + onPressed: () { + Navigator.of(context).push( + CustomPageRoute(page: ImmediateLiveCarePendingRequestPage()), + ); + }, + backgroundColor: AppColors.warningColorYellow.withValues(alpha: 0.20), + textColor: AppColors.alertColor, + borderColor: AppColors.infoBannerBgColor, + 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(), ], ), ), @@ -1034,6 +1185,7 @@ class _LandingPageState extends State { mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ Row( + crossAxisAlignment: CrossAxisAlignment.start, children: [ AppCustomChipWidget( labelText: liveCareData.stringCallStatus, @@ -1048,13 +1200,42 @@ class _LandingPageState extends State { false, ), ), + SizedBox(width: 8.w), + AppCustomChipWidget( + richText: ValueListenableBuilder( + valueListenable: immediateLiveCareViewModel.durationNotifier, + builder: (context, duration, child) { + final hours = duration.inHours.toString().padLeft(2, '0'); + final minutes = duration.inMinutes.remainder(60).toString().padLeft(2, '0'); + final seconds = duration.inSeconds.remainder(60).toString().padLeft(2, '0'); + return Column( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + '$hours:$minutes:$seconds'.toText10(isBold: true), + ], + ); + }, + ), + // labelText: DateUtil.formatDateToDate( + // DateUtil.convertStringToDate(liveCareData.arrivalTime), + // false, + // ), + ), ], ), - Utils.buildSvgWithAssets( - icon: AppAssets.waiting_icon, - width: 24.h, - height: 24.h, - ), + Container( + decoration: RoundedRectangleBorder().toSmoothCornerDecoration( + color: AppColors.greyColor, + borderRadius: 10.h, + hasShadow: false, + ), + padding: EdgeInsets.all(6.h), + child: Lottie.asset(AppAnimations.hourGlass, repeat: true, reverse: false, frameRate: FrameRate(60), width: 40.h, height: 40.h, fit: BoxFit.fill)), + // Utils.buildSvgWithAssets( + // icon: AppAssets.waiting_icon, + // width: 24.h, + // height: 24.h, + // ), ], ); } @@ -1069,10 +1250,7 @@ class _LandingPageState extends State { // LiveCare Card: Queue position info Widget _buildLiveCareQueueInfo(int? patCount) { - return LocaleKeys.yourTurnIsAfterPatients.tr( - namedArgs: {'count': (patCount ?? 0).toString()}, - context: context, - ).toText14(isBold: true); + return LocaleKeys.yourTurnIsAfterPatients.tr(namedArgs: {'count': (patCount ?? 0).toString()}, context: context).toText14(isBold: true); } // LiveCare Card: Waiting time counter @@ -1183,7 +1361,7 @@ class _LandingPageState extends State { return Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ - displayTitle.toText20(weight: FontWeight.w600), + displayTitle.toText20(isBold: true), (context.select((vm) => vm.subTitle) ?? '').toText12(), ], ); diff --git a/lib/presentation/home/navigation_screen.dart b/lib/presentation/home/navigation_screen.dart index 5cfc8ff7..d1ad847d 100644 --- a/lib/presentation/home/navigation_screen.dart +++ b/lib/presentation/home/navigation_screen.dart @@ -4,6 +4,7 @@ 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/features/book_appointments/book_appointments_view_model.dart'; +import 'package:hmg_patient_app_new/features/my_appointments/my_appointments_view_model.dart'; 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'; @@ -45,6 +46,7 @@ class _LandingNavigationState extends State { setState(() => _currentIndex = index); if (_currentIndex == 2) { getIt.get().onTabChanged(0); + getIt.get().getPatientFavouriteDoctors(); context.navigateWithName(AppRoutes.bookAppointmentPage); return; } diff --git a/lib/presentation/home/service_info_page.dart b/lib/presentation/home/service_info_page.dart index 5b6b0f34..75df9437 100644 --- a/lib/presentation/home/service_info_page.dart +++ b/lib/presentation/home/service_info_page.dart @@ -47,14 +47,14 @@ class ServiceInfoPage extends StatelessWidget { ).paddingSymmetrical(24.h, 0.h), SizedBox(height: 32.h), AppCustomChipWidget( - richText: serviceName.toText14(color: AppColors.infoColor, weight: FontWeight.w500), + richText: serviceName.toText14(color: AppColors.infoColor, isBold: true), backgroundColor: AppColors.infoColor.withAlpha(50), textColor: AppColors.infoColor, ).paddingSymmetrical(24.h, 0.h), SizedBox(height: 24.h), serviceHeader.toText28(isBold: true, height: 1.4).paddingSymmetrical(24.h, 0.h), SizedBox(height: 24.h), - serviceDescription.toText14(weight: FontWeight.w500, color: AppColors.greyTextColor).paddingSymmetrical(24.h, 0.h), + serviceDescription.toText14(isBold: true, color: AppColors.greyTextColor).paddingSymmetrical(24.h, 0.h), SizedBox(height: 24.h), ], ), @@ -75,7 +75,7 @@ class ServiceInfoPage extends StatelessWidget { borderColor: AppColors.primaryRedColor, textColor: AppColors.whiteColor, fontSize: 16.f, - fontWeight: FontWeight.w500, + isBold: true, borderRadius: 12.r, padding: EdgeInsets.symmetric(horizontal: 10.w), height: 56.h, diff --git a/lib/presentation/home/widgets/habib_wallet_card.dart b/lib/presentation/home/widgets/habib_wallet_card.dart index a8ba04dd..2a45e2f5 100644 --- a/lib/presentation/home/widgets/habib_wallet_card.dart +++ b/lib/presentation/home/widgets/habib_wallet_card.dart @@ -1,6 +1,8 @@ import 'package:easy_localization/easy_localization.dart'; import 'package:flutter/material.dart'; import 'package:hmg_patient_app_new/core/app_assets.dart'; +import 'package:hmg_patient_app_new/core/app_state.dart'; +import 'package:hmg_patient_app_new/core/dependencies.dart'; import 'package:hmg_patient_app_new/core/utils/size_utils.dart'; import 'package:hmg_patient_app_new/core/utils/utils.dart'; import 'package:hmg_patient_app_new/extensions/string_extensions.dart'; @@ -24,10 +26,10 @@ class HabibWalletCard extends StatelessWidget { Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ - LocaleKeys.myBalance.tr(context: context).toText16(weight: FontWeight.w600), + LocaleKeys.myBalance.tr(context: context).toText16(isBold: true), // Row( // children: [ - // LocaleKeys.viewAllServices.tr(context: context).toText12(color: AppColors.primaryRedColor, weight: FontWeight.w500), + // LocaleKeys.viewAllServices.tr(context: context).toText12(color: AppColors.primaryRedColor, isBold: true), // SizedBox(width: 2.h), // Icon(Icons.arrow_forward_ios, color: AppColors.primaryRedColor, size: 14.h), // ], @@ -55,7 +57,7 @@ class HabibWalletCard extends StatelessWidget { // Row( // mainAxisAlignment: MainAxisAlignment.spaceBetween, // children: [ - LocaleKeys.habibWallet.tr(context: context).toText16(weight: FontWeight.w500, letterSpacing: -0.2), + LocaleKeys.habibWallet.tr(context: context).toText16(isBold: true, letterSpacing: -0.2), // Container( // height: 40.h, // width: 40.h, @@ -76,64 +78,90 @@ class HabibWalletCard extends StatelessWidget { // ], // ), SizedBox(height: 4.h), - Consumer(builder: (context, habibWalletVM, child) { - return Row( - children: [ - Utils.buildSvgWithAssets( - icon: AppAssets.saudi_riyal_icon, - iconColor: AppColors.inputLabelTextColor, - width: 24.h, - height: 24.h, - fit: BoxFit.contain, - ), - SizedBox(width: 8.h), - NumberFormat.decimalPattern() - .format(habibWalletVM.habibWalletAmount) - .toString() - .toText32(isBold: true, isEnglishOnly: true) - .toShimmer2(isShow: habibWalletVM.isWalletAmountLoading, radius: 12.h, width: 80.h, height: 40.h), - ], - ); - }), - Padding( - padding: EdgeInsets.symmetric(horizontal: 50.h), - child: Row( - children: [ - LocaleKeys.viewDetails.tr(context: context).toText12(color: AppColors.primaryRedColor), - SizedBox(width: 2.h), - Icon(Icons.arrow_forward_ios, color: AppColors.primaryRedColor, size: 10.h), - ], - ).onPress(() { - Navigator.of(context).push( - CustomPageRoute( - page: HabibWalletPage(), - ), - ); - }), + Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Consumer(builder: (context, habibWalletVM, child) { + return Row( + mainAxisSize: MainAxisSize.max, + children: [ + Row( + children: [ + Utils.buildSvgWithAssets( + icon: AppAssets.saudi_riyal_icon, + iconColor: AppColors.inputLabelTextColor, + width: 24.h, + height: 24.h, + fit: BoxFit.contain, + ), + SizedBox(width: 8.h), + NumberFormat.decimalPattern() + .format(habibWalletVM.habibWalletAmount) + .toString() + .toText32(isBold: true, isEnglishOnly: true) + .toShimmer2(isShow: habibWalletVM.isWalletAmountLoading, radius: 12.h, width: 80.h, height: 40.h), + ], + ), + Spacer(), + Utils.getPaymentMethods(), + ], + ); + }), + ], ), SizedBox(height: 16.h), Row( - mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ - SizedBox( - width: 200.h, - child: Utils.getPaymentMethods(), + Expanded( + flex: 6, + child: CustomButton( + height: 40.h, + icon: AppAssets.recharge_icon, + iconColor: AppColors.infoColor, + iconSize: 24.h, + backgroundColor: AppColors.infoColor.withAlpha(15), + textColor: AppColors.infoColor, + text: LocaleKeys.recharge.tr(context: context), + borderWidth: 0.w, + fontWeight: FontWeight.w600, + borderColor: AppColors.infoColor.withAlpha(15), + padding: EdgeInsets.fromLTRB(4, 0, 12, 0), + fontSize: 14.f, + onPressed: () { + Navigator.of(context).push(CustomPageRoute(page: RechargeWalletPage())); + }, + ), ), - CustomButton( - height: 40.h, - icon: AppAssets.recharge_icon, - iconSize: 24.h, - backgroundColor: AppColors.infoColor, - textColor: Colors.white, - 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())); - }, + SizedBox(width: 8.h), + Expanded( + flex: 1, + child: Container( + height: (isFoldable || isTablet) ? 50.h : 40.h, + decoration: RoundedRectangleBorder().toSmoothCornerDecoration( + color: AppColors.textColor, + borderRadius: 10.h, + side: BorderSide( + color: AppColors.textColor, + width: 1.2, + ), + ), + child: Transform.flip( + flipX: getIt.get().isArabic(), + child: Utils.buildSvgWithAssets( + icon: AppAssets.forward_arrow_icon, + iconColor: AppColors.whiteColor, + width: 24.w, + height: 24.h, + fit: BoxFit.contain, + ), + ), + ).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 5428e21d..c36f7a90 100644 --- a/lib/presentation/home/widgets/large_service_card.dart +++ b/lib/presentation/home/widgets/large_service_card.dart @@ -8,6 +8,7 @@ import 'package:hmg_patient_app_new/core/utils/size_utils.dart'; import 'package:hmg_patient_app_new/extensions/string_extensions.dart'; import 'package:hmg_patient_app_new/extensions/widget_extensions.dart'; import 'package:hmg_patient_app_new/features/book_appointments/book_appointments_view_model.dart'; +import 'package:hmg_patient_app_new/features/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/book_appointment/book_appointment_page.dart'; import 'package:hmg_patient_app_new/presentation/home/data/service_card_data.dart'; @@ -27,6 +28,7 @@ class LargeServiceCard extends StatelessWidget { final String icon; final String title; final String subtitle; + final bool isPNG; const LargeServiceCard({ super.key, @@ -35,6 +37,7 @@ class LargeServiceCard extends StatelessWidget { this.icon = "", this.title = "", this.subtitle = "", + this.isPNG = false, }); @override @@ -71,7 +74,7 @@ class LargeServiceCard extends StatelessWidget { Row( crossAxisAlignment: CrossAxisAlignment.start, children: [ - Container( + isPNG ? Image.asset(serviceCardData.icon, width: 35.h, height: 35.h) : Container( height: 48.h, width: 48.h, decoration: RoundedRectangleBorder().toSmoothCornerDecoration( @@ -95,7 +98,7 @@ class LargeServiceCard extends StatelessWidget { crossAxisAlignment: CrossAxisAlignment.start, children: [ serviceCardData.title.tr(context: context).toText14(isBold: true, color: AppColors.textColor), - serviceCardData.subtitle.tr(context: context).toText12(fontWeight: FontWeight.w500, color: AppColors.textColorLight, maxLine: 2), + serviceCardData.subtitle.tr(context: context).toText12(isBold: true, color: AppColors.textColorLight, maxLine: 2), ], ), ), @@ -111,7 +114,7 @@ class LargeServiceCard extends StatelessWidget { borderColor: serviceCardData.isBold ? AppColors.successLightColor.withValues(alpha: 0.01) : AppColors.bgRedLightColor, textColor: serviceCardData.isBold ? AppColors.successColor : AppColors.primaryRedColor, fontSize: 14.f, - fontWeight: FontWeight.w500, + fontWeight: FontWeight.w600, borderRadius: 10.r, height: 40.h, ).paddingSymmetrical(16.w, 0.h), @@ -128,6 +131,7 @@ class LargeServiceCard extends StatelessWidget { switch (serviceCardData.serviceName) { case "livecare": { + getIt.get().getPatientFavouriteDoctors(); getIt.get().onTabChanged(1); Navigator.of(getIt.get().navigatorKey.currentContext!).push( CustomPageRoute( @@ -170,6 +174,7 @@ class FadedLargeServiceCard extends StatelessWidget { final String icon; final String title; final String subtitle; + final bool isPNG; const FadedLargeServiceCard({ super.key, @@ -178,6 +183,7 @@ class FadedLargeServiceCard extends StatelessWidget { this.icon = "", this.title = "", this.subtitle = "", + this.isPNG = false, }); @override @@ -223,7 +229,7 @@ class FadedLargeServiceCard extends StatelessWidget { Row( crossAxisAlignment: CrossAxisAlignment.center, children: [ - Container( + isPNG ? Image.asset(serviceCardData.icon, width: 32.h, height: 32.h).circle(100.h) : Container( height: 32.h, width: 32.h, decoration: RoundedRectangleBorder().toSmoothCornerDecoration( @@ -245,11 +251,11 @@ class FadedLargeServiceCard extends StatelessWidget { ), ), SizedBox(width: 12.w), - serviceCardData.title.tr(context: context).toText18(weight: FontWeight.w500, color: AppColors.textColor).expanded, + serviceCardData.title.tr(context: context).toText18(isBold: true, color: AppColors.textColor).expanded, ], ), SizedBox(height: 10.h), - serviceCardData.subtitle.tr(context: context).toText14(weight: FontWeight.w500, color: AppColors.blackBgColor, letterSpacing: 0, maxlines: 2), + serviceCardData.subtitle.tr(context: context).toText14(isBold: true, color: AppColors.blackBgColor, letterSpacing: 0, maxlines: 2), SizedBox(height: 12.h), CustomButton( text: serviceCardData.isBold ? LocaleKeys.visitPharmacyOnline.tr(context: context) : LocaleKeys.bookNow.tr(context: context), @@ -261,7 +267,7 @@ class FadedLargeServiceCard extends StatelessWidget { borderColor: serviceCardData.isBold ? AppColors.successLightColor.withValues(alpha: 0.01) : AppColors.blackBgColor, textColor: serviceCardData.isBold ? AppColors.successColor : AppColors.whiteColor, fontSize: 14.f, - fontWeight: FontWeight.w500, + fontWeight: FontWeight.w600, borderRadius: 10.r, height: 40.h, ), diff --git a/lib/presentation/home/widgets/small_service_card.dart b/lib/presentation/home/widgets/small_service_card.dart index 0c2da8d6..45023c3b 100644 --- a/lib/presentation/home/widgets/small_service_card.dart +++ b/lib/presentation/home/widgets/small_service_card.dart @@ -16,6 +16,7 @@ import 'package:hmg_patient_app_new/presentation/health_calculators_and_converts import 'package:hmg_patient_app_new/presentation/insurance/insurance_home_page.dart'; import 'package:hmg_patient_app_new/presentation/lab/lab_orders_page.dart'; import 'package:hmg_patient_app_new/presentation/medical_file/patient_sickleaves_list_page.dart'; +import 'package:hmg_patient_app_new/presentation/my_invoices/my_invoices_list.dart'; import 'package:hmg_patient_app_new/presentation/parking/paking_page.dart'; import 'package:hmg_patient_app_new/presentation/prescriptions/prescriptions_list_page.dart'; import 'package:hmg_patient_app_new/widgets/common_bottom_sheet.dart'; @@ -68,11 +69,11 @@ class SmallServiceCard extends StatelessWidget { Utils.buildSvgWithAssets(icon: icon, iconColor: resolvedIconColor, width: 32.h, height: 32.h), SizedBox(height: 6.h), if (isFoldable || isTablet) ...[ - title.tr(context: context).toText10(color: resolvedTextColor, weight: FontWeight.w500, isCenter: true, letterSpacing: -0.2, maxlines: 1), - subtitle.tr(context: context).toText10(color: resolvedTextColor, weight: FontWeight.w500, isCenter: true, letterSpacing: -0.2, maxlines: 1), + title.tr(context: context).toText10(color: resolvedTextColor, isBold: true, isCenter: true, letterSpacing: -0.2, maxlines: 1), + subtitle.tr(context: context).toText10(color: resolvedTextColor, isBold: true, isCenter: true, letterSpacing: -0.2, maxlines: 1), ] else ...[ - title.tr(context: context).toText12(color: resolvedTextColor, fontWeight: FontWeight.w500, isCenter: true, letterSpacing: -0.2, maxLine: 1), - subtitle.tr(context: context).toText12(color: resolvedTextColor, fontWeight: FontWeight.w500, isCenter: true, letterSpacing: -0.2, maxLine: 1), + title.tr(context: context).toText12(color: resolvedTextColor, isBold: true, isCenter: true, letterSpacing: -0.2, maxLine: 1), + subtitle.tr(context: context).toText12(color: resolvedTextColor, isBold: true, isCenter: true, letterSpacing: -0.2, maxLine: 1), ] ], ), @@ -92,6 +93,13 @@ class SmallServiceCard extends StatelessWidget { ), ); break; + case "my_invoice": + Navigator.of(context).push( + CustomPageRoute( + page: MyInvoicesList(), + ), + ); + break; case "prescriptions": Navigator.of(context).push( CustomPageRoute( diff --git a/lib/presentation/home/widgets/welcome_widget.dart b/lib/presentation/home/widgets/welcome_widget.dart index b2d4e3a1..bdd71b53 100644 --- a/lib/presentation/home/widgets/welcome_widget.dart +++ b/lib/presentation/home/widgets/welcome_widget.dart @@ -20,13 +20,15 @@ import 'package:smooth_corner/smooth_corner.dart'; class WelcomeWidget extends StatelessWidget { final String name; - final String imageUrl; + final String? imageUrl; + final Widget? imageWidget; final VoidCallback? onTap; const WelcomeWidget({ super.key, required this.name, - required this.imageUrl, + this.imageUrl, + this.imageWidget, this.onTap, }); @@ -50,19 +52,31 @@ class WelcomeWidget extends StatelessWidget { ), ); }), - Image.asset(imageUrl, width: 40, height: 40), + Container( + height: 32.h, + width: 1.5.w, + margin: EdgeInsets.only(right: 10.w,left: 10.w), + decoration: BoxDecoration( + color: AppColors.greyTextColor.withOpacity(.3), + borderRadius: BorderRadius.circular(1.r), + ), + ), + imageWidget ?? + (imageUrl != null + ? Image.asset(imageUrl!, width: 40, height: 40) + : SizedBox(width: 40, height: 40)), Column( crossAxisAlignment: CrossAxisAlignment.start, spacing: 4.h, mainAxisSize: MainAxisSize.min, children: [ - LocaleKeys.welcome.tr(context: context).toText14(color: AppColors.greyTextColor, height: 1, weight: FontWeight.w500), + LocaleKeys.welcome.tr(context: context).toText14(color: AppColors.greyTextColor, height: 1, isBold: true), Row( spacing: 4.h, crossAxisAlignment: CrossAxisAlignment.center, mainAxisSize: MainAxisSize.min, children: [ - Flexible(child: name.toText16(weight: FontWeight.w500, textOverflow: TextOverflow.ellipsis, maxlines: 1, height: 1, isEnglishOnly: true)), + Flexible(child: name.toText16(isBold: true, textOverflow: TextOverflow.ellipsis, maxlines: 1, height: 1, isEnglishOnly: true)), // Icon(Icons.keyboard_arrow_down, size: 20, color: AppColors.greyTextColor), Transform.flip( flipX: getIt.get().isArabic(), 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 7017441c..ff2f9af7 100644 --- a/lib/presentation/home_health_care/hhc_order_detail_page.dart +++ b/lib/presentation/home_health_care/hhc_order_detail_page.dart @@ -102,7 +102,7 @@ class _HhcOrderDetailPageState extends State { child: (isLoading ? "Processing" : order.statusText ?? '') .toText12( color: statusColor, - fontWeight: FontWeight.w600, + isBold: true, ) .toShimmer2(isShow: isLoading), ), @@ -110,7 +110,7 @@ class _HhcOrderDetailPageState extends State { (isLoading ? "Jan 15, 2024" : _formatDate(order.created)) .toText12( color: AppColors.textColorLight, - fontWeight: FontWeight.w500, + isBold: true, ) .toShimmer2(isShow: isLoading), ], @@ -124,7 +124,7 @@ class _HhcOrderDetailPageState extends State { if (!isLoading) ...[ LocaleKeys.requestID.tr(context: context).toText14( color: AppColors.textColorLight, - weight: FontWeight.w500, + weight: FontWeight.w600, ), SizedBox(width: 4.w), ], @@ -161,7 +161,7 @@ class _HhcOrderDetailPageState extends State { // borderColor: AppColors.primaryRedColor, // textColor: AppColors.whiteColor, // fontSize: 14.f, - // fontWeight: FontWeight.w600, + // isBold: true, // borderRadius: 10.r, // height: 44.h, // ).toShimmer2(isShow: isLoading), diff --git a/lib/presentation/home_health_care/hhc_procedures_page.dart b/lib/presentation/home_health_care/hhc_procedures_page.dart index 88766ea0..2ece0598 100644 --- a/lib/presentation/home_health_care/hhc_procedures_page.dart +++ b/lib/presentation/home_health_care/hhc_procedures_page.dart @@ -97,7 +97,7 @@ class _HhcProceduresPageState extends State { children: [ Row( children: [ - LocaleKeys.requestID.tr(context: context).toText14(color: AppColors.textColorLight, weight: FontWeight.w500), + LocaleKeys.requestID.tr(context: context).toText14(color: AppColors.textColorLight, isBold: true), SizedBox(width: 4.w), "${order.iD ?? '-'}".toText16(isBold: true), ], @@ -106,7 +106,7 @@ class _HhcProceduresPageState extends State { if (order.created != null) DateFormat('MMM dd, yyyy').format(DateTime.parse(order.created!)).toText12( color: AppColors.textColorLight, - fontWeight: FontWeight.w500, + isBold: true, ), ], ), @@ -178,7 +178,7 @@ class _HhcProceduresPageState extends State { Expanded( child: serviceName.toText14( color: AppColors.blackColor, - weight: FontWeight.w500, + weight: FontWeight.w600, maxlines: 3, ), ), @@ -215,7 +215,7 @@ class _HhcProceduresPageState extends State { Expanded( child: LocaleKeys.pendingOrderWait.tr(context: context).toText12( color: AppColors.infoBannerTextColor, - fontWeight: FontWeight.w500, + isBold: true, ), ), ], @@ -233,7 +233,7 @@ class _HhcProceduresPageState extends State { borderColor: AppColors.primaryRedColor, textColor: AppColors.whiteColor, fontSize: 14.f, - fontWeight: FontWeight.w600, + isBold: true, borderRadius: 10.r, height: 44.h, ), @@ -321,7 +321,7 @@ class _HhcProceduresPageState extends State { crossAxisAlignment: CrossAxisAlignment.start, children: [ serviceName.toText16( - weight: FontWeight.w500, + weight: FontWeight.w600, color: AppColors.blackColor, maxlines: 2, ), @@ -357,13 +357,12 @@ class _HhcProceduresPageState extends State { Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ - LocaleKeys.selectedServices.tr(context: context).toText12( - color: AppColors.textColorLight, - fontWeight: FontWeight.w600, - ), - "${hmgServicesViewModel.selectedHhcServices.length} service(s) selected".toText14( - isBold: true, - weight: FontWeight.bold, + LocaleKeys.selectedServices.tr(context: context).toText12(color: AppColors.textColorLight, isBold: true), + Row( + children: [ + ("${hmgServicesViewModel.selectedHhcServices.length} ").toText14(isBold: true, weight: FontWeight.bold, isEnglishOnly: true), + LocaleKeys.servicesSelected.tr(context: context).toText14(isBold: true, weight: FontWeight.bold), + ], ), ], ), @@ -508,7 +507,7 @@ class _HhcProceduresPageState extends State { context, noDataText: LocaleKeys.youHaveNoPendingRequests.tr(context: context), ), - ) : LocaleKeys.homeHealthCareText.tr(context: context).toText18(weight: FontWeight.w500).paddingSymmetrical(24.h, 24.h), + ) : LocaleKeys.homeHealthCareText.tr(context: context).toText18(isBold: true).paddingSymmetrical(24.h, 24.h), ], ); } 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 9a9342d7..27245bee 100644 --- a/lib/presentation/home_health_care/hhc_selection_review_page.dart +++ b/lib/presentation/home_health_care/hhc_selection_review_page.dart @@ -172,7 +172,7 @@ class _HhcSelectionReviewPageState extends State { children: [ LocaleKeys.hereIsYourRequestNumber.tr(context: context).toText14( color: AppColors.textColorLight, - weight: FontWeight.w500, + weight: FontWeight.w600, ), SizedBox(width: 4.w), ("$requestId").toText16(isBold: true), diff --git a/lib/presentation/insurance/insurance_approval_details_page.dart b/lib/presentation/insurance/insurance_approval_details_page.dart index e163b649..6589bc4e 100644 --- a/lib/presentation/insurance/insurance_approval_details_page.dart +++ b/lib/presentation/insurance/insurance_approval_details_page.dart @@ -145,7 +145,7 @@ class InsuranceApprovalDetailsPage extends StatelessWidget { child: insuranceApprovalResponseModel.apporvalDetails != null ? Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ - "Approval Details".toText16(isBold: true), + LocaleKeys.approvalDetails.toText16(isBold: true), ListView.separated( padding: EdgeInsets.only(top: 16.h), shrinkWrap: true, @@ -170,7 +170,7 @@ class InsuranceApprovalDetailsPage extends StatelessWidget { LocaleKeys.procedureName.tr(context: context).toText14(isBold: true), Expanded( child: (insuranceApprovalResponseModel.apporvalDetails![index]!.procedureName ?? "") - .toText12(fontWeight: FontWeight.w500, color: AppColors.greyTextColor, textAlignment: TextAlign.end, maxLine: 2), + .toText12(isBold: true, color: AppColors.greyTextColor, textAlignment: TextAlign.end, maxLine: 2), ), ], ), @@ -179,7 +179,7 @@ class InsuranceApprovalDetailsPage extends StatelessWidget { mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ "${LocaleKeys.status.tr(context: context)}: ".toText14(isBold: true), - insuranceApprovalResponseModel.apporvalDetails![index]!.status!.toText12(fontWeight: FontWeight.w500, color: AppColors.greyTextColor), + insuranceApprovalResponseModel.apporvalDetails![index]!.status!.toText12(isBold: true, color: AppColors.greyTextColor), ], ), SizedBox(height: 8.h), @@ -187,7 +187,7 @@ class InsuranceApprovalDetailsPage extends StatelessWidget { mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ "${LocaleKeys.usageStatus.tr(context: context)}: ".toText14(isBold: true), - (insuranceApprovalResponseModel.apporvalDetails![index]!.isInvoicedDesc ?? "").toText12(fontWeight: FontWeight.w500, color: AppColors.greyTextColor), + (insuranceApprovalResponseModel.apporvalDetails![index]!.isInvoicedDesc ?? "").toText12(isBold: true, color: AppColors.greyTextColor), ], ), ], diff --git a/lib/presentation/insurance/insurance_home_page.dart b/lib/presentation/insurance/insurance_home_page.dart index 1ac176e1..985de895 100644 --- a/lib/presentation/insurance/insurance_home_page.dart +++ b/lib/presentation/insurance/insurance_home_page.dart @@ -105,7 +105,7 @@ class _InsuranceHomePageState extends State { borderColor: AppColors.bgGreenColor.withOpacity(0.0), textColor: AppColors.bgGreenColor, fontSize: 14, - fontWeight: FontWeight.w500, + isBold: true, borderRadius: 12, padding: EdgeInsets.fromLTRB(10, 0, 10, 0), height: 40.h, diff --git a/lib/presentation/insurance/widgets/insurance_approval_card.dart b/lib/presentation/insurance/widgets/insurance_approval_card.dart index faedb58d..b50e199f 100644 --- a/lib/presentation/insurance/widgets/insurance_approval_card.dart +++ b/lib/presentation/insurance/widgets/insurance_approval_card.dart @@ -136,7 +136,7 @@ class InsuranceApprovalCard extends StatelessWidget { borderColor: AppColors.secondaryLightRedColor, textColor: AppColors.primaryRedColor, fontSize: (isFoldable || isTablet) ? 12.f : 14.f, - fontWeight: FontWeight.w500, + fontWeight: FontWeight.w600, borderRadius: 12.r, padding: EdgeInsets.symmetric(horizontal: 10.w), height: isTablet || isFoldable ? 46.h : 40.h, diff --git a/lib/presentation/insurance/widgets/insurance_history.dart b/lib/presentation/insurance/widgets/insurance_history.dart index 7219a34e..0f5bde1e 100644 --- a/lib/presentation/insurance/widgets/insurance_history.dart +++ b/lib/presentation/insurance/widgets/insurance_history.dart @@ -73,7 +73,7 @@ class InsuranceHistory extends StatelessWidget { borderColor: AppColors.primaryRedColor.withOpacity(0.0), textColor: AppColors.primaryRedColor, fontSize: 10, - fontWeight: FontWeight.w500, + isBold: true, borderRadius: 8, padding: EdgeInsets.fromLTRB(10, 0, 10, 0), height: 30.h, @@ -81,7 +81,7 @@ class InsuranceHistory extends StatelessWidget { ], ), SizedBox(height: 8.h), - // "Haroon Amjad".toText16(weight: FontWeight.w600), + // "Haroon Amjad".toText16(isBold: true), SizedBox(height: 8.h), Row( children: [ diff --git a/lib/presentation/insurance/widgets/insurance_update_details_card.dart b/lib/presentation/insurance/widgets/insurance_update_details_card.dart index 51b9e05d..f7fd4402 100644 --- a/lib/presentation/insurance/widgets/insurance_update_details_card.dart +++ b/lib/presentation/insurance/widgets/insurance_update_details_card.dart @@ -58,7 +58,7 @@ class PatientInsuranceCardUpdateCard extends StatelessWidget { child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ - insuranceViewModel.patientInsuranceUpdateResponseModel!.memberName!.toText16(weight: FontWeight.w600), + insuranceViewModel.patientInsuranceUpdateResponseModel!.memberName!.toText16(isBold: true), LocaleKeys.policyNumberInsurancePage.tr(namedArgs: {'number': insuranceViewModel.patientInsuranceUpdateResponseModel!.policyNumber.toString()}).toText12(isBold: true, color: AppColors.lightGrayColor, isEnglishOnly: true), SizedBox(height: 8.h), Row( @@ -168,7 +168,7 @@ class PatientInsuranceCardUpdateCard extends StatelessWidget { borderColor: AppColors.successColor.withOpacity(0.01), textColor: AppColors.whiteColor, fontSize: 16.f, - fontWeight: FontWeight.w500, + isBold: true, borderRadius: 12.r, padding: EdgeInsets.fromLTRB(10, 0, 10, 0), ).paddingSymmetrical(24.w, 0.h), diff --git a/lib/presentation/insurance/widgets/patient_insurance_card.dart b/lib/presentation/insurance/widgets/patient_insurance_card.dart index 16cbac20..3734bd95 100644 --- a/lib/presentation/insurance/widgets/patient_insurance_card.dart +++ b/lib/presentation/insurance/widgets/patient_insurance_card.dart @@ -135,10 +135,10 @@ class PatientInsuranceCard extends StatelessWidget { ], ), SizedBox(height: 12.h), - insuranceCardDetailsModel.groupName!.toText12(isBold: true), + insuranceCardDetailsModel.groupName!.toText12(isBold: true, isEnglishOnly: true), Row( children: [ - (insuranceCardDetailsModel.companyName!.length > 40 ? "${insuranceCardDetailsModel.companyName!.substring(0, 40)}..." : insuranceCardDetailsModel.companyName!).toText12(isBold: true), + (insuranceCardDetailsModel.companyName!.length > 40 ? "${insuranceCardDetailsModel.companyName!.substring(0, 40)}..." : insuranceCardDetailsModel.companyName!).toText12(isBold: true, isEnglishOnly: true), SizedBox( width: 6.h, ), @@ -149,7 +149,7 @@ class PatientInsuranceCard extends StatelessWidget { borderRadius: 50.r, ), child: ((insuranceCardDetailsModel.subCategoryDesc ?? "").length > 10 ? (insuranceCardDetailsModel.subCategoryDesc ?? "").substring(0, 10) : (insuranceCardDetailsModel.subCategoryDesc ?? "")) - .toText8(isBold: true, color: AppColors.whiteColor), + .toText8(isBold: true, color: AppColors.whiteColor, isEnglishOnly: true), ), ], ), @@ -192,7 +192,7 @@ class PatientInsuranceCard extends StatelessWidget { borderColor: AppColors.warningColorYellow.withOpacity(0.0), textColor: AppColors.warningColorYellow, fontSize: 14, - fontWeight: FontWeight.w500, + isBold: true, borderRadius: 12, padding: EdgeInsets.fromLTRB(10, 0, 10, 0), height: 40.h, diff --git a/lib/presentation/lab/lab_order_by_test.dart b/lib/presentation/lab/lab_order_by_test.dart index 5cb94320..7740a596 100644 --- a/lib/presentation/lab/lab_order_by_test.dart +++ b/lib/presentation/lab/lab_order_by_test.dart @@ -39,7 +39,7 @@ class LabOrderByTest extends StatelessWidget { children: [ '${tests!.description}'.toText16(isBold: true, isEnglishOnly: true), SizedBox(height: 4.h), - (appState.isArabic() ? tests!.testDescriptionAr : tests!.testDescriptionEn)!.toText12(fontWeight: FontWeight.w500), + (appState.isArabic() ? tests!.testDescriptionAr : tests!.testDescriptionEn)!.toText12(isBold: true), SizedBox(height: 8.h), Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, @@ -47,9 +47,9 @@ class LabOrderByTest extends StatelessWidget { AppCustomChipWidget( richText: Row( children: [ - "${LocaleKeys.lastTested.tr(context: context)}: ".toText12(fontWeight: FontWeight.w500), + "${LocaleKeys.lastTested.tr(context: context)}: ".toText12(isBold: true), Directionality( - textDirection: ui.TextDirection.ltr,child: DateUtil.formatDateToDate(DateUtil.convertStringToDate(tests!.createdOn), false).toText12(fontWeight: FontWeight.w500, isEnglishOnly: true)) + textDirection: ui.TextDirection.ltr,child: DateUtil.formatDateToDate(DateUtil.convertStringToDate(tests!.createdOn), false).toText12(isBold: true, isEnglishOnly: true)) ], ), backgroundColor: AppColors.greyLightColor, @@ -74,7 +74,7 @@ class LabOrderByTest extends StatelessWidget { borderColor: AppColors.secondaryLightRedColor, textColor: AppColors.primaryRedColor, fontSize: 14.f, - fontWeight: FontWeight.w500, + fontWeight: FontWeight.w600, borderRadius: 12, padding: EdgeInsets.fromLTRB(10, 0, 10, 0), height: 40.h, diff --git a/lib/presentation/lab/lab_orders_page.dart b/lib/presentation/lab/lab_orders_page.dart index b3ef6cde..b1495fed 100644 --- a/lib/presentation/lab/lab_orders_page.dart +++ b/lib/presentation/lab/lab_orders_page.dart @@ -1 +1 @@ -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'; import 'dart:ui' as ui; 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 { if (labProvider.isLabOrdersLoading) { return; } else { String? value = await Navigator.of(context).push( CustomPageRoute( page: SearchLabResultsContent(labSuggestionsList: labProvider.labSuggestions), fullScreenDialog: true, direction: AxisDirection.down, ), ); if (value != null) { selectedFilterText = value; labProvider.filterLabReports(value); } } }, child: Consumer( builder: (context, labViewModel, 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: () { // labViewModel.setIsSortByClinic(true); // }, // backgroundColor: labViewModel.isSortByClinic ? AppColors.bgRedLightColor : AppColors.whiteColor, // borderColor: labViewModel.isSortByClinic ? AppColors.primaryRedColor : AppColors.textColor.withValues(alpha: 0.2), // textColor: labViewModel.isSortByClinic ? AppColors.primaryRedColor : AppColors.blackColor, // fontSize: 12.f, // 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: () { // labViewModel.setIsSortByClinic(false); // }, // backgroundColor: labViewModel.isSortByClinic ? AppColors.whiteColor : AppColors.bgRedLightColor, // borderColor: labViewModel.isSortByClinic ? AppColors.textColor.withValues(alpha: 0.2) : AppColors.primaryRedColor, // textColor: labViewModel.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 ? Column( children: [ AppCustomChipWidget( labelText: selectedFilterText!, backgroundColor: AppColors.alertColor, textColor: AppColors.whiteColor, deleteIcon: AppAssets.close_bottom_sheet_icon, deleteIconColor: AppColors.whiteColor, deleteIconHasColor: true, isEnglishOnly: true, onDeleteTap: () { selectedFilterText = ""; labProvider.filterLabReports(""); }, ), SizedBox(height: 8.h), ], ) : SizedBox(), activeIndex == 0 ? // By Visit - show grouped view when available labViewModel.isLabOrdersLoading ? ListView.builder( shrinkWrap: true, physics: AlwaysScrollableScrollPhysics(), padding: EdgeInsets.zero, itemCount: 5, itemBuilder: (context, index) => LabResultItemView( onTap: () {}, labOrder: null, index: index, isLoading: true, ), ) : (labViewModel.patientLabOrders.isNotEmpty ? ListView.builder( shrinkWrap: true, physics: NeverScrollableScrollPhysics(), padding: EdgeInsets.zero, itemCount: labViewModel.filteredLabOrders.length, itemBuilder: (context, index) { final group = labViewModel.filteredLabOrders[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: [ Wrap( direction: Axis.horizontal, spacing: 4.h, runSpacing: 4.h, children: [ AppCustomChipWidget( labelText: "${group.testDetails!.length} ${LocaleKeys.tests.tr(context: context)}", backgroundColor: AppColors.successColor.withOpacity(0.1), textColor: AppColors.successColor, ), AppCustomChipWidget( labelText: "${_appState.isArabic() ? group.isInOutPatientDescriptionN : group.isInOutPatientDescription}", backgroundColor: AppColors.warningColorYellow.withOpacity(0.1), textColor: AppColors.warningColorYellow, ) ], ), Icon(isExpanded ? Icons.expand_less : Icons.expand_more), ], ), SizedBox(height: 8.h), Row( mainAxisSize: MainAxisSize.min, children: [ Image.network( group.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: (group.doctorName ?? group.doctorNameEnglish ?? "").toString().toText14(weight: FontWeight.w500)), ], ), SizedBox(height: 8.h), Wrap( direction: Axis.horizontal, spacing: 4.h, runSpacing: 4.h, children: [ Directionality( textDirection: ui.TextDirection.ltr, child: AppCustomChipWidget( labelText: DateUtil.formatDateToDate(DateUtil.convertStringToDate(group.orderDate ?? ""), false), isEnglishOnly: true, )), AppCustomChipWidget( labelText: (group.projectName ?? ""), ), AppCustomChipWidget( labelText: (group.clinicDescription ?? ""), ), ], ), ], ), ), 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( children: [ ListView.separated( shrinkWrap: true, physics: NeverScrollableScrollPhysics(), padding: EdgeInsets.zero, itemBuilder: (cxt, index) { PatientLabOrdersResponseModel order = group; return Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ "• ${order.testDetails![index].description!}".toText14(weight: FontWeight.w500), SizedBox(height: 4.h), order.testDetails![index].testDescriptionEn!.toText12(fontWeight: FontWeight.w500, color: AppColors.textColorLight), // 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!}"), isEnglishOnly: true, // ), // Directionality( // textDirection: ui.TextDirection.ltr, // child: AppCustomChipWidget( // labelText: DateUtil.formatDateToDate(DateUtil.convertStringToDate(order.orderDate ?? ""), false), // isEnglishOnly: true, // )), // AppCustomChipWidget( // labelText: labViewModel.isSortByClinic ? (order.projectName ?? "") : (order.clinicDescription ?? ""), // ), // ], // ), // // 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: () { // labViewModel.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.testDetails!.length > 3 ? 3 : group.testDetails!.length), SizedBox(height: 16.h), CustomButton( text: "${LocaleKeys.viewResults.tr()} (${group.testDetails!.length})", onPressed: () { labProvider.currentlySelectedPatientOrder = group; labProvider.getPatientLabResultByHospital(group); labProvider.getPatientSpecialResult(group); Navigator.of(context).push( CustomPageRoute( page: LabResultByClinic(labOrder: group), ), ); }, backgroundColor: AppColors.infoColor.withAlpha(20), borderColor: AppColors.infoColor.withAlpha(0), textColor: AppColors.infoColor, fontSize: (isFoldable || isTablet) ? 12.f : 14.f, fontWeight: FontWeight.w500, borderRadius: 12.r, padding: EdgeInsets.fromLTRB(10.w, 0, 10.w, 0), height: 40.h, iconSize: 14.h, icon: AppAssets.view_report_icon, iconColor: AppColors.infoColor, ), SizedBox(height: 16.h), ], ), ) : SizedBox.shrink(), ), ], ), ), ), ), )); }, ) : Utils.getNoDataWidget(context, noDataText: LocaleKeys.noLabResults.tr(context: context))) : // By Test or other tabs keep existing behavior (labViewModel.isLabOrdersLoading) ? Column( children: List.generate( 5, (index) => LabResultItemView( onTap: () {}, labOrder: null, index: index, isLoading: true, )), ) : AlphabeticScroll( alpahbetsAvailable: labViewModel.filteredIndexedCharacterForUniqueTest, details: labViewModel.filteredUniqueTestsList, labViewModel: labViewModel, 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'; import 'dart:ui' as ui; 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 { if (labProvider.isLabOrdersLoading) { return; } else { String? value = await Navigator.of(context).push( CustomPageRoute( page: SearchLabResultsContent(labSuggestionsList: labProvider.labSuggestions), fullScreenDialog: true, direction: AxisDirection.down, ), ); if (value != null) { selectedFilterText = value; labProvider.filterLabReports(value); } } }, child: Consumer( builder: (context, labViewModel, 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: () { // labViewModel.setIsSortByClinic(true); // }, // backgroundColor: labViewModel.isSortByClinic ? AppColors.bgRedLightColor : AppColors.whiteColor, // borderColor: labViewModel.isSortByClinic ? AppColors.primaryRedColor : AppColors.textColor.withValues(alpha: 0.2), // textColor: labViewModel.isSortByClinic ? AppColors.primaryRedColor : AppColors.blackColor, // fontSize: 12.f, // isBold: true, // borderRadius: 10, // padding: EdgeInsets.fromLTRB(10, 0, 10, 0), // height: 40.h, // ), // SizedBox(width: 8.h), // CustomButton( // text: LocaleKeys.byHospital.tr(context: context), // onPressed: () { // labViewModel.setIsSortByClinic(false); // }, // backgroundColor: labViewModel.isSortByClinic ? AppColors.whiteColor : AppColors.bgRedLightColor, // borderColor: labViewModel.isSortByClinic ? AppColors.textColor.withValues(alpha: 0.2) : AppColors.primaryRedColor, // textColor: labViewModel.isSortByClinic ? AppColors.blackColor : AppColors.primaryRedColor, // fontSize: 12, // isBold: true, // borderRadius: 10, // padding: EdgeInsets.fromLTRB(10, 0, 10, 0), // height: 40.h, // ), ], ), ), SizedBox(height: 8.h), selectedFilterText!.isNotEmpty ? Column( children: [ AppCustomChipWidget( labelText: selectedFilterText!, backgroundColor: AppColors.alertColor, textColor: AppColors.whiteColor, deleteIcon: AppAssets.close_bottom_sheet_icon, deleteIconColor: AppColors.whiteColor, deleteIconHasColor: true, isEnglishOnly: true, onDeleteTap: () { selectedFilterText = ""; labProvider.filterLabReports(""); }, ), SizedBox(height: 8.h), ], ) : SizedBox(), activeIndex == 0 ? // By Visit - show grouped view when available labViewModel.isLabOrdersLoading ? ListView.builder( shrinkWrap: true, physics: AlwaysScrollableScrollPhysics(), padding: EdgeInsets.zero, itemCount: 5, itemBuilder: (context, index) => LabResultItemView( onTap: () {}, labOrder: null, index: index, isLoading: true, ), ) : (labViewModel.patientLabOrders.isNotEmpty ? ListView.builder( shrinkWrap: true, physics: NeverScrollableScrollPhysics(), padding: EdgeInsets.zero, itemCount: labViewModel.filteredLabOrders.length, itemBuilder: (context, index) { final group = labViewModel.filteredLabOrders[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: [ Wrap( direction: Axis.horizontal, spacing: 4.h, runSpacing: 4.h, children: [ AppCustomChipWidget( richText: Row( children: [ "${group.testDetails!.length} ".toText10(color: AppColors.successColor, isEnglishOnly: true, weight: FontWeight.w600), LocaleKeys.tests.tr(context: context).toText10(color: AppColors.successColor, weight: FontWeight.w600), ], ), labelText: "${group.testDetails!.length} ${LocaleKeys.tests.tr(context: context)}", backgroundColor: AppColors.successColor.withOpacity(0.1), textColor: AppColors.successColor, ), AppCustomChipWidget( labelText: "${_appState.isArabic() ? group.isInOutPatientDescriptionN : group.isInOutPatientDescription}", backgroundColor: AppColors.warningColorYellow.withOpacity(0.1), textColor: AppColors.warningColorYellow, ) ], ), Icon(isExpanded ? Icons.expand_less : Icons.expand_more), ], ), SizedBox(height: 8.h), Row( mainAxisSize: MainAxisSize.min, children: [ Image.network( group.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: (group.doctorName ?? group.doctorNameEnglish ?? "").toString().toText14(isBold: true)), ], ), SizedBox(height: 8.h), Wrap( direction: Axis.horizontal, spacing: 4.h, runSpacing: 4.h, children: [ Directionality( textDirection: ui.TextDirection.ltr, child: AppCustomChipWidget( labelText: DateUtil.formatDateToDate(DateUtil.convertStringToDate(group.orderDate ?? ""), false), isEnglishOnly: true, )), AppCustomChipWidget( labelText: (group.projectName ?? ""), ), AppCustomChipWidget( labelText: (group.clinicDescription ?? ""), ), ], ), ], ), ), 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( children: [ ListView.separated( shrinkWrap: true, physics: NeverScrollableScrollPhysics(), padding: EdgeInsets.zero, itemBuilder: (cxt, index) { PatientLabOrdersResponseModel order = group; return Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ "• ${order.testDetails![index].description!}".toText14(isBold: true, isEnglishOnly: true), SizedBox(height: 4.h), order.testDetails![index].testDescriptionEn!.toText12(isBold: true, color: AppColors.textColorLight, isEnglishOnly: true), // 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(isBold: true)), // ], // ), // SizedBox(height: 8.h), // Wrap( // direction: Axis.horizontal, // spacing: 4.h, // runSpacing: 4.h, // children: [ // AppCustomChipWidget( // labelText: ("${LocaleKeys.orderNo.tr(context: context)}: ${order.orderNo!}"), isEnglishOnly: true, // ), // Directionality( // textDirection: ui.TextDirection.ltr, // child: AppCustomChipWidget( // labelText: DateUtil.formatDateToDate(DateUtil.convertStringToDate(order.orderDate ?? ""), false), // isEnglishOnly: true, // )), // AppCustomChipWidget( // labelText: labViewModel.isSortByClinic ? (order.projectName ?? "") : (order.clinicDescription ?? ""), // ), // ], // ), // // Row( // // children: [ // // CustomButton( // // text: ("Order No: ".needTranslation + order.orderNo!), // // onPressed: () {}, // // backgroundColor: AppColors.greyColor, // // borderColor: AppColors.greyColor, // // textColor: AppColors.blackColor, // // fontSize: 10, // // isBold: true, // // 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, // // isBold: true, // // 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, // // isBold: true, // // 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: () { // labViewModel.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, // isBold: true, // 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.testDetails!.length > 3 ? 3 : group.testDetails!.length), SizedBox(height: 16.h), CustomButton( text: "${LocaleKeys.viewResults.tr()} (${group.testDetails!.length})", onPressed: () { labProvider.currentlySelectedPatientOrder = group; labProvider.getPatientLabResultByHospital(group); labProvider.getPatientSpecialResult(group); Navigator.of(context).push( CustomPageRoute( page: LabResultByClinic(labOrder: group), ), ); }, backgroundColor: AppColors.infoColor.withAlpha(20), borderColor: AppColors.infoColor.withAlpha(0), textColor: AppColors.infoColor, fontSize: (isFoldable || isTablet) ? 12.f : 14.f, isBold: true, borderRadius: 12.r, padding: EdgeInsets.fromLTRB(10.w, 0, 10.w, 0), height: 40.h, iconSize: 14.h, icon: AppAssets.view_report_icon, iconColor: AppColors.infoColor, ), SizedBox(height: 16.h), ], ), ) : SizedBox.shrink(), ), ], ), ), ), ), )); }, ) : Utils.getNoDataWidget(context, noDataText: LocaleKeys.noLabResults.tr(context: context))) : // By Test or other tabs keep existing behavior (labViewModel.isLabOrdersLoading) ? Column( children: List.generate( 5, (index) => LabResultItemView( onTap: () {}, labOrder: null, index: index, isLoading: true, )), ) : AlphabeticScroll( alpahbetsAvailable: labViewModel.filteredIndexedCharacterForUniqueTest, details: labViewModel.filteredUniqueTestsList, labViewModel: labViewModel, 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_item_view.dart b/lib/presentation/lab/lab_result_item_view.dart index db80918b..554bd8f2 100644 --- a/lib/presentation/lab/lab_result_item_view.dart +++ b/lib/presentation/lab/lab_result_item_view.dart @@ -118,7 +118,7 @@ class LabResultItemView extends StatelessWidget { // ...labOrder!.testDetails!.map((detail) { // return Padding( // padding: EdgeInsets.only(bottom: 8.h), - // child: '● ${detail.description}'.toText14(weight: FontWeight.w500), + // child: '● ${detail.description}'.toText14(isBold: true), // ); // }).toList(), // SizedBox(height: 16.h), diff --git a/lib/presentation/lab/lab_result_via_clinic/LabResultByClinic.dart b/lib/presentation/lab/lab_result_via_clinic/LabResultByClinic.dart index c883c863..62910f47 100644 --- a/lib/presentation/lab/lab_result_via_clinic/LabResultByClinic.dart +++ b/lib/presentation/lab/lab_result_via_clinic/LabResultByClinic.dart @@ -136,7 +136,7 @@ class LabResultByClinic extends StatelessWidget { ).paddingAll(24.h), )), ), - Container( + Utils.havePrivilege(115) ? Container( decoration: RoundedRectangleBorder().toSmoothCornerDecoration( color: AppColors.whiteColor, borderRadius: 24.h, @@ -168,21 +168,22 @@ class LabResultByClinic extends StatelessWidget { ], ), ).paddingSymmetrical(24.h, 24.h).onPress(() async { - final _dialogService = getIt.get(); - await _dialogService.showCommonBottomSheetWithoutH( + final dialogService = getIt.get(); + final appState = 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)); + labViewModel.getAiOverviewLabOrders(langId: appState.getLanguageID(), labOrder: labOrder, loadingText: LocaleKeys.loadingAIAnalysis.tr(context: context)); }, onCancelPressed: () { context.pop(); }); }), - ), + ) : SizedBox.shrink(), ], ), ); diff --git a/lib/presentation/lab/lab_result_via_clinic/lab_ai_analysis_detailed_page.dart b/lib/presentation/lab/lab_result_via_clinic/lab_ai_analysis_detailed_page.dart index 2a4fd66d..4debd41b 100644 --- a/lib/presentation/lab/lab_result_via_clinic/lab_ai_analysis_detailed_page.dart +++ b/lib/presentation/lab/lab_result_via_clinic/lab_ai_analysis_detailed_page.dart @@ -30,6 +30,7 @@ class _LabAiAnalysisDetailedPageState extends State { Widget build(BuildContext context) { labProvider = Provider.of(context, listen: false); return CollapsingListView( + isClose: true, title: LocaleKeys.aiAnalysis.tr(context: context), child: Consumer( builder: (context, model, child) { @@ -44,11 +45,14 @@ class _LabAiAnalysisDetailedPageState extends State { if (analysis.summary != null && analysis.summary!.isNotEmpty) { expandableItems.add( ExpandableListItem( + backgroundColor: Colors.transparent, + expandedBackgroundColor:Colors.transparent , 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), @@ -63,6 +67,9 @@ class _LabAiAnalysisDetailedPageState extends State { if (analysis.aiGeneratedAttributes != null && analysis.aiGeneratedAttributes!.isNotEmpty) { expandableItems.add( ExpandableListItem( + + backgroundColor: Colors.transparent, + expandedBackgroundColor:Colors.transparent , title: LocaleKeys.labResults.tr(context: context).toText18(weight: FontWeight.w700, color: AppColors.blackColor), children: [ SizedBox(height: 10.h), @@ -83,6 +90,8 @@ class _LabAiAnalysisDetailedPageState extends State { if (analysis.aiPredictions != null && analysis.aiPredictions!.isNotEmpty) { expandableItems.add( ExpandableListItem( + backgroundColor: Colors.transparent, + expandedBackgroundColor:Colors.transparent , title: LocaleKeys.prediction.tr(context: context).toText18(weight: FontWeight.w700, color: AppColors.blackColor), children: [ SizedBox(height: 10.h), @@ -104,6 +113,8 @@ class _LabAiAnalysisDetailedPageState extends State { if (analysis.aiRecommendations != null && analysis.aiRecommendations!.isNotEmpty) { expandableItems.add( ExpandableListItem( + backgroundColor: Colors.transparent, + expandedBackgroundColor:Colors.transparent , title: LocaleKeys.suggestions.tr(context: context).toText18(weight: FontWeight.w700, color: AppColors.blackColor), children: [ SizedBox(height: 10.h), @@ -122,7 +133,7 @@ class _LabAiAnalysisDetailedPageState extends State { 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)), + child: rec.priority!.toText12(isBold: true, color: _getPriorityTextColor(rec.priority)), ), SizedBox(height: 8.h), ], @@ -151,6 +162,8 @@ class _LabAiAnalysisDetailedPageState extends State { if (analysis.riskFactors != null && analysis.riskFactors!.isNotEmpty) { expandableItems.add( ExpandableListItem( + backgroundColor: Colors.transparent, + expandedBackgroundColor:Colors.transparent , title: LocaleKeys.riskFactors.tr(context: context).toText18(weight: FontWeight.w700, color: AppColors.blackColor), children: [ SizedBox(height: 10.h), @@ -172,7 +185,7 @@ class _LabAiAnalysisDetailedPageState extends State { 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), + child: (risk.risk ?? "").toText10(isBold: true, color: AppColors.blackColor), ), ), ), @@ -220,6 +233,8 @@ class _LabAiAnalysisDetailedPageState extends State { if (analysis.prevention != null && analysis.prevention!.isNotEmpty) { expandableItems.add( ExpandableListItem( + backgroundColor: Colors.transparent, + expandedBackgroundColor:Colors.transparent , title: LocaleKeys.prevention.tr(context: context).toText18(weight: FontWeight.w700, color: AppColors.blackColor), children: [ SizedBox(height: 10.h), @@ -307,10 +322,11 @@ class _LabAiAnalysisDetailedPageState extends State { else CustomExpandableList( expansionMode: ExpansionMode.exactlyOne, - dividerColor: Color(0xff2b353e1a), + dividerColor: AppColors.dividerColor, 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), @@ -335,7 +351,7 @@ class _LabAiAnalysisDetailedPageState extends State { if (priority == null) return AppColors.primaryRedColor; switch (priority.toLowerCase()) { case 'high': - return Color(0xFFED1C2B).withOpacity(.10); + return AppColors.secondaryLightRedColor; case 'medium' || 'moderate': return AppColors.lightGreenButtonColor; case 'low': @@ -415,7 +431,7 @@ class _LabAiAnalysisDetailedPageState extends State { Widget _buildCombinedLabCard(AiGeneratedAttribute attr) { return Container( - decoration: RoundedRectangleBorder().toSmoothCornerDecoration(color: AppColors.whiteColor, borderRadius: 24.h, hasShadow: true), + decoration: RoundedRectangleBorder().toSmoothCornerDecoration(color: AppColors.whiteColor, borderRadius: 24.h, hasShadow: false), padding: EdgeInsets.all(16.h), child: Column( crossAxisAlignment: CrossAxisAlignment.start, @@ -434,7 +450,7 @@ class _LabAiAnalysisDetailedPageState extends State { 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), + LocaleKeys.contextDependent.tr().toText12(isBold: true, color: AppColors.textColor), ], ), ), @@ -449,7 +465,7 @@ class _LabAiAnalysisDetailedPageState extends State { decoration: BoxDecoration(color: _getResultFlagColor(attr.calculatedResultFlag), borderRadius: BorderRadius.circular(4.r)), child: Row( children: [ - (attr.calculatedResultFlag ?? "").toText12(fontWeight: FontWeight.w600, color: _getResultTextColor(attr.calculatedResultFlag)), + (attr.calculatedResultFlag ?? "").toText12(isBold: true, color: _getResultTextColor(attr.calculatedResultFlag)), ], ), ), @@ -470,7 +486,7 @@ class _LabAiAnalysisDetailedPageState extends State { // children: [ // Consumer( // builder: (context, labViewModel, child) { - // return labViewModel.getSeverityText(attr.calculatedResultFlag ?? "").tr(context: context).toText10(weight: FontWeight.w500, color: AppColors.greyTextColor); + // return labViewModel.getSeverityText(attr.calculatedResultFlag ?? "").tr(context: context).toText10(isBold: true, color: AppColors.greyTextColor); // }, // ), // Consumer( @@ -502,13 +518,17 @@ class _LabAiAnalysisDetailedPageState extends State { 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)), + child: ("${LocaleKeys.calculatedValue.tr()}: ${attr.resultValue!}").toText12( + isBold: true, + color: _getResultTextColor(attr.calculatedResultFlag), + isEnglishOnly: true + ), ), 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), + child: ("${LocaleKeys.unit.tr()}: ${attr.uom!}").toText12(isBold: true, color: AppColors.textColor), ), ], ), @@ -519,7 +539,7 @@ class _LabAiAnalysisDetailedPageState extends State { 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), + child: ("${LocaleKeys.referenceRange.tr(context: context)}: ${attr.referanceRange!.trim()}").toText12(isBold: true, color: AppColors.textColor), ), if (attr.calculationFormula != null && attr.calculationFormula!.isNotEmpty) ...[ SizedBox(height: 8.h), @@ -533,8 +553,8 @@ class _LabAiAnalysisDetailedPageState extends State { crossAxisAlignment: CrossAxisAlignment.start, mainAxisAlignment: MainAxisAlignment.center, children: [ - LocaleKeys.calculationFormula.tr().toText16(weight: FontWeight.w600), - (attr.calculationFormula ?? "").toText12(fontWeight: FontWeight.w500, color: AppColors.textColorLight), + LocaleKeys.calculationFormula.tr().toText16(isBold: true), + (attr.calculationFormula ?? "").toText12(isBold: true, color: AppColors.textColorLight, isEnglishOnly: true), ], ), ), 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 1c7d0400..7ae38db1 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 @@ -40,57 +40,47 @@ class LabOrderResultItem extends StatelessWidget { // ...labOrder!.testDetails!.map((detail) { Padding( padding: EdgeInsets.only(bottom: 8.h), - child: '${tests!.description}'.toText14(weight: FontWeight.w500), + child: '${tests!.description}'.toText14(isBold: true, isEnglishOnly: true), ), - // (tests!.packageShortDescription ?? "").toText12(fontWeight: FontWeight.w500, color: AppColors.textColorLight), + // (tests!.packageShortDescription ?? "").toText12(isBold: true, color: AppColors.textColorLight), (((tests!.testShortDescription != null && tests!.testShortDescription!.isNotEmpty) ? tests!.testShortDescription : tests!.packageShortDescription) ?? "") - .toText12(fontWeight: FontWeight.w500, color: AppColors.textColorLight), + .toText12(isBold: true, color: AppColors.textColorLight), SizedBox(height: 12.h), Directionality( textDirection: ui.TextDirection.ltr, - child: Row( - mainAxisSize: MainAxisSize.max, + child: Column( + mainAxisSize: MainAxisSize.min, + crossAxisAlignment: CrossAxisAlignment.start, children: [ - Flexible( + 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: -1, + ), + ), + // SizedBox( + // height: 2.h, + // ), + Visibility( + visible: tests?.referanceRange != null, child: Text( - "${tests?.resultValue} ${tests?.uOM}", + "(${LocaleKeys.referenceRange.tr(context: context)}: ${tests?.referanceRange})", style: TextStyle( - fontSize: 24.f, + fontSize: 12.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, - 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, - ), + color: AppColors.greyTextColor, ), + softWrap: true, ), ) ], @@ -107,7 +97,7 @@ class LabOrderResultItem extends StatelessWidget { spacing: 6.h, crossAxisAlignment: CrossAxisAlignment.start, children: [ - severityText.tr().toText10(weight: FontWeight.w500, color: AppColors.greyTextColor), + severityText.tr().toText10(isBold: true, color: AppColors.greyTextColor), Utils.buildSvgWithAssets(icon: AppAssets.lab_result_indicator, width: 21, height: 23, iconColor: iconColor), ], ), @@ -126,7 +116,7 @@ class LabOrderResultItem extends StatelessWidget { borderColor: AppColors.infoColor.withAlpha(0), textColor: AppColors.infoColor, fontSize: 14, - fontWeight: FontWeight.w500, + fontWeight: FontWeight.w600, borderRadius: 12, padding: EdgeInsets.fromLTRB(10, 0, 10, 0), height: 40.h, diff --git a/lib/presentation/lab/lab_result_via_clinic/lab_order_specialResult.dart b/lib/presentation/lab/lab_result_via_clinic/lab_order_specialResult.dart index 5f71a7ea..55bf8ef1 100644 --- a/lib/presentation/lab/lab_result_via_clinic/lab_order_specialResult.dart +++ b/lib/presentation/lab/lab_result_via_clinic/lab_order_specialResult.dart @@ -62,14 +62,14 @@ class LabOrderSpecialResult extends StatelessWidget { // ...labOrder!.testDetails!.map((detail) { LocaleKeys.specialResult .tr() - .toText14(weight: FontWeight.w500) + .toText14(isBold: true) .toShimmer2(isShow: isLoading), data.isEmpty ? LocaleKeys.noSpecialResult .tr() .toText12( - fontWeight: FontWeight.w500, + isBold: true, color: AppColors.textColorLight) .toShimmer2(isShow: isLoading) : HtmlWidget(data).toShimmer2(isShow: isLoading) @@ -89,7 +89,7 @@ class LabOrderSpecialResult extends StatelessWidget { "Special Results", style: TextStyle( fontSize: 18.fSize, - fontWeight: FontWeight.w600, + isBold: true, color: AppColors.blackColor), ), Container( @@ -111,7 +111,7 @@ class LabOrderSpecialResult extends StatelessWidget { ? Text("No result available".needTranslation, style: TextStyle( fontSize: 12.fSize, - fontWeight: FontWeight.w500, + isBold: true, color: AppColors.textColorLight)) .toShimmer2(isShow: isLoading) : HtmlWidget(data) diff --git a/lib/presentation/lab/lab_results/lab_result_details.dart b/lib/presentation/lab/lab_results/lab_result_details.dart index ae73b08b..fb5fd583 100644 --- a/lib/presentation/lab/lab_results/lab_result_details.dart +++ b/lib/presentation/lab/lab_results/lab_result_details.dart @@ -28,18 +28,54 @@ import '../../../core/common_models/data_points.dart'; import '../../../widgets/common_bottom_sheet.dart' show showCommonBottomSheetWithoutHeight; import 'dart:ui' as ui; -class LabResultDetails extends StatelessWidget { +class LabResultDetails extends StatefulWidget { final LabResult recentLabResult; final String? testDescription; const LabResultDetails({super.key, required this.recentLabResult, required this.testDescription}); + @override + State createState() => _LabResultDetailsState(); +} + +class _LabResultDetailsState extends State { + final GlobalKey _aiAnalysisKey = GlobalKey(); + LabOrderResponseByAi? _previousAiData; + + LabResult get recentLabResult => widget.recentLabResult; + + String? get testDescription => widget.testDescription; + @override Widget build(BuildContext context) { LabViewModel labViewModel = Provider.of(context, listen: false); final appState = getIt.get(); return Scaffold( body: Consumer(builder: (context, labVM, child) { + // Detect changes in labOrderResponseByAi + WidgetsBinding.instance.addPostFrameCallback((_) { + if (labVM.labOrderResponseByAi != null && _previousAiData == null) { + // AI data just became available — scroll to it + final ctx = _aiAnalysisKey.currentContext; + if (ctx != null) { + Scrollable.ensureVisible( + ctx, + duration: const Duration(milliseconds: 400), + curve: Curves.easeOut, + ); + } + } else if (labVM.labOrderResponseByAi == null && _previousAiData != null) { + // AI data just became null — scroll to top + final scrollable = Scrollable.maybeOf(context); + scrollable?.position.animateTo( + 0, + duration: const Duration(milliseconds: 400), + curve: Curves.easeOut, + ); + } + _previousAiData = labVM.labOrderResponseByAi; + }); + return Column( children: [ Expanded( @@ -89,7 +125,7 @@ class LabResultDetails extends StatelessWidget { children: [ LocaleKeys.history.tr(context: context).toText16(isBold: true, color: AppColors.textColor), (labVM.isLabResultsHistoryShowMore ? "Show Less" : "Show More") - .toText14(weight: FontWeight.w500, color: AppColors.primaryRedColor, isUnderLine: true) + .toText14(isBold: true, color: AppColors.primaryRedColor, isUnderLine: true) .onPress(() { labVM.setIsLabResultsHistoryShowMore(); }), @@ -110,7 +146,7 @@ class LabResultDetails extends StatelessWidget { selector: (_, model) => model.labOrderResponseByAi, builder: (_, aiData, __) { if (aiData != null) { - return AiAnalysisWidget(data: aiData).paddingOnly(bottom: 16.h); + return AiAnalysisWidget(key: _aiAnalysisKey, data: aiData).paddingOnly(bottom: 16.h); } return const SizedBox.shrink(); }, @@ -120,7 +156,7 @@ class LabResultDetails extends StatelessWidget { ), ), ), - labVM.labOrderResponseByAi == null ? Container( + Utils.havePrivilege(115) ? labVM.labOrderResponseByAi == null ? Container( decoration: RoundedRectangleBorder().toSmoothCornerDecoration( color: AppColors.whiteColor, borderRadius: 24.h, @@ -161,13 +197,13 @@ class LabResultDetails extends StatelessWidget { onOkPressed: () { context.pop(); labViewModel.getAiOverviewSingleLabResult( - langId: appState.getLanguageID().toString(), recentLabResult: recentLabResult, loadingText: LocaleKeys.loadingAIAnalysis.tr(context: context)); + langId: appState.getLanguageID(), recentLabResult: recentLabResult, loadingText: LocaleKeys.loadingAIAnalysis.tr(context: context)); }, onCancelPressed: () { context.pop(); }); }), - ) : SizedBox.shrink(), + ) : SizedBox.shrink() : SizedBox.shrink(), ], ); }), @@ -187,7 +223,7 @@ class LabResultDetails extends StatelessWidget { Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ - (recentLabResult.testCode ?? "").toText28(isBold: true, color: AppColors.textColor, letterSpacing: -2), + (recentLabResult.testCode ?? "").toText28(isBold: true, color: AppColors.textColor, letterSpacing: -2, isEnglishOnly: true), Visibility( visible: recentLabResult.checkIfGraphShouldBeDisplayed() == true, child: Column( @@ -199,7 +235,7 @@ class LabResultDetails extends StatelessWidget { recentLabResult.calculatedResultFlag ?? "", ) .tr(context: context) - .toText10(weight: FontWeight.w500, color: AppColors.greyTextColor), + .toText10(isBold: true, color: AppColors.greyTextColor), Utils.buildSvgWithAssets( icon: AppAssets.lab_result_indicator, width: 21, @@ -213,7 +249,7 @@ class LabResultDetails extends StatelessWidget { ], ), SizedBox(height: 4.h), - ("${LocaleKeys.resultOf.tr(context: context)} ${recentLabResult.verifiedOn ?? ""}").toText11(weight: FontWeight.w500, color: AppColors.greyTextColor, isEnglishOnly: true), + ("${LocaleKeys.resultOf.tr(context: context)} ${recentLabResult.verifiedOn ?? ""}").toText11(isBold: true, color: AppColors.greyTextColor, isEnglishOnly: true), ], ), Row( @@ -224,12 +260,16 @@ class LabResultDetails extends StatelessWidget { mainAxisSize: MainAxisSize.max, children: [ Flexible( - child: ("${recentLabResult.resultValue} ${recentLabResult.uOM}" ?? "").toText28( - isBold: true, - color: context.read().getColor( - recentLabResult.calculatedResultFlag ?? "", - ), - letterSpacing: -2), + child: Directionality( + textDirection: ui.TextDirection.ltr, + child: ("${recentLabResult.resultValue} ${recentLabResult.uOM}" ?? "").toText28( + isBold: true, + color: context.read().getColor( + recentLabResult.calculatedResultFlag ?? "", + ), + letterSpacing: -2, + isEnglishOnly: true), + ), ), SizedBox( width: 4.h, @@ -245,7 +285,7 @@ class LabResultDetails extends StatelessWidget { "${LocaleKeys.referenceRange.tr(context: context)}: \n${recentLabResult.referanceRange!.trim()}", style: TextStyle( fontSize: 12.f, - fontWeight: FontWeight.w500, + fontWeight: FontWeight.w600, fontFamily: 'Poppins', color: AppColors.greyTextColor, ), @@ -281,7 +321,7 @@ class LabResultDetails extends StatelessWidget { // style: TextStyle( // fontSize: 16.f, // fontFamily: 'Poppins', - // fontWeight: FontWeight.w600, + // isBold: true, // color: AppColors.textColor, // ), // ), @@ -386,7 +426,7 @@ class LabResultDetails extends StatelessWidget { return SizedBox.shrink(); // } }, - graphColor: AppColors.graphGridColor, + graphColor: AppColors.textColor.withAlpha(90), graphShadowColor: AppColors.graphGridColor.withOpacity(.5), graphGridColor: graphColor.withOpacity(.4), bottomLabelFormatter: (value, data) { @@ -443,7 +483,7 @@ class LabResultDetails extends StatelessWidget { // itemCount: labmodel.isLabResultsHistoryShowMore ? (labmodel.filteredGraphValues.length < 3 ? labmodel.filteredGraphValues : 3) : labmodel.filteredGraphValues.length, itemCount: !labmodel.isLabResultsHistoryShowMore ? (labmodel.mainLabResults.length < 3 ? labmodel.mainLabResults.length : 3) : labmodel.mainLabResults.length, itemBuilder: (context, index) { - var data = labmodel.mainLabResults.reversed.toList()[index]; + var data = labmodel.mainLabResults.toList()[index]; return LabHistoryItem( dayNameAndDate: labmodel.getFormattedDate(DateUtil.convertStringToDate(data.verifiedOnDateTime)), result: data.resultValue??"", @@ -486,8 +526,8 @@ class LabResultDetails extends StatelessWidget { crossAxisAlignment: CrossAxisAlignment.start, spacing: 8.h, children: [ - LocaleKeys.whatIsThisResult.tr(context: context).toText16(weight: FontWeight.w600, color: AppColors.textColor), - testDescription?.toText12(fontWeight: FontWeight.w500, color: AppColors.textColorLight) ?? SizedBox.shrink() + LocaleKeys.whatIsThisResult.tr(context: context).toText16(isBold: true, color: AppColors.textColor), + testDescription?.toText12(isBold: true, color: AppColors.textColorLight) ?? SizedBox.shrink() ], )); } diff --git a/lib/presentation/lab/lab_results/lab_result_list_item.dart b/lib/presentation/lab/lab_results/lab_result_list_item.dart index f2dc90e4..908d129d 100644 --- a/lib/presentation/lab/lab_results/lab_result_list_item.dart +++ b/lib/presentation/lab/lab_results/lab_result_list_item.dart @@ -24,7 +24,7 @@ class LabHistoryItem extends StatelessWidget{ dayNameAndDate, style: TextStyle( fontSize: 14.f, - fontWeight: FontWeight.w500, + fontWeight: FontWeight.w600, fontFamily: 'Poppins', color: AppColors.labelTextColor ), @@ -33,7 +33,7 @@ class LabHistoryItem extends StatelessWidget{ result, style: TextStyle( fontSize: 18.f, - fontWeight: FontWeight.w600, + fontWeight: FontWeight.w600, fontFamily: 'Poppins', color: AppColors.textColor ), diff --git a/lib/presentation/lab/search_lab_report.dart b/lib/presentation/lab/search_lab_report.dart index be57150f..ac559f42 100644 --- a/lib/presentation/lab/search_lab_report.dart +++ b/lib/presentation/lab/search_lab_report.dart @@ -94,7 +94,7 @@ class _SearchLabResultsContentState extends State { ), SizedBox(height: ResponsiveExtension(20).h), if (filteredSuggestions.isNotEmpty) ...[ - "Suggestions".toText16(isBold: true, isEnglishOnly: true), + LocaleKeys.suggestions.tr(context: context).toText16(isBold: true, isEnglishOnly: true), ], ], ), @@ -147,7 +147,7 @@ class SuggestionChip extends StatelessWidget { child: label.toText12( // color: isSelected ? AppColors.textColor : Colors.black87, color: AppColors.textColor, - fontWeight: FontWeight.w500, + isBold: true, isEnglishOnly: true ), ), diff --git a/lib/presentation/medical_file/eye_measurement_details_page.dart b/lib/presentation/medical_file/eye_measurement_details_page.dart index 08226095..68f261cd 100644 --- a/lib/presentation/medical_file/eye_measurement_details_page.dart +++ b/lib/presentation/medical_file/eye_measurement_details_page.dart @@ -172,7 +172,7 @@ class EyeMeasurementDetailsPage extends StatelessWidget { mainAxisAlignment: MainAxisAlignment.spaceAround, crossAxisAlignment: CrossAxisAlignment.start, children: [ - Expanded(flex: 2, child: title.toText11(weight: FontWeight.w500)), + Expanded(flex: 2, child: title.toText11(isBold: true)), Expanded( flex: 2, child: Row( diff --git a/lib/presentation/medical_file/eye_measurements_appointments_page.dart b/lib/presentation/medical_file/eye_measurements_appointments_page.dart index d8438ba4..df30a5a3 100644 --- a/lib/presentation/medical_file/eye_measurements_appointments_page.dart +++ b/lib/presentation/medical_file/eye_measurements_appointments_page.dart @@ -25,7 +25,7 @@ class EyeMeasurementsAppointmentsPage extends StatelessWidget { return Scaffold( backgroundColor: AppColors.bgScaffoldColor, body: CollapsingListView( - title: "Eye Measurements", + title: LocaleKeys.eyeMeasurements.tr(context: context), child: SingleChildScrollView( child: Consumer(builder: (context, myAppointmentsVM, child) { return Column( diff --git a/lib/presentation/medical_file/medical_file_page.dart b/lib/presentation/medical_file/medical_file_page.dart index a5a42e3b..89839f0d 100644 --- a/lib/presentation/medical_file/medical_file_page.dart +++ b/lib/presentation/medical_file/medical_file_page.dart @@ -34,6 +34,7 @@ import 'package:hmg_patient_app_new/features/my_appointments/my_appointments_vie import 'package:hmg_patient_app_new/features/my_invoices/my_invoices_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/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/active_medication/active_medication_page.dart'; import 'package:hmg_patient_app_new/presentation/allergies/allergies_list_page.dart'; @@ -55,6 +56,8 @@ import 'package:hmg_patient_app_new/presentation/medical_file/eye_measurements_a import 'package:hmg_patient_app_new/presentation/medical_file/patient_sickleaves_list_page.dart'; import 'package:hmg_patient_app_new/presentation/medical_file/vaccine_list_page.dart'; import 'package:hmg_patient_app_new/presentation/medical_file/widgets/lab_rad_card.dart'; +import 'package:hmg_patient_app_new/presentation/medical_file/widgets/health_tracker_menu_card.dart'; +import 'package:hmg_patient_app_new/presentation/medical_file/widgets/health_tools_card.dart'; import 'package:hmg_patient_app_new/presentation/medical_file/widgets/medical_file_card.dart'; import 'package:hmg_patient_app_new/presentation/medical_file/widgets/patient_sick_leave_card.dart'; import 'package:hmg_patient_app_new/presentation/medical_report/medical_reports_page.dart'; @@ -78,6 +81,7 @@ import 'package:hmg_patient_app_new/widgets/input_widget.dart'; import 'package:hmg_patient_app_new/widgets/loader/bottomsheet_loader.dart'; import 'package:hmg_patient_app_new/widgets/routes/custom_page_route.dart'; import 'package:hmg_patient_app_new/widgets/shimmer/common_shimmer_widget.dart'; +import 'package:hmg_patient_app_new/widgets/user_avatar_widget.dart'; import 'package:provider/provider.dart'; import 'package:url_launcher/url_launcher.dart'; @@ -242,7 +246,12 @@ class _MedicalFilePageState extends State { Row( crossAxisAlignment: CrossAxisAlignment.start, children: [ - Image.asset(appState.getAuthenticatedUser()?.gender == 1 ? AppAssets.maleImg : AppAssets.femaleImg, width: 56.w, height: 56.h), + UserAvatarWidget( + width: 56.w, + height: 56.h, + fit: BoxFit.cover, + isCircular: true, + ), SizedBox(width: 8.w), Column( crossAxisAlignment: CrossAxisAlignment.start, @@ -250,7 +259,7 @@ class _MedicalFilePageState extends State { SizedBox( width: MediaQuery.of(context).size.width * 0.6, child: "${appState.getAuthenticatedUser()!.firstName} ${appState.getAuthenticatedUser()!.lastName}" - .toText18(isBold: true, weight: FontWeight.w600, textOverflow: TextOverflow.ellipsis, maxlines: 2), + .toText18(isBold: true, textOverflow: TextOverflow.ellipsis, maxlines: 2), ), SizedBox(height: 4.h), Wrap( @@ -375,10 +384,10 @@ class _MedicalFilePageState extends State { child: Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ - LocaleKeys.vitalSigns.tr(context: context).toText16(weight: FontWeight.w500, letterSpacing: -0.2), + LocaleKeys.vitalSigns.tr(context: context).toText16(isBold: true, letterSpacing: -0.2), Row( children: [ - LocaleKeys.viewAll.tr(context: context).toText12(color: AppColors.primaryRedColor, fontWeight: FontWeight.w500), + LocaleKeys.viewAll.tr(context: context).toText12(color: AppColors.primaryRedColor, isBold: true), SizedBox(width: 2.h), Icon(Icons.arrow_forward_ios, color: AppColors.primaryRedColor, size: 10.h), ], @@ -497,24 +506,20 @@ class _MedicalFilePageState extends State { itemPadding: EdgeInsets.symmetric(vertical: 16.h, horizontal: 14.h), items: [ ExpandableListItem( - title: LocaleKeys.medicalServices.tr(context: context).toText18(weight: FontWeight.w600), + title: LocaleKeys.medicalServices.tr(context: context).toText18(isBold: true), expandedBackgroundColor: Colors.transparent, children: [ SizedBox(height: 10.h), getSelectedTabData(0), ], ), - ExpandableListItem( - title: LocaleKeys.medicalReports.tr(context: context).toText18( - weight: FontWeight.w600, - ), - expandedBackgroundColor: Colors.transparent, + ExpandableListItem(title: LocaleKeys.medicalReports.tr(context: context).toText18(isBold: true), expandedBackgroundColor: Colors.transparent, children: [ SizedBox(height: 10.h), getSelectedTabData(2), ]), ExpandableListItem( - title: LocaleKeys.insuranceAndPayments.tr(context: context).toText18(weight: FontWeight.w600), + title: LocaleKeys.insuranceAndPayments.tr(context: context).toText18(isBold: true), expandedBackgroundColor: Colors.transparent, children: [ SizedBox(height: 10.h), @@ -522,7 +527,7 @@ class _MedicalFilePageState extends State { ], ), ExpandableListItem( - title: LocaleKeys.healthTrackers.tr(context: context).toText18(weight: FontWeight.w600), + title: LocaleKeys.healthTools.tr(context: context).toText18(isBold: true), expandedBackgroundColor: Colors.transparent, children: [ SizedBox(height: 10.h), @@ -589,10 +594,10 @@ class _MedicalFilePageState extends State { Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ - LocaleKeys.appointmentsAndVisits.tr(context: context).toText16(weight: FontWeight.w500, letterSpacing: -0.2), + LocaleKeys.appointmentsAndVisits.tr(context: context).toText16(isBold: true, letterSpacing: -0.2), Row( children: [ - LocaleKeys.viewAll.tr(context: context).toText12(color: AppColors.primaryRedColor, fontWeight: FontWeight.w500), + LocaleKeys.viewAll.tr(context: context).toText12(color: AppColors.primaryRedColor, isBold: true), SizedBox(width: 2.h), Icon(Icons.arrow_forward_ios, color: AppColors.primaryRedColor, size: 10.h), ], @@ -642,7 +647,7 @@ class _MedicalFilePageState extends State { borderColor: AppColors.secondaryLightRedColor, textColor: AppColors.primaryRedColor, fontSize: 14.f, - fontWeight: FontWeight.w500, + fontWeight: FontWeight.w600, borderRadius: 12.r, padding: EdgeInsets.symmetric(horizontal: 10.w), height: isFoldable || isTablet ? 50.h : 40.h, @@ -715,7 +720,7 @@ class _MedicalFilePageState extends State { ); }), SizedBox(height: 10.h), - LocaleKeys.labAndRadiology.tr(context: context).toText16(weight: FontWeight.w500, letterSpacing: -0.2), + LocaleKeys.labAndRadiology.tr(context: context).toText16(isBold: true, letterSpacing: -0.2), SizedBox(height: 16.h), Row( children: [ @@ -757,10 +762,10 @@ class _MedicalFilePageState extends State { Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ - LocaleKeys.activeMedicationsAndPrescriptions.tr(context: context).toText16(weight: FontWeight.w500, letterSpacing: -0.2), + LocaleKeys.activeMedicationsAndPrescriptions.tr(context: context).toText16(isBold: true, letterSpacing: -0.2), Row( children: [ - LocaleKeys.viewAll.tr(context: context).toText12(color: AppColors.primaryRedColor, fontWeight: FontWeight.w500), + LocaleKeys.viewAll.tr(context: context).toText12(color: AppColors.primaryRedColor, isBold: true), SizedBox(width: 2.w), Icon(Icons.arrow_forward_ios, color: AppColors.primaryRedColor, size: 10.h), ], @@ -872,7 +877,7 @@ class _MedicalFilePageState extends State { // borderColor: AppColors.secondaryLightRedColor, // textColor: AppColors.primaryRedColor, // fontSize: 12.f, - // fontWeight: FontWeight.w500, + // isBold: true, // borderRadius: 12.r, // height: 40.h, // icon: AppAssets.requests, @@ -893,7 +898,7 @@ class _MedicalFilePageState extends State { // borderColor: AppColors.secondaryLightRedColor, // textColor: AppColors.primaryRedColor, // fontSize: 12.f, - // fontWeight: FontWeight.w500, + // isBold: true, // borderRadius: 12.h, // height: 40.h, // icon: AppAssets.all_medications_icon, @@ -927,10 +932,10 @@ class _MedicalFilePageState extends State { Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ - LocaleKeys.myDoctor.tr(context: context).toText16(weight: FontWeight.w500, letterSpacing: -0.2), + LocaleKeys.myDoctor.tr(context: context).toText16(isBold: true, letterSpacing: -0.2), Row( children: [ - LocaleKeys.viewAll.tr(context: context).toText12(color: AppColors.primaryRedColor, fontWeight: FontWeight.w500), + LocaleKeys.viewAll.tr(context: context).toText12(color: AppColors.primaryRedColor, isBold: true), SizedBox(width: 2.w), Icon(Icons.arrow_forward_ios, color: AppColors.primaryRedColor, size: 10.h), ], @@ -957,7 +962,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(isBold: true, isCenter: true, maxLine: 2).toShimmer2(isShow: true), ], ) : myAppointmentsVM.patientMyDoctorsList.isEmpty @@ -977,7 +982,7 @@ class _MedicalFilePageState extends State { ), ).paddingSymmetrical(0.w, 0.h) : SizedBox( - height: 100.h, + height: 110.h, child: ListView.separated( scrollDirection: Axis.horizontal, itemCount: myAppointmentsVM.patientMyDoctorsList.length, @@ -1001,11 +1006,10 @@ class _MedicalFilePageState extends State { fit: BoxFit.cover, ).circle(100).toShimmer2(isShow: false, radius: 50.r), SizedBox(height: 8.h), - Expanded( + SizedBox( + width: 80.w, child: (myAppointmentsVM.patientMyDoctorsList[index].doctorName) - .toString() - .toText12(fontWeight: FontWeight.w500, isCenter: true, maxLine: 2) - .toShimmer2(isShow: false), + .toString().toText12(isBold: true, isCenter: true, maxLine: 2).toShimmer2(isShow: false), ), ], ), @@ -1020,7 +1024,7 @@ class _MedicalFilePageState extends State { LoaderBottomSheet.hideLoader(); Navigator.of(context).push( CustomPageRoute( - page: DoctorProfilePage(), + page: DoctorProfilePage(isDoctorAllowedToBook: !(myAppointmentsVM.patientMyDoctorsList[index].isLiveCareClinic ?? false)), ), ); }, onError: (err) { @@ -1042,7 +1046,7 @@ class _MedicalFilePageState extends State { ).paddingSymmetrical(0.w, 0); }), SizedBox(height: 24.h), - LocaleKeys.others.tr(context: context).toText16(weight: FontWeight.w500, letterSpacing: -0.2), + LocaleKeys.others.tr(context: context).toText16(isBold: true, letterSpacing: -0.2), SizedBox(height: 16.h), GridView( gridDelegate: SliverGridDelegateWithFixedCrossAxisCount( @@ -1152,7 +1156,7 @@ class _MedicalFilePageState extends State { borderColor: AppColors.bgGreenColor.withOpacity(0.0), textColor: AppColors.bgGreenColor, fontSize: 14.f, - fontWeight: FontWeight.w500, + fontWeight: FontWeight.w600, borderRadius: 12.r, padding: EdgeInsets.fromLTRB(10.w, 0, 10.w, 0), height: isFoldable ? 50.h : 40.h, @@ -1278,7 +1282,7 @@ class _MedicalFilePageState extends State { width: 62.w, height: 62.h, ), - ).paddingSymmetrical(0.w, 0.h); + ).paddingSymmetrical(0.w, 0.h); }), SizedBox(height: 16.h), GridView( @@ -1292,21 +1296,21 @@ class _MedicalFilePageState extends State { padding: EdgeInsets.zero, shrinkWrap: true, children: [ - MedicalFileCard( - label: LocaleKeys.monthlyReports.tr(context: context), - textColor: AppColors.blackColor, - backgroundColor: AppColors.whiteColor, - svgIcon: AppAssets.monthly_reports_icon, - isLargeText: true, - iconSize: 36.h, - ).onPress(() { - monthlyReportViewModel.setHealthSummaryEnabled(cacheService.getBool(key: CacheConst.isMonthlyReportEnabled) ?? false); - Navigator.of(context).push( - CustomPageRoute( - page: MonthlyReport(), - ), - ); - }), + // MedicalFileCard( + // label: LocaleKeys.monthlyReports.tr(context: context), + // textColor: AppColors.blackColor, + // backgroundColor: AppColors.whiteColor, + // svgIcon: AppAssets.monthly_reports_icon, + // isLargeText: true, + // iconSize: 36.h, + // ).onPress(() { + // monthlyReportViewModel.setHealthSummaryEnabled(cacheService.getBool(key: CacheConst.isMonthlyReportEnabled) ?? false); + // Navigator.of(context).push( + // CustomPageRoute( + // page: MonthlyReport(), + // ), + // ); + // }), MedicalFileCard( label: LocaleKeys.medicalReports.tr(context: context), textColor: AppColors.blackColor, @@ -1343,95 +1347,93 @@ class _MedicalFilePageState extends State { ], ); case 3: - // Trackers Tab Data + // Health Tools Tab Data return Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ - // Row( - // children: [ - // LocaleKeys.healthTrackers.tr(context: context).toText16(weight: FontWeight.w500, color: AppColors.textColor), - // ], - // ), - // SizedBox(height: 16.h), GridView( gridDelegate: SliverGridDelegateWithFixedCrossAxisCount( crossAxisCount: 3, crossAxisSpacing: 10.h, mainAxisSpacing: 16.w, - mainAxisExtent: 115.h, + mainAxisExtent: 120.h, ), physics: NeverScrollableScrollPhysics(), - padding: EdgeInsets.zero, + padding: EdgeInsets.only(top: 12.h), shrinkWrap: true, children: [ - MedicalFileCard( - label: LocaleKeys.bloodSugar.tr(context: context), + // Health Trackers + HealthToolsCard( + label: LocaleKeys.healthTrackers.tr(context: context), textColor: AppColors.blackColor, - backgroundColor: AppColors.whiteColor, - svgIcon: AppAssets.blood_sugar_icon, - isLargeText: true, - iconSize: 36.w, - ).onPress(() => context.navigateWithName(AppRoutes.healthTrackerDetailPage, arguments: HealthTrackerTypeEnum.bloodSugar)), - MedicalFileCard( - label: LocaleKeys.bloodPressure.tr(context: context), + svgIcon: AppAssets.general_health, + iconColor:null, + iconSize: 24.w, + ).onPress(() { + if (getIt.get().isAuthenticated) { + context.navigateWithName(AppRoutes.healthTrackersPage); + } + }), + // Daily Water Monitor + HealthToolsCard( + label: LocaleKeys.dailyWaterMonitor.tr(context: context), textColor: AppColors.blackColor, - backgroundColor: AppColors.whiteColor, - svgIcon: AppAssets.lab_result_icon, - isLargeText: true, - iconSize: 36.w, - ).onPress(() => context.navigateWithName(AppRoutes.healthTrackerDetailPage, arguments: HealthTrackerTypeEnum.bloodPressure)), - MedicalFileCard( - label: LocaleKeys.weightTracker.tr(context: context), + svgIcon: AppAssets.daily_water_monitor_icon, + iconColor: AppColors.infoColor, + iconSize: 24.w, + ).onPress(() async { + if (getIt.get().isAuthenticated) { + LoaderBottomSheet.showLoader(loadingText: LocaleKeys.fetchingYourWaterIntakeDetails.tr()); + final waterMonitorVM = getIt.get(); + await waterMonitorVM.fetchUserDetailsForMonitoring( + onSuccess: (userDetail) { + LoaderBottomSheet.hideLoader(); + if (userDetail == null) { + waterMonitorVM.populateFromAuthenticatedUser(); + context.navigateWithName(AppRoutes.waterMonitorSettingsPage); + } else { + context.navigateWithName(AppRoutes.waterConsumptionPage); + } + }, + onError: (error) { + LoaderBottomSheet.hideLoader(); + context.navigateWithName(AppRoutes.waterConsumptionPage); + }, + ); + } + }), + // Health Calculators + HealthToolsCard( + label: LocaleKeys.healthCalculatorsServices.tr(context: context), textColor: AppColors.blackColor, - backgroundColor: AppColors.whiteColor, - svgIcon: AppAssets.weight_tracker_icon, - isLargeText: true, - iconSize: 36.w, - ).onPress(() => context.navigateWithName(AppRoutes.healthTrackerDetailPage, arguments: HealthTrackerTypeEnum.weightTracker)), - ], - ).paddingSymmetrical(0.w, 0.0), - SizedBox(height: 16.h), - // Row( - // children: [ - // LocaleKeys.others.tr(context: context).toText16(weight: FontWeight.w500, color: AppColors.textColor), - // ], - // ), - SizedBox(height: 16.h), - GridView( - gridDelegate: SliverGridDelegateWithFixedCrossAxisCount( - crossAxisCount: 3, - crossAxisSpacing: 10.h, - mainAxisSpacing: 16.w, - mainAxisExtent: 115.h, - ), - physics: NeverScrollableScrollPhysics(), - padding: EdgeInsets.zero, - shrinkWrap: true, - children: [ - // MedicalFileCard( - // 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(() { - // 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(() {}), + svgIcon: AppAssets.health_calculators_services_icon, + iconColor: AppColors.successColor, + iconSize: 24.w, + ).onPress(() { + context.navigateWithName(AppRoutes.healthCalculatorsPage); + }), + // Health Converters + HealthToolsCard( + label: LocaleKeys.healthConvertersServices.tr(context: context), + textColor: AppColors.blackColor, + svgIcon: AppAssets.health_converters_icon, + iconColor: AppColors.primaryRedColor, + iconSize: 24.w, + ).onPress(() { + context.navigateWithName(AppRoutes.healthConvertersPage); + }), + // Smart Watches + HealthToolsCard( + label: LocaleKeys.smartWatchesServices.tr(context: context), + textColor: AppColors.blackColor, + svgIcon: AppAssets.smartwatch_icon, + iconColor: AppColors.warningColorYellow, + iconSize: 24.w, + ).onPress(() { + if (getIt.get().isAuthenticated) { + context.navigateWithName(AppRoutes.smartWatches); + } + }), ], ).paddingSymmetrical(0.w, 0.0), SizedBox(height: 24.h), @@ -1535,7 +1537,7 @@ class _MedicalFilePageState extends State { child: _buildVitalSignCard( icon: AppAssets.bmiVital, label: LocaleKeys.bmi.tr(context: context), - value: vitalSign.bodyMassIndex?.toString() ?? '--', + value: _formatValue(vitalSign.bodyMassIndex), unit: '', status: vitalSign.bodyMassIndex != null ? _getBMIStatus(vitalSign.bodyMassIndex) : null, // status: "Overweight", @@ -1547,7 +1549,7 @@ class _MedicalFilePageState extends State { child: _buildVitalSignCard( icon: AppAssets.heightVital, label: LocaleKeys.height.tr(context: context), - value: vitalSign.heightCm?.toString() ?? '--', + value: _formatValue(vitalSign.heightCm), unit: 'cm', status: null, onTap: onTap, @@ -1565,7 +1567,7 @@ class _MedicalFilePageState extends State { child: _buildVitalSignCard( icon: AppAssets.weightVital, label: LocaleKeys.weight.tr(context: context), - value: vitalSign.weightKg?.toString() ?? '--', + value: _formatValue(vitalSign.weightKg), unit: 'kg', status: vitalSign.weightKg != null ? _getWeightStatus(vitalSign.bodyMassIndex) : null, onTap: onTap, @@ -1576,12 +1578,18 @@ class _MedicalFilePageState extends State { child: _buildVitalSignCard( icon: AppAssets.bloodPressure, label: LocaleKeys.bloodPressure.tr(context: context), - value: vitalSign.bloodPressureLower != null && vitalSign.bloodPressureHigher != null ? "${vitalSign.bloodPressureHigher}/${vitalSign.bloodPressureLower}" : '--', + value: (vitalSign.bloodPressureLower != null && vitalSign.bloodPressureHigher != null && + vitalSign.bloodPressureLower != 0 && vitalSign.bloodPressureHigher != 0) + ? "${vitalSign.bloodPressureHigher}/${vitalSign.bloodPressureLower}" + : '--', unit: '', - status: _getBloodPressureStatus( - systolic: vitalSign.bloodPressureHigher, - diastolic: vitalSign.bloodPressureLower, - ), + status: (vitalSign.bloodPressureLower != null && vitalSign.bloodPressureHigher != null && + vitalSign.bloodPressureLower != 0 && vitalSign.bloodPressureHigher != 0) + ? _getBloodPressureStatus( + systolic: vitalSign.bloodPressureHigher, + diastolic: vitalSign.bloodPressureLower, + ) + : null, onTap: onTap, ), ), @@ -1590,25 +1598,36 @@ class _MedicalFilePageState extends State { ]; } - String _getBMIStatus(dynamic bmi) { + VitalSignStatus? _getBMIStatus(dynamic bmi) { return VitalSignUiModel.bmiStatus(bmi); } - String? _getWeightStatus(dynamic bmi) { - if (bmi == null) return 'Normal'; - return VitalSignUiModel.bmiStatus(bmi); + VitalSignStatus? _getWeightStatus(dynamic bmi) { + return VitalSignUiModel.weightStatus(bmi); } - String? _getBloodPressureStatus({dynamic systolic, dynamic diastolic}) { + VitalSignStatus? _getBloodPressureStatus({dynamic systolic, dynamic diastolic}) { return VitalSignUiModel.bloodPressureStatus(systolic: systolic, diastolic: diastolic); } + // Helper function to format vital sign values, showing '--' instead of 0 or null + String _formatValue(dynamic value) { + if (value == null) return '--'; + if (value is num && value == 0) return '--'; + if (value is String) { + if (value.trim().isEmpty) return '--'; + final parsed = double.tryParse(value); + if (parsed == null || parsed == 0) return '--'; + } + return value.toString(); + } + Widget _buildVitalSignCard({ required String icon, required String label, required String value, required String unit, - required String? status, + required VitalSignStatus? status, required VoidCallback onTap, }) { final VitalSignUiModel scheme = VitalSignUiModel.scheme(status: status, label: label); @@ -1675,11 +1694,11 @@ class _MedicalFilePageState extends State { isEnglishOnly: true ), ), - if (unit.isNotEmpty) ...[ + if (unit.isNotEmpty && value != '--' && value != '0') ...[ SizedBox(width: 3.w), unit.toText12( color: AppColors.textColor, - fontWeight: FontWeight.w500, + isBold: true, ), ], ], @@ -1688,7 +1707,7 @@ class _MedicalFilePageState extends State { if (status != null) ...[ SizedBox(width: 4.w), AppCustomChipWidget( - labelText: status, + labelText: VitalSignUiModel.statusToString(status), backgroundColor: scheme.chipBg, textColor: scheme.chipFg, ), diff --git a/lib/presentation/medical_file/patient_sickleaves_list_page.dart b/lib/presentation/medical_file/patient_sickleaves_list_page.dart index 4518eca5..42d94ce7 100644 --- a/lib/presentation/medical_file/patient_sickleaves_list_page.dart +++ b/lib/presentation/medical_file/patient_sickleaves_list_page.dart @@ -24,6 +24,8 @@ import 'package:provider/provider.dart'; import 'widgets/patient_sick_leave_card.dart'; +import 'dart:ui' as ui; + class PatientSickleavesListPage extends StatefulWidget { const PatientSickleavesListPage({super.key}); @@ -70,7 +72,7 @@ class _PatientSickleavesListPageState extends State { // borderColor: model.isSickLeavesSortByClinic ? AppColors.primaryRedColor : AppColors.textColor.withValues(alpha: 0.2), // textColor: model.isSickLeavesSortByClinic ? AppColors.primaryRedColor : AppColors.blackColor, // fontSize: 12, - // fontWeight: FontWeight.w500, + // isBold: true, // borderRadius: 10, // padding: EdgeInsets.fromLTRB(10, 0, 10, 0), // height: 40.h, @@ -85,7 +87,7 @@ class _PatientSickleavesListPageState extends State { // borderColor: model.isSickLeavesSortByClinic ? AppColors.textColor.withValues(alpha: 0.2) : AppColors.primaryRedColor, // textColor: model.isSickLeavesSortByClinic ? AppColors.blackColor : AppColors.primaryRedColor, // fontSize: 12, - // fontWeight: FontWeight.w500, + // isBold: true, // borderRadius: 10, // padding: EdgeInsets.fromLTRB(10, 0, 10, 0), // height: 40.h, @@ -144,7 +146,7 @@ class _PatientSickleavesListPageState extends State { fit: BoxFit.fill, ).circle(100), SizedBox(width: 8.h), - Expanded(child: model.patientSickLeaveList[index].doctorName!.toText14(weight: FontWeight.w500)), + Expanded(child: model.patientSickLeaveList[index].doctorName!.toText14(isBold: true)), ], ), SizedBox(height: 8.h), @@ -153,14 +155,26 @@ class _PatientSickleavesListPageState extends State { spacing: 6.h, runSpacing: 6.h, children: [ - AppCustomChipWidget( - labelText: DateUtil.formatDateToDate(DateUtil.convertStringToDate(model.patientSickLeaveList[index].appointmentDate), false), + Directionality( + textDirection: ui.TextDirection.ltr, + child: AppCustomChipWidget( + labelText: DateUtil.formatDateToDate(DateUtil.convertStringToDate(model.patientSickLeaveList[index].appointmentDate), false), + isEnglishOnly: true, + ), ), AppCustomChipWidget( labelText: model.isSickLeavesSortByClinic ? model.patientSickLeaveList[index].projectName! : model.patientSickLeaveList[index].clinicName!, ), AppCustomChipWidget( - labelText: "${model.patientSickLeaveList[index].sickLeaveDays} Days", + richText: Row( + mainAxisSize: MainAxisSize.min, + children: [ + "${model.patientSickLeaveList[index].sickLeaveDays} ".toText10(isBold: true), + LocaleKeys.days.tr(context: context).toText10(isBold: true) + ], + ), + // labelText: "${model.patientSickLeaveList[index].sickLeaveDays} ${LocaleKeys.days.tr(context: context)}", + isEnglishOnly: true, ), ], ), @@ -189,7 +203,7 @@ class _PatientSickleavesListPageState extends State { borderColor: AppColors.secondaryLightRedColor, textColor: AppColors.primaryRedColor, fontSize: 14, - fontWeight: FontWeight.w500, + isBold: true, borderRadius: 12, padding: EdgeInsets.fromLTRB(10, 0, 10, 0), height: 40.h, @@ -222,7 +236,7 @@ class _PatientSickleavesListPageState extends State { // fit: BoxFit.fill, // ).circle(100), // SizedBox(width: 8.h), - // Expanded(child: sickLeave.doctorName!.toText14(weight: FontWeight.w500)), + // Expanded(child: sickLeave.doctorName!.toText14(isBold: true)), // ], // ), // SizedBox(height: 8.h), @@ -267,7 +281,7 @@ class _PatientSickleavesListPageState extends State { // borderColor: AppColors.secondaryLightRedColor, // textColor: AppColors.primaryRedColor, // fontSize: 14, - // fontWeight: FontWeight.w500, + // isBold: true, // borderRadius: 12, // padding: EdgeInsets.fromLTRB(10, 0, 10, 0), // height: 40.h, @@ -312,7 +326,7 @@ class _PatientSickleavesListPageState extends State { // borderColor: AppColors.greyColor, // textColor: AppColors.blackColor, // fontSize: 10, - // fontWeight: FontWeight.w500, + // isBold: true, // borderRadius: 8, // padding: EdgeInsets.fromLTRB(10, 0, 10, 0), // height: 30.h, @@ -360,7 +374,7 @@ class _PatientSickleavesListPageState extends State { // // fit: BoxFit.fill, // // ).circle(100), // // SizedBox(width: 8.h), - // // Expanded(child: sickLeave.doctorName!.toText14(weight: FontWeight.w500)), + // // Expanded(child: sickLeave.doctorName!.toText14(isBold: true)), // // ], // // ), // // SizedBox(height: 8.h), @@ -405,7 +419,7 @@ class _PatientSickleavesListPageState extends State { // // borderColor: AppColors.secondaryLightRedColor, // // textColor: AppColors.primaryRedColor, // // fontSize: 14, - // // fontWeight: FontWeight.w500, + // // isBold: true, // // borderRadius: 12, // // padding: EdgeInsets.fromLTRB(10, 0, 10, 0), // // height: 40.h, diff --git a/lib/presentation/medical_file/vaccine_list_page.dart b/lib/presentation/medical_file/vaccine_list_page.dart index bf02cb74..06f1a6d5 100644 --- a/lib/presentation/medical_file/vaccine_list_page.dart +++ b/lib/presentation/medical_file/vaccine_list_page.dart @@ -29,11 +29,19 @@ class _VaccineListPageState extends State { @override void initState() { + super.initState(); scheduleMicrotask(() { + medicalFileViewModel = Provider.of(context, listen: false); medicalFileViewModel.setIsPatientVaccineListLoading(true); - medicalFileViewModel.getPatientVaccinesList(); + medicalFileViewModel.getPatientVaccinesList( + onSuccess: (data) { + // print("✅ Vaccine data received: ${medicalFileViewModel.patientVaccineList.length} items"); + }, + onError: (error) { + //print("❌ Vaccine error: $error"); + }, + ); }); - super.initState(); } @override @@ -133,11 +141,18 @@ class _VaccineListPageState extends State { // height: 63.h, // fit: BoxFit.fill, // ).circle(100).toShimmer2(isShow: false), - SizedBox(width: 16.h), + + SizedBox(width: 8.h), Expanded( child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ + AppCustomChipWidget( + backgroundColor: AppColors.lightGreenButtonColor, + textColor: AppColors.textGreenColor, + padding: EdgeInsets.all(0), + labelText: medicalFileVM.patientVaccineList[index].vaccineName).toShimmer2(isShow: false, width: 16.h, ), + SizedBox(height:10.h), (medicalFileVM.patientVaccineList[index].doctorName).toString().toText16(isBold: true).toShimmer2(isShow: false), SizedBox(height: 8.h), Wrap( @@ -148,7 +163,7 @@ class _VaccineListPageState extends State { AppCustomChipWidget( icon: AppAssets.doctor_calendar_icon, labelText: DateUtil.formatDateToDate(DateUtil.convertStringToDate(medicalFileVM.patientVaccineList[index].vaccinationDate), false)), - AppCustomChipWidget(labelText: medicalFileVM.patientVaccineList[index].vaccineName).toShimmer2(isShow: false, width: 16.h), + AppCustomChipWidget(labelText: medicalFileVM.patientVaccineList[index].clinicName).toShimmer2(isShow: false, width: 16.h), AppCustomChipWidget(labelText: medicalFileVM.patientVaccineList[index].projectName).toShimmer2(isShow: false, width: 16.h), ], diff --git a/lib/presentation/medical_file/widgets/health_tools_card.dart b/lib/presentation/medical_file/widgets/health_tools_card.dart new file mode 100644 index 00000000..a66d2471 --- /dev/null +++ b/lib/presentation/medical_file/widgets/health_tools_card.dart @@ -0,0 +1,75 @@ +import 'package:flutter/material.dart'; +import 'package:hmg_patient_app_new/core/utils/size_utils.dart'; +import 'package:hmg_patient_app_new/core/utils/utils.dart'; +import 'package:hmg_patient_app_new/extensions/string_extensions.dart'; +import 'package:hmg_patient_app_new/extensions/widget_extensions.dart'; +import 'package:hmg_patient_app_new/theme/colors.dart'; + +class HealthToolsCard extends StatelessWidget { + final String label; + final Color textColor; + final String svgIcon; + final double? iconSize; + final Color? iconColor; + + const HealthToolsCard({ + super.key, + required this.label, + required this.textColor, + required this.svgIcon, + this.iconSize, + this.iconColor, + }); + + @override + Widget build(BuildContext context) { + final iconS = iconSize ?? 24.w; + + return Container( + decoration: RoundedRectangleBorder().toSmoothCornerDecoration( + color: AppColors.whiteColor, + borderRadius: 20.r, + hasShadow: false, + ), + padding: EdgeInsets.all(12.w), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + mainAxisAlignment: MainAxisAlignment.center, + mainAxisSize: MainAxisSize.min, + children: [ + Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + // Icon container with white background and border + Container( + decoration: RoundedRectangleBorder().toSmoothCornerDecoration( + color: AppColors.whiteColor, + borderRadius: 12.r, + side: BorderSide( + color: AppColors.borderOnlyColor.withValues(alpha: 0.1), + width: 1, + ), + ), + height: 48.w, + width: 48.w, + child: Center( + child: Utils.buildSvgWithAssets( + icon: svgIcon, + width: iconS, + height: iconS, + fit: BoxFit.contain, + applyThemeColor: false, + iconColor: iconColor, + ), + ), + ), + SizedBox(height: 6.h), + label.toText13(color: textColor, isBold: true, maxLine: 2), + ], + ), + ], + ), + ); + } +} + diff --git a/lib/presentation/medical_file/widgets/health_tracker_menu_card.dart b/lib/presentation/medical_file/widgets/health_tracker_menu_card.dart new file mode 100644 index 00000000..6284a827 --- /dev/null +++ b/lib/presentation/medical_file/widgets/health_tracker_menu_card.dart @@ -0,0 +1,86 @@ +import 'package:flutter/material.dart'; +import 'package:hmg_patient_app_new/core/app_assets.dart'; +import 'package:hmg_patient_app_new/core/app_state.dart'; +import 'package:hmg_patient_app_new/core/dependencies.dart'; +import 'package:hmg_patient_app_new/core/utils/size_utils.dart'; +import 'package:hmg_patient_app_new/core/utils/utils.dart'; +import 'package:hmg_patient_app_new/extensions/string_extensions.dart'; +import 'package:hmg_patient_app_new/extensions/widget_extensions.dart'; +import 'package:hmg_patient_app_new/theme/colors.dart'; + +class HealthTrackerMenuCard extends StatelessWidget { + final String label; + final Color textColor; + final Color backgroundColor; + final String svgIcon; + final double? iconSize; + final Color? iconColor; + final Color iconBgColor; + + const HealthTrackerMenuCard({ + super.key, + required this.label, + required this.textColor, + required this.backgroundColor, + required this.svgIcon, + required this.iconBgColor, + this.iconSize, + this.iconColor, + }); + + @override + Widget build(BuildContext context) { + final iconS = iconSize ?? 22.w; + return Container( + decoration: RoundedRectangleBorder().toSmoothCornerDecoration( + color: backgroundColor, + borderRadius: 20.r, + hasShadow: false + ), + padding: EdgeInsets.all(16.w), + child: Row( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Container( + decoration: RoundedRectangleBorder().toSmoothCornerDecoration( + color: iconBgColor, + borderRadius: 10.r + ), + height: 40.w, + width: 40.w, + child: Utils.buildSvgWithAssets( + icon: svgIcon, + fit: BoxFit.none, + height: iconS, + width: iconS, + applyThemeColor: false, + iconColor: iconColor, + ), + ), + SizedBox(width: 12.w), + Expanded( + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + mainAxisAlignment: MainAxisAlignment.center, + children: [ + label.toText14(color: textColor, isBold: true), + ], + ), + ), + SizedBox(width: 12.w), + Transform.flip( + flipX: getIt.get().isArabic(), + child: Utils.buildSvgWithAssets( + icon: AppAssets.arrowRight, + width: 24.w, + height: 24.h, + fit: BoxFit.contain, + iconColor: AppColors.textColor, + ), + ), + ], + ), + ); + } +} + 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 4c1a72b7..13ea39dd 100644 --- a/lib/presentation/medical_file/widgets/medical_file_appointment_card.dart +++ b/lib/presentation/medical_file/widgets/medical_file_appointment_card.dart @@ -44,7 +44,7 @@ class MedicalFileAppointmentCard extends StatelessWidget { 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) + .toText12(color: AppointmentType.isArrived(patientAppointmentHistoryResponseModel) ? AppColors.textColor : AppColors.primaryRedColor, isBold: true, isEnglishOnly: true) .paddingSymmetrical(8.w, 0), ), icon: AppointmentType.isArrived(patientAppointmentHistoryResponseModel) ? AppAssets.appointment_calendar_icon : AppAssets.alarm_clock_icon, @@ -76,7 +76,7 @@ class MedicalFileAppointmentCard extends StatelessWidget { children: [ (patientAppointmentHistoryResponseModel.doctorNameObj ?? "").toText14(isBold: true, maxlines: 1, isEnglishOnly: !Utils.isArabicText(patientAppointmentHistoryResponseModel.doctorNameObj ?? "")).toShimmer2(isShow: myAppointmentsViewModel.isMyAppointmentsLoading), (patientAppointmentHistoryResponseModel.clinicName ?? "") - .toText12(maxLine: 1, fontWeight: FontWeight.w500, color: AppColors.greyTextColor) + .toText12(maxLine: 1, isBold: true, color: AppColors.greyTextColor) .toShimmer2(isShow: myAppointmentsViewModel.isMyAppointmentsLoading), ], ), @@ -108,7 +108,7 @@ class MedicalFileAppointmentCard extends StatelessWidget { borderColor: AppointmentType.getNextActionButtonColor(patientAppointmentHistoryResponseModel.nextAction).withOpacity(0.01), textColor: AppointmentType.getNextActionTextColor(patientAppointmentHistoryResponseModel.nextAction), fontSize: 14.f, - fontWeight: FontWeight.w500, + fontWeight: FontWeight.w600, borderRadius: 12.r, padding: EdgeInsets.symmetric(horizontal: 10.w), height: 40.h, @@ -118,8 +118,13 @@ class MedicalFileAppointmentCard extends StatelessWidget { ).toShimmer2(isShow: myAppointmentsViewModel.isMyAppointmentsLoading), ), SizedBox(width: 8.w), - Expanded( - flex: 2, + ((((patientAppointmentHistoryResponseModel.isLiveCareAppointment ?? false) || + (patientAppointmentHistoryResponseModel.isExecludeDoctor ?? false) || + !Utils.isClinicAllowedForRebook(patientAppointmentHistoryResponseModel.clinicID ?? 0))) && + AppointmentType.isArrived(patientAppointmentHistoryResponseModel)) + ? SizedBox.shrink() + : Expanded( + flex: 2, child: Container( height: 40.h, width: 40.w, @@ -163,40 +168,75 @@ class MedicalFileAppointmentCard extends StatelessWidget { } Widget getArrivedAppointmentButton(BuildContext context) { - return DateTime.now().difference(DateUtil.convertStringToDate(patientAppointmentHistoryResponseModel.appointmentDate)).inDays <= 15 - ? CustomButton( - text: LocaleKeys.askDoctor.tr(context: context), - onPressed: () { - onAskDoctorTap(); - }, - 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: 40.h, - icon: AppAssets.ask_doctor_icon, - iconColor: AppColors.primaryRedColor, - iconSize: 16.h, + if ((((patientAppointmentHistoryResponseModel.isLiveCareAppointment ?? false) || + (patientAppointmentHistoryResponseModel.isExecludeDoctor ?? false) || + !Utils.isClinicAllowedForRebook(patientAppointmentHistoryResponseModel.clinicID ?? 0))) && + AppointmentType.isArrived(patientAppointmentHistoryResponseModel)) { + return CustomButton( + text: LocaleKeys.viewDetails.tr(context: context), + onPressed: () { + Navigator.of(context) + .push( + CustomPageRoute( + page: AppointmentDetailsPage(patientAppointmentHistoryResponseModel: patientAppointmentHistoryResponseModel), + ), ) - : CustomButton( - text: LocaleKeys.rebook.tr(context: context), - onPressed: () { - onRescheduleTap(); - }, - backgroundColor: AppColors.greyColor, - borderColor: AppColors.greyColor, - textColor: AppColors.blackColor, - fontSize: 14.f, - fontWeight: FontWeight.w500, - borderRadius: 12.r, - padding: EdgeInsets.symmetric(horizontal: 10.w), - height: 40.h, - icon: AppAssets.rebook_appointment_icon, - iconColor: AppColors.blackColor, - iconSize: 16.h, - ); + .then((_) { + myAppointmentsViewModel.initAppointmentsViewModel(); + myAppointmentsViewModel.getPatientAppointments(true, false); + }); + }, + backgroundColor: AppColors.secondaryLightRedColor, + borderColor: AppColors.secondaryLightRedColor, + textColor: AppColors.primaryRedColor, + fontSize: (isFoldable || isTablet) ? 12.f : 14.f, + fontWeight: FontWeight.w600, + borderRadius: 12.r, + padding: EdgeInsets.symmetric(horizontal: 10.w), + // height: isTablet || isFoldable ? 46.h : 40.h, + height: 40.h, + icon: null, + iconColor: AppColors.primaryRedColor, + iconSize: 16.h, + ); + } else { + return + // DateTime.now().difference(DateUtil.convertStringToDate(patientAppointmentHistoryResponseModel.appointmentDate)).inDays <= 15 + // ? CustomButton( + // text: LocaleKeys.askDoctor.tr(context: context), + // onPressed: () { + // onAskDoctorTap(); + // }, + // backgroundColor: AppColors.secondaryLightRedColor, + // borderColor: AppColors.secondaryLightRedColor, + // textColor: AppColors.primaryRedColor, + // fontSize: 14.f, + // fontWeight: FontWeight.w600, + // borderRadius: 12.r, + // padding: EdgeInsets.symmetric(horizontal: 10.w), + // height: 40.h, + // icon: AppAssets.ask_doctor_icon, + // iconColor: AppColors.primaryRedColor, + // iconSize: 16.h, + // ) + // : + CustomButton( + text: LocaleKeys.rebook.tr(context: context), + onPressed: () { + onRescheduleTap(); + }, + backgroundColor: AppColors.greyColor, + borderColor: AppColors.greyColor, + textColor: AppColors.blackColor, + fontSize: 14.f, + fontWeight: FontWeight.w600, + borderRadius: 12.r, + padding: EdgeInsets.symmetric(horizontal: 10.w), + height: 40.h, + icon: AppAssets.rebook_appointment_icon, + iconColor: AppColors.blackColor, + iconSize: 16.h, + ); + } } } diff --git a/lib/presentation/medical_file/widgets/medical_file_card.dart b/lib/presentation/medical_file/widgets/medical_file_card.dart index c2551d30..a79d8aa9 100644 --- a/lib/presentation/medical_file/widgets/medical_file_card.dart +++ b/lib/presentation/medical_file/widgets/medical_file_card.dart @@ -11,6 +11,8 @@ class MedicalFileCard extends StatelessWidget { final String svgIcon; final double? iconSize; final bool isLargeText; + final bool isLocked; + final Color? iconColor; const MedicalFileCard({ super.key, @@ -20,6 +22,8 @@ class MedicalFileCard extends StatelessWidget { this.svgIcon = "", this.iconSize, this.isLargeText = false, + this.isLocked = false, + this.iconColor, }); @override @@ -37,11 +41,28 @@ class MedicalFileCard extends StatelessWidget { mainAxisAlignment: MainAxisAlignment.center, mainAxisSize: MainAxisSize.min, children: [ - Utils.buildSvgWithAssets(icon: svgIcon, width: iconS, height: iconS, fit: BoxFit.contain, applyThemeColor: false), - SizedBox(height: 8.h), - isLargeText ? label.toText13(color: textColor, isBold: true, maxLine: 2) : label.toText11(color: textColor, isBold: true, maxLine: 2), + Opacity( + opacity: isLocked ? 0.4 : 1.0, + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Utils.buildSvgWithAssets( + icon: svgIcon, + width: iconS, + height: iconS, + fit: BoxFit.contain, + applyThemeColor: false, + iconColor: iconColor, + ), + SizedBox(height: 8.h), + isLargeText ? label.toText13(color: textColor, isBold: true, maxLine: 2) : label.toText11(color: textColor, isBold: true, maxLine: 2), + ], + ), + ), ], ), ); } } + + 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 18bd2e69..a8edeae3 100644 --- a/lib/presentation/medical_file/widgets/patient_sick_leave_card.dart +++ b/lib/presentation/medical_file/widgets/patient_sick_leave_card.dart @@ -21,6 +21,8 @@ import 'package:hmg_patient_app_new/widgets/routes/custom_page_route.dart'; import 'package:open_filex/open_filex.dart'; import 'package:provider/provider.dart'; +import 'dart:ui' as ui; + class PatientSickLeaveCard extends StatelessWidget { PatientSickLeaveCard({super.key, required this.patientSickLeavesResponseModel, this.isLoading = false, this.isSickLeaveListPage = false}); @@ -72,11 +74,15 @@ class PatientSickLeaveCard extends StatelessWidget { spacing: 3.h, runSpacing: 4.h, children: [ - AppCustomChipWidget( - icon: AppAssets.doctor_calendar_icon, - labelText: DateUtil.formatDateToDate(DateUtil.convertStringToDate(patientSickLeavesResponseModel.appointmentDate), false), - labelPadding: EdgeInsetsDirectional.only(start: -4.h, end: 8.h), - ).toShimmer2(isShow: isLoading), + Directionality( + textDirection: ui.TextDirection.ltr, + child: AppCustomChipWidget( + icon: AppAssets.doctor_calendar_icon, + labelText: DateUtil.formatDateToDate(DateUtil.convertStringToDate(patientSickLeavesResponseModel.appointmentDate), false), + labelPadding: EdgeInsetsDirectional.only(start: -4.h, end: 8.h), + isEnglishOnly: true, + ).toShimmer2(isShow: isLoading), + ), AppCustomChipWidget(labelText: isLoading ? "Pending Activation" : patientSickLeavesResponseModel.clinicName!).toShimmer2(isShow: isLoading), ], ), @@ -118,7 +124,7 @@ class PatientSickLeaveCard extends StatelessWidget { borderColor: AppColors.secondaryLightRedColor, textColor: AppColors.primaryRedColor, fontSize: 14, - fontWeight: FontWeight.w500, + isBold: true, borderRadius: 12, padding: EdgeInsets.fromLTRB(10, 0, 10, 0), height: 40.h, diff --git a/lib/presentation/medical_report/medical_reports_page.dart b/lib/presentation/medical_report/medical_reports_page.dart index 9dccdf79..f1b55e02 100644 --- a/lib/presentation/medical_report/medical_reports_page.dart +++ b/lib/presentation/medical_report/medical_reports_page.dart @@ -63,7 +63,7 @@ class _MedicalReportsPageState extends State { // borderColor: medicalFileVM.isMedicalReportsSortByClinic ? AppColors.primaryRedColor : AppColors.textColor.withValues(alpha: 0.2), // textColor: medicalFileVM.isMedicalReportsSortByClinic ? AppColors.primaryRedColor : AppColors.blackColor, // fontSize: 12, - // fontWeight: FontWeight.w500, + // isBold: true, // borderRadius: 10, // padding: EdgeInsets.fromLTRB(10, 0, 10, 0), // height: 40.h, @@ -78,7 +78,7 @@ class _MedicalReportsPageState extends State { // borderColor: medicalFileVM.isMedicalReportsSortByClinic ? AppColors.textColor.withValues(alpha: 0.2) : AppColors.primaryRedColor, // textColor: medicalFileVM.isMedicalReportsSortByClinic ? AppColors.blackColor : AppColors.primaryRedColor, // fontSize: 12, - // fontWeight: FontWeight.w500, + // isBold: true, // borderRadius: 10, // padding: EdgeInsets.fromLTRB(10, 0, 10, 0), // height: 40.h, @@ -100,7 +100,7 @@ class _MedicalReportsPageState extends State { borderColor: medicalFileVM.selectedMedicalReportsTabIndex == 0 ? AppColors.primaryRedColor : AppColors.textColor.withValues(alpha: 0.2), textColor: medicalFileVM.selectedMedicalReportsTabIndex == 0 ? AppColors.primaryRedColor : AppColors.blackColor, fontSize: 12, - fontWeight: FontWeight.w500, + isBold: true, borderRadius: 10, padding: EdgeInsets.fromLTRB(10, 0, 10, 0), height: 40.h, @@ -118,7 +118,7 @@ class _MedicalReportsPageState extends State { borderColor: medicalFileVM.selectedMedicalReportsTabIndex == 1 ? AppColors.primaryRedColor : AppColors.textColor.withValues(alpha: 0.2), textColor: medicalFileVM.selectedMedicalReportsTabIndex == 1 ? AppColors.primaryRedColor : AppColors.blackColor, fontSize: 12, - fontWeight: FontWeight.w500, + isBold: true, borderRadius: 10, padding: EdgeInsets.fromLTRB(10, 0, 10, 0), height: 40.h, @@ -136,7 +136,7 @@ class _MedicalReportsPageState extends State { borderColor: medicalFileVM.selectedMedicalReportsTabIndex == 2 ? AppColors.primaryRedColor : AppColors.textColor.withValues(alpha: 0.2), textColor: medicalFileVM.selectedMedicalReportsTabIndex == 2 ? AppColors.primaryRedColor : AppColors.blackColor, fontSize: 12, - fontWeight: FontWeight.w500, + isBold: true, borderRadius: 10, padding: EdgeInsets.fromLTRB(10, 0, 10, 0), height: 40.h, @@ -200,7 +200,7 @@ class _MedicalReportsPageState extends State { borderColor: AppColors.greyColor, textColor: AppColors.blackColor, fontSize: 10, - fontWeight: FontWeight.w500, + isBold: true, borderRadius: 8, padding: EdgeInsets.fromLTRB(10, 0, 10, 0), height: 30.h, @@ -248,7 +248,7 @@ class _MedicalReportsPageState extends State { fit: BoxFit.fill, ).circle(100), SizedBox(width: 8.h), - Expanded(child: report.doctorName!.toText14(weight: FontWeight.w500)), + Expanded(child: report.doctorName!.toText14(isBold: true)), ], ), SizedBox(height: 8.h), @@ -295,7 +295,7 @@ class _MedicalReportsPageState extends State { borderColor: AppColors.secondaryLightRedColor, textColor: AppColors.primaryRedColor, fontSize: 14, - fontWeight: FontWeight.w500, + fontWeight: FontWeight.w600, borderRadius: 12, padding: EdgeInsets.fromLTRB(10, 0, 10, 0), height: 40.h, @@ -372,7 +372,7 @@ class _MedicalReportsPageState extends State { borderColor: AppColors.primaryRedColor, textColor: Colors.white, fontSize: 16.f, - fontWeight: FontWeight.w500, + fontWeight: FontWeight.w600, borderRadius: 12.r, height: 46.h, icon: AppAssets.requests, @@ -419,7 +419,7 @@ class _MedicalReportsPageState extends State { callBackFunc: () {}, isFullScreen: false, isCloseButtonVisible: true, - isAutoDismiss: true + isAutoDismiss: false ); } } 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 282a2d60..49cb0e3a 100644 --- a/lib/presentation/medical_report/widgets/patient_medical_report_card.dart +++ b/lib/presentation/medical_report/widgets/patient_medical_report_card.dart @@ -93,7 +93,7 @@ class PatientMedicalReportCard extends StatelessWidget { borderColor: AppColors.secondaryLightRedColor, textColor: AppColors.primaryRedColor, fontSize: 14, - fontWeight: FontWeight.w500, + fontWeight: FontWeight.w600, borderRadius: 12.h, height: 40.h, icon: AppAssets.download_1, @@ -112,7 +112,7 @@ class PatientMedicalReportCard extends StatelessWidget { borderColor: AppColors.primaryRedColor, textColor: AppColors.whiteColor, fontSize: 14, - fontWeight: FontWeight.w500, + fontWeight: FontWeight.w600, borderRadius: 12.h, height: 40.h, icon: AppAssets.download_1, diff --git a/lib/presentation/monthly_report/monthly_report.dart b/lib/presentation/monthly_report/monthly_report.dart index 998d7f48..1bbaf178 100644 --- a/lib/presentation/monthly_report/monthly_report.dart +++ b/lib/presentation/monthly_report/monthly_report.dart @@ -79,10 +79,10 @@ class MonthlyReport extends StatelessWidget { Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ - LocaleKeys.email.tr(context: context).toText12(color: AppColors.greyTextColor, fontWeight: FontWeight.w500), + LocaleKeys.email.tr(context: context).toText12(color: AppColors.greyTextColor, isBold: true), SizedBox( width: MediaQuery.of(context).size.width * 0.7, - child: "${appState.getAuthenticatedUser()!.emailAddress}".toText16(color: AppColors.textColor, weight: FontWeight.w500, maxlines: 2), + child: "${appState.getAuthenticatedUser()!.emailAddress}".toText16(color: AppColors.textColor, isBold: true, maxlines: 2), ), ], ), @@ -98,7 +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: LocaleKeys.monthlyHealthSummaryReportDisclaimer.tr(context: context).toText10(weight: FontWeight.w500, color: AppColors.greyTextColorLight), + child: LocaleKeys.monthlyHealthSummaryReportDisclaimer.tr(context: context).toText10(isBold: true, color: AppColors.greyTextColorLight), ), ], ), @@ -172,7 +172,7 @@ class MonthlyReport extends StatelessWidget { borderColor: AppColors.primaryRedColor, textColor: AppColors.whiteColor, fontSize: 16.f, - fontWeight: FontWeight.w500, + fontWeight: FontWeight.w600, borderRadius: 12.r, height: 46.h, iconColor: AppColors.whiteColor, diff --git a/lib/presentation/my_family/my_family.dart b/lib/presentation/my_family/my_family.dart index 66b31cfc..d25154c7 100644 --- a/lib/presentation/my_family/my_family.dart +++ b/lib/presentation/my_family/my_family.dart @@ -67,7 +67,7 @@ class _FamilyMedicalScreenState extends State { icon: AppAssets.add_icon, fontSize: 16.f, borderRadius: 12.r, - fontWeight: FontWeight.w500)) + isBold: true)) .paddingOnly(bottom: 20.h) : SizedBox(), child: Column( diff --git a/lib/presentation/my_family/widget/family_cards.dart b/lib/presentation/my_family/widget/family_cards.dart index a621168f..c8553207 100644 --- a/lib/presentation/my_family/widget/family_cards.dart +++ b/lib/presentation/my_family/widget/family_cards.dart @@ -1,4 +1,5 @@ import 'dart:async'; +import 'dart:convert'; import 'package:easy_localization/easy_localization.dart'; import 'package:flutter/material.dart'; @@ -22,6 +23,7 @@ import 'package:hmg_patient_app_new/widgets/buttons/custom_button.dart'; import 'package:hmg_patient_app_new/widgets/chip/app_custom_chip_widget.dart'; import 'package:hmg_patient_app_new/widgets/chip/custom_chip_widget.dart'; import 'package:hmg_patient_app_new/widgets/common_bottom_sheet.dart'; +import 'package:hmg_patient_app_new/widgets/user_avatar_widget.dart'; import 'package:provider/provider.dart'; class FamilyCards extends StatefulWidget { @@ -65,6 +67,29 @@ class _FamilyCardsState extends State { super.initState(); } + + double _calculateAspectRatio(BuildContext context) { + final screenWidth = MediaQuery.of(context).size.width; + final itemWidth = (screenWidth - 32.w - 10.w) / 2; + double itemHeight = 0; + itemHeight += 30.h; // 15.h top + 15.h bottom + itemHeight += 70.h; + itemHeight += 8.h; + itemHeight += 20.h; + + if (widget.isShowDetails) { + itemHeight += 4.h; + itemHeight += 30.h; + itemHeight += 8.h; + } else { + itemHeight += 4.h; + } + + itemHeight += 70.h; + final aspectRatio = itemWidth / itemHeight; + return aspectRatio; + } + @override Widget build(BuildContext context) { widget.profileViewList = []; @@ -79,8 +104,7 @@ class _FamilyCardsState extends State { children: [ Utils.buildSvgWithAssets(icon: AppAssets.alertSquare), SizedBox(width: 8.h), - LocaleKeys.whoCanViewMyMedicalFile.tr(context: context).toText14(color: AppColors.textColor, isUnderLine: true, weight: FontWeight.w500) - .onPress(() { + LocaleKeys.whoCanViewMyMedicalFile.tr(context: context).toText14(color: AppColors.textColor, isUnderLine: true, isBold: true).onPress(() { dialogService.showFamilyBottomSheetWithoutHWithChild( label: LocaleKeys.manageFiles.tr(context: context), message: "", @@ -136,18 +160,11 @@ class _FamilyCardsState extends State { ? AppColors.textGreenColor : AppColors.alertColor), SizedBox(height: 8.h), - Wrap( - alignment: WrapAlignment.start, - crossAxisAlignment: WrapCrossAlignment.start, - runAlignment: WrapAlignment.start, - spacing: 0.h, - children: [ - (profile.patientName ?? "").toText14(isBold: false, isCenter: false, maxlines: 1, weight: FontWeight.w600), - (getStatusTextByRequest( - FamilyFileEnum.values.firstWhere((e) => e.toInt == profile.status), profile.isRequestFromMySide ?? false)) - .toText12( - isBold: false, isCenter: false, maxLine: 1, fontWeight: FontWeight.w500, color: AppColors.greyTextColor), - ]), + Wrap(alignment: WrapAlignment.start, crossAxisAlignment: WrapCrossAlignment.start, runAlignment: WrapAlignment.start, spacing: 0.h, children: [ + (profile.patientName ?? "").toText14(isBold: true, isCenter: false, maxlines: 1), + (getStatusTextByRequest(FamilyFileEnum.values.firstWhere((e) => e.toInt == profile.status), profile.isRequestFromMySide ?? false)) + .toText12(isCenter: false, maxLine: 1, isBold: true, color: AppColors.greyTextColor), + ]), SizedBox(height: 8.h), CustomChipWidget( height: 30.h, @@ -181,15 +198,20 @@ class _FamilyCardsState extends State { Row( crossAxisAlignment: CrossAxisAlignment.start, children: [ - Image.asset(appState.getAuthenticatedUser()?.gender == 1 ? AppAssets.maleImg : AppAssets.femaleImg, width: 56.w, height: 56.h), + UserAvatarWidget( + width: 56.w, + height: 56.h, + fit: BoxFit.cover, + isCircular: true, + ), SizedBox(width: 8.w), Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ SizedBox( width: MediaQuery.of(context).size.width * 0.6, - child: "${appState.getAuthenticatedUser()!.firstName} ${appState.getAuthenticatedUser()!.lastName}" - .toText18(isBold: true, weight: FontWeight.w600, textOverflow: TextOverflow.ellipsis, maxlines: 2), + child: + "${appState.getAuthenticatedUser()!.firstName} ${appState.getAuthenticatedUser()!.lastName}".toText18(isBold: true, textOverflow: TextOverflow.ellipsis, maxlines: 2), ), SizedBox(height: 4.h), Wrap( @@ -199,7 +221,7 @@ class _FamilyCardsState extends State { children: [ AppCustomChipWidget( icon: AppAssets.file_icon, - richText: "${LocaleKeys.fileno.tr(context: context)}: ${appState.getAuthenticatedUser()!.patientId}".toText10(isEnglishOnly: true), + richText: "${LocaleKeys.fileno.tr(context: context)}: ${appState.getAuthenticatedUser()!.patientId}".toText10(isBold: true), labelPadding: EdgeInsetsDirectional.only(start: -4.w, end: 6.w), ), AppCustomChipWidget( @@ -311,14 +333,17 @@ class _FamilyCardsState extends State { crossAxisCount: 2, crossAxisSpacing: 10.w, mainAxisSpacing: 10.h, - childAspectRatio: widget.isShowDetails ? 0.56.h : 0.74.h, + childAspectRatio: _calculateAspectRatio(context), ), padding: EdgeInsets.only(bottom: 20.h), itemBuilder: (context, index) { final profile = widget.profileViewList![index]; final isActive = (profile.responseId == appState.getAuthenticatedUser()?.patientId); final isParentUser = appState.getAuthenticatedUser()?.isParentUser ?? false; - final canSwitch = isParentUser || (!isParentUser && profile.responseId == appState.getSuperUserID); + final isSuperUser = appState.getUserSwitchPayloads; + // TODO: canSwitch functionality is currently disabled - set to true to allow all switches + // Original logic: final canSwitch = isParentUser || (!isParentUser && profile.responseId == appState.getSuperUserID); + final canSwitch = true; // Disabled - allows all users to switch return Container( padding: EdgeInsets.symmetric(vertical: 15.h, horizontal: 15.h), decoration: RoundedRectangleBorder().toSmoothCornerDecoration(color: AppColors.whiteColor, borderRadius: 24.r), @@ -329,32 +354,32 @@ class _FamilyCardsState extends State { Column( mainAxisSize: MainAxisSize.min, children: [ - Utils.buildImgWithAssets( - icon: profile.gender == null - ? AppAssets.dummyUser - : profile.gender == 1 - ? ((profile.age ?? 0) < 7 ? AppAssets.babyBoyImg : AppAssets.maleImg) - : (profile.age! < 7 ? AppAssets.babyGirlImg : AppAssets.femaleImg), + UserAvatarWidget( width: 72.h, height: 70.h, + gender: profile.gender, + age: profile.age, + fit: BoxFit.cover, + customProfileImageData: "", // Prevent showing logged-in user's image for family members + isCircular: true, ), SizedBox(height: 8.h), - (profile.patientName ?? "Unknown").toText14(isBold: false, isCenter: true, maxlines: 1, weight: FontWeight.w600), - SizedBox(height: 8.h), - CustomChipWidget( - chipType: ChipTypeEnum.alert, - backgroundColor: AppColors.lightGrayBGColor, - chipText: "Relation: ${profile.relationship ?? " N/A"}", - iconAsset: AppAssets.heart, - isShowBorder: false, - borderRadius: 8.h, - textColor: AppColors.textColor), + (profile.patientName ?? "Unknown").toText14(isBold: true, isCenter: true, maxlines: 1), + // SizedBox(height: 8.h), + // CustomChipWidget( + // chipType: ChipTypeEnum.alert, + // backgroundColor: AppColors.lightGrayBGColor, + // chipText: "${LocaleKeys.relationship.tr(context: context)}: ${profile.relationship ?? " N/A"}", + // iconAsset: AppAssets.heart, + // isShowBorder: false, + // borderRadius: 8.h, + // textColor: AppColors.textColor), widget.isShowDetails ? SizedBox(height: 4.h) : SizedBox(), widget.isShowDetails ? CustomChipWidget( chipType: ChipTypeEnum.alert, backgroundColor: AppColors.lightGrayBGColor, - chipText: "Age: ${profile.age ?? "N/A"} Years", + chipText: "${LocaleKeys.age.tr(context: context)}: ${profile.age ?? "N/A"} ${LocaleKeys.years.tr(context: context)}", isShowBorder: false, borderRadius: 8.h, textColor: AppColors.textColor, @@ -468,15 +493,15 @@ class _FamilyCardsState extends State { Wrap( 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( + (profile.patientName ?? "").toText14(isBold: true, isCenter: true, maxlines: 1), + SizedBox( + width: 2.w, + ), + (getStatusTextByRequest(FamilyFileEnum.values.firstWhere((e) => e.toInt == profile.status), profile.isRequestFromMySide ?? false)).toText14( isBold: false, isCenter: true, maxlines: 1, - weight: FontWeight.w500, + weight: FontWeight.w600, color: AppColors.greyTextColor, ), ], diff --git a/lib/presentation/my_invoices/my_invoices_details_page.dart b/lib/presentation/my_invoices/my_invoices_details_page.dart index a605160d..9f0370a0 100644 --- a/lib/presentation/my_invoices/my_invoices_details_page.dart +++ b/lib/presentation/my_invoices/my_invoices_details_page.dart @@ -18,6 +18,7 @@ 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'; import 'package:hmg_patient_app_new/widgets/common_bottom_sheet.dart'; import 'package:hmg_patient_app_new/widgets/loader/bottomsheet_loader.dart'; +import 'package:open_filex/open_filex.dart'; import 'package:provider/provider.dart'; import 'dart:ui' as ui; @@ -45,7 +46,42 @@ class _MyInvoicesDetailsPageState extends State { Expanded( child: CollapsingListView( title: LocaleKeys.invoiceDetails.tr(context: context), - sendEmail: () async { + downloadInvoice: Utils.isVidaPlusProject(widget.getInvoiceDetailsResponseModel.projectID ?? 0) ? null : () { + LoaderBottomSheet.showLoader(loadingText: LocaleKeys.loadingText.tr(context: context)); + myInvoicesViewModel.downloadInvoicePDF( + setupId: widget.getInvoicesListResponseModel.setupId!, + invoiceNo: widget.getInvoicesListResponseModel.invoiceNo!, + projectID: widget.getInvoicesListResponseModel.projectId!, + onError: (err) { + LoaderBottomSheet.hideLoader(); + showCommonBottomSheetWithoutHeight( + context, + child: Utils.getErrorWidget(loadingText: err), + callBackFunc: () {}, + isFullScreen: false, + isCloseButtonVisible: true, + ); + }, + onSuccess: (value) async { + LoaderBottomSheet.hideLoader(); + if (myInvoicesViewModel.downloadInvoicePDFBase64!.isNotEmpty) { + String path = await Utils.createFileFromString(myInvoicesViewModel.downloadInvoicePDFBase64!, "pdf"); + try { + OpenFilex.open(path); + } catch (ex) { + showCommonBottomSheetWithoutHeight( + context, + child: Utils.getErrorWidget(loadingText: "Cannot open file"), + callBackFunc: () {}, + isFullScreen: false, + isCloseButtonVisible: true, + ); + } + } + }, + ); + }, + sendEmail: Utils.isVidaPlusProject(widget.getInvoiceDetailsResponseModel.projectID ?? 0) ? () async { LoaderBottomSheet.showLoader(loadingText: LocaleKeys.sendingEmailPleaseWait.tr(context: context)); await myInvoicesViewModel.sendInvoiceEmail( appointmentNo: widget.getInvoiceDetailsResponseModel.appointmentNo!, @@ -71,7 +107,7 @@ class _MyInvoicesDetailsPageState extends State { isCloseButtonVisible: true, ); }); - }, + } : null, child: SingleChildScrollView( child: Padding( padding: EdgeInsets.all(24.h), @@ -114,7 +150,7 @@ class _MyInvoicesDetailsPageState extends State { Column( children: [ Image.network( - widget.getInvoiceDetailsResponseModel.doctorImageURL!, + widget.getInvoiceDetailsResponseModel.doctorImageURL ?? 'https://hmgwebservices.com/Images/MobileImages/DUBAI/unkown_female.png', width: 63.h, height: 63.h, fit: BoxFit.cover, @@ -126,10 +162,10 @@ class _MyInvoicesDetailsPageState extends State { height: 40.h, decoration: BoxDecoration( color: AppColors.whiteColor, - shape: BoxShape.circle, // Makes the container circular + shape: BoxShape.circle, border: Border.all( - color: AppColors.scaffoldBgColor, // Color of the border - width: 1.5.w, // Width of the border + color: AppColors.scaffoldBgColor, + width: 1.5.w, ), ), child: Column( @@ -137,7 +173,7 @@ class _MyInvoicesDetailsPageState extends State { children: [ Utils.buildSvgWithAssets(icon: AppAssets.rating_icon, width: 15.w, height: 15.h, applyThemeColor: false), SizedBox(height: 2.h), - "${widget.getInvoicesListResponseModel.decimalDoctorRate}".toText11(isBold: true, color: AppColors.textColor), + "${widget.getInvoicesListResponseModel.decimalDoctorRate ?? '0.0'}".toText11(isBold: true, color: AppColors.textColor), ], ), ).circle(100), @@ -149,7 +185,7 @@ class _MyInvoicesDetailsPageState extends State { child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ - (getIt().isArabic() ? widget.getInvoiceDetailsResponseModel.doctorNameN ?? LocaleKeys.doctor.tr(context: context) : widget.getInvoiceDetailsResponseModel.doctorName!).toText16(isBold: true), + (widget.getInvoiceDetailsResponseModel.doctorName ?? LocaleKeys.doctor.tr(context: context)).toText16(isBold: true), SizedBox(height: 8.h), Wrap( direction: Axis.horizontal, @@ -157,29 +193,33 @@ class _MyInvoicesDetailsPageState extends State { runSpacing: 6.h, children: [ AppCustomChipWidget( - labelText: "${LocaleKeys.invoiceNo.tr(context: context)}: ${widget.getInvoiceDetailsResponseModel.invoiceNo!}", + labelText: "${LocaleKeys.invoiceNo.tr(context: context)}: ${widget.getInvoiceDetailsResponseModel.invoiceNo ?? '-'}", labelPadding: EdgeInsetsDirectional.only(start: 6.w, end: 6.w), isEnglishOnly: true, ), AppCustomChipWidget( - labelText: ((getIt().isArabic() ? widget.getInvoiceDetailsResponseModel.clinicDescriptionN : widget.getInvoiceDetailsResponseModel.clinicDescription ?? "")!.length > 15 - ? '${widget.getInvoiceDetailsResponseModel.clinicDescription!.substring(0, 12)}...' - : getIt().isArabic() ? widget.getInvoiceDetailsResponseModel.clinicDescriptionN : widget.getInvoiceDetailsResponseModel.clinicDescription ?? ""), + labelText: () { + final clinicName = getIt().isArabic() + ? (widget.getInvoiceDetailsResponseModel.clinicDescriptionN ?? widget.getInvoiceDetailsResponseModel.clinicDescription ?? LocaleKeys.clinic.tr(context: context)) + : (widget.getInvoiceDetailsResponseModel.clinicDescription ?? widget.getInvoiceDetailsResponseModel.clinicDescriptionN ?? LocaleKeys.clinic.tr(context: context)); + return clinicName.length > 15 ? '${clinicName.substring(0, 12)}...' : clinicName; + }(), labelPadding: EdgeInsetsDirectional.only(start: 4.w, end: 4.w), ), AppCustomChipWidget( - labelText: widget.getInvoiceDetailsResponseModel.projectName!, + labelText: widget.getInvoiceDetailsResponseModel.projectName ?? LocaleKeys.hospital.tr(context: context), labelPadding: EdgeInsetsDirectional.only(start: 6.w, end: 6.w), ), - Directionality( - textDirection: ui.TextDirection.ltr, - child: AppCustomChipWidget( - labelPadding: EdgeInsetsDirectional.only(start: -4.w, end: 6.w), - icon: AppAssets.doctor_calendar_icon, - labelText: DateUtil.formatDateToDate(DateUtil.convertStringToDate(widget.getInvoiceDetailsResponseModel.appointmentDate), false), - isEnglishOnly: true, + if (widget.getInvoiceDetailsResponseModel.appointmentDate != null) + Directionality( + textDirection: ui.TextDirection.ltr, + child: AppCustomChipWidget( + labelPadding: EdgeInsetsDirectional.only(start: -4.w, end: 6.w), + icon: AppAssets.doctor_calendar_icon, + labelText: DateUtil.formatDateToDate(DateUtil.convertStringToDate(widget.getInvoiceDetailsResponseModel.appointmentDate), false), + isEnglishOnly: true, + ), ), - ), ], ), ], @@ -205,22 +245,25 @@ class _MyInvoicesDetailsPageState extends State { children: [ LocaleKeys.insurance.tr(context: context).toText16(isBold: true), SizedBox(height: 16.h), - (getIt().isArabic() ? widget.getInvoiceDetailsResponseModel.groupNameN : widget.getInvoiceDetailsResponseModel.groupName)!.toText14(isBold: true), + (getIt().isArabic() + ? (widget.getInvoiceDetailsResponseModel.groupNameN ?? widget.getInvoiceDetailsResponseModel.groupName ?? LocaleKeys.insurance.tr(context: context)) + : (widget.getInvoiceDetailsResponseModel.groupName ?? widget.getInvoiceDetailsResponseModel.groupNameN ?? LocaleKeys.insurance.tr(context: context)) + ).toText14(isBold: true), Row( children: [ Expanded(child: (widget.getInvoiceDetailsResponseModel.companyName ?? "").toText14(isBold: true)), ], ), SizedBox(height: 12.h), - Row( - children: [ - AppCustomChipWidget( - labelText: "Insurance ID: ${widget.getInvoiceDetailsResponseModel.insuranceID ?? "-"}", - labelPadding: EdgeInsetsDirectional.only(start: 6.w, end: 6.w), - isEnglishOnly: true, - ), - ], - ), + // Row( + // children: [ + // AppCustomChipWidget( + // labelText: "Insurance ID: ${widget.getInvoiceDetailsResponseModel.insuranceID ?? "-"}", + // labelPadding: EdgeInsetsDirectional.only(start: 6.w, end: 6.w), + // isEnglishOnly: true, + // ), + // ], + // ), ], ), ), @@ -244,12 +287,12 @@ class _MyInvoicesDetailsPageState extends State { // Header row Row( children: [ - Expanded(flex: 3, child: LocaleKeys.name.tr().toText12(color: AppColors.textColor, fontWeight: FontWeight.w600)), - Expanded(flex: 1, child: LocaleKeys.quantity.tr().toText12(color: AppColors.textColor, fontWeight: FontWeight.w600, isCenter: true)), + Expanded(flex: 3, child: LocaleKeys.name.tr().toText12(color: AppColors.textColor, isBold: true)), + Expanded(flex: 1, child: LocaleKeys.quantity.tr().toText12(color: AppColors.textColor, isBold: true, isCenter: true)), Expanded( - flex: 2, child: LocaleKeys.price.tr().toText12(color: AppColors.textColor, fontWeight: FontWeight.w600, isCenter: true)), + flex: 2, child: LocaleKeys.price.tr().toText12(color: AppColors.textColor, isBold: true, isCenter: true)), Expanded( - flex: 2, child: LocaleKeys.total.tr().toText12(color: AppColors.textColor, fontWeight: FontWeight.w600, isCenter: true)), + flex: 2, child: LocaleKeys.total.tr().toText12(color: AppColors.textColor, isBold: true, isCenter: true)), ], ), SizedBox(height: 16.h), @@ -261,10 +304,10 @@ class _MyInvoicesDetailsPageState extends State { padding: EdgeInsets.symmetric(vertical: 6.h), child: Row( children: [ - Expanded(flex: 3, child: (consultation.procedureName ?? '-').toText12(fontWeight: FontWeight.w500)), - Expanded(flex: 1, child: '${consultation.quantity ?? '-'}'.toText12(fontWeight: FontWeight.w500, isCenter: true, isEnglishOnly: true)), - Expanded(flex: 2, child: (NumberFormat.decimalPattern().format(consultation.price) ?? '-').toText12(fontWeight: FontWeight.w500, isCenter: true, isEnglishOnly: true)), - Expanded(flex: 2, child: (NumberFormat.decimalPattern().format(consultation.total) ?? '-').toText12(fontWeight: FontWeight.w500, isCenter: true, isEnglishOnly: true)), + Expanded(flex: 3, child: (consultation.procedureName ?? '-').toText12(isBold: true)), + Expanded(flex: 1, child: '${consultation.quantity ?? '-'}'.toText12(isBold: true, isCenter: true, isEnglishOnly: true)), + Expanded(flex: 2, child: (NumberFormat.decimalPattern().format(consultation.totalPatientShare) ?? '-').toText12(isBold: true, isCenter: true, isEnglishOnly: true)), + Expanded(flex: 2, child: (NumberFormat.decimalPattern().format(consultation.totalPatientShare) ?? '-').toText12(isBold: true, isCenter: true, isEnglishOnly: true)), ], ), ), @@ -299,7 +342,7 @@ class _MyInvoicesDetailsPageState extends State { children: [ LocaleKeys.amountBeforeTax.tr(context: context).toText14(isBold: true), Utils.getPaymentAmountWithSymbol( - NumberFormat.decimalPattern().format(widget.getInvoiceDetailsResponseModel.listConsultation!.first.totalShare).toString().toText16(isBold: true, isEnglishOnly: true), AppColors.blackColor, 13, + NumberFormat.decimalPattern().format(widget.getInvoiceDetailsResponseModel.listConsultation!.first.patientShare).toString().toText16(isBold: true, isEnglishOnly: true), AppColors.blackColor, 13, isSaudiCurrency: true), ], ).paddingSymmetrical(24.h, 0.h), @@ -309,7 +352,7 @@ class _MyInvoicesDetailsPageState extends State { LocaleKeys.vat15.tr(context: context).toText14(isBold: true, color: AppColors.greyTextColor), Utils.getPaymentAmountWithSymbol( NumberFormat.decimalPattern() - .format(widget.getInvoiceDetailsResponseModel.listConsultation!.first.totalVATAmount!) + .format(widget.getInvoiceDetailsResponseModel.listConsultation!.first.patientVATAmount!) .toString() .toText14(isBold: true, color: AppColors.greyTextColor, isEnglishOnly: true), AppColors.greyTextColor, @@ -318,25 +361,25 @@ class _MyInvoicesDetailsPageState extends State { ], ).paddingSymmetrical(24.h, 0.h), SizedBox(height: 16.h), - Row( - mainAxisAlignment: MainAxisAlignment.spaceBetween, - children: [ - LocaleKeys.discount.tr(context: context).toText14(isBold: true), - Utils.getPaymentAmountWithSymbol( - NumberFormat.decimalPattern() - .format(widget.getInvoiceDetailsResponseModel.listConsultation!.first.discountAmount!) - .toString() - .toText14(isBold: true, color: AppColors.primaryRedColor, isEnglishOnly: true), - AppColors.primaryRedColor, 13, - isSaudiCurrency: true), - ], - ).paddingSymmetrical(24.h, 0.h), + // Row( + // mainAxisAlignment: MainAxisAlignment.spaceBetween, + // children: [ + // LocaleKeys.discount.tr(context: context).toText14(isBold: true), + // Utils.getPaymentAmountWithSymbol( + // NumberFormat.decimalPattern() + // .format(widget.getInvoiceDetailsResponseModel.listConsultation!.first.discountAmount!) + // .toString() + // .toText14(isBold: true, color: AppColors.primaryRedColor, isEnglishOnly: true), + // AppColors.primaryRedColor, 13, + // isSaudiCurrency: true), + // ], + // ).paddingSymmetrical(24.h, 0.h), Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ LocaleKeys.paid.tr(context: context).toText14(isBold: true), Utils.getPaymentAmountWithSymbol( - NumberFormat.decimalPattern().format(widget.getInvoiceDetailsResponseModel.listConsultation!.first.grandTotal!).toString().toText14(isBold: true, color: AppColors.textColor, isEnglishOnly: true), + NumberFormat.decimalPattern().format(widget.getInvoiceDetailsResponseModel.listConsultation!.first.totalPatientShare!).toString().toText14(isBold: true, color: AppColors.textColor, isEnglishOnly: true), 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 054d2fce..5a8e9c70 100644 --- a/lib/presentation/my_invoices/my_invoices_list.dart +++ b/lib/presentation/my_invoices/my_invoices_list.dart @@ -20,6 +20,7 @@ import 'package:hmg_patient_app_new/widgets/appbar/collapsing_list_view.dart'; import 'package:hmg_patient_app_new/widgets/common_bottom_sheet.dart'; import 'package:hmg_patient_app_new/widgets/loader/bottomsheet_loader.dart'; import 'package:hmg_patient_app_new/widgets/routes/custom_page_route.dart'; +import 'package:open_filex/open_filex.dart'; import 'package:provider/provider.dart'; import '../../widgets/buttons/custom_button.dart'; @@ -134,7 +135,7 @@ class _MyInvoicesListState extends State { borderColor: myInvoicesVM.currentFilter == InvoiceFilterType.all ? AppColors.primaryRedColor : AppColors.textColor.withOpacity(0.2), textColor: myInvoicesVM.currentFilter == InvoiceFilterType.all ? AppColors.primaryRedColor : AppColors.blackColor, fontSize: 12, - fontWeight: FontWeight.w500, + fontWeight: FontWeight.w600, borderRadius: 10, padding: EdgeInsets.fromLTRB(10, 0, 10, 0), height: 40.h, @@ -143,14 +144,14 @@ class _MyInvoicesListState extends State { CustomButton( text: LocaleKeys.hospitals.tr(context: context), onPressed: () { - if (myInvoicesVM.allInvoicesList.isEmpty) return; + if (myInvoicesVM.getOriginalInvoicesList().isEmpty) return; _showHospitalFilterBottomSheet(context); }, backgroundColor: myInvoicesVM.currentFilter == InvoiceFilterType.hospital ? AppColors.bgRedLightColor : AppColors.whiteColor, borderColor: myInvoicesVM.currentFilter == InvoiceFilterType.hospital ? AppColors.primaryRedColor : AppColors.textColor.withOpacity(0.2), textColor: myInvoicesVM.currentFilter == InvoiceFilterType.hospital ? AppColors.primaryRedColor : AppColors.blackColor, fontSize: 12, - fontWeight: FontWeight.w500, + fontWeight: FontWeight.w600, borderRadius: 10, padding: EdgeInsets.fromLTRB(10, 0, 10, 0), height: 40.h, @@ -159,14 +160,14 @@ class _MyInvoicesListState extends State { CustomButton( text: LocaleKeys.clinics.tr(context: context), onPressed: () { - if (myInvoicesVM.allInvoicesList.isEmpty) return; + if (myInvoicesVM.getOriginalInvoicesList().isEmpty) return; _showClinicFilterBottomSheet(context); }, backgroundColor: myInvoicesVM.currentFilter == InvoiceFilterType.clinic ? AppColors.bgRedLightColor : AppColors.whiteColor, borderColor: myInvoicesVM.currentFilter == InvoiceFilterType.clinic ? AppColors.primaryRedColor : AppColors.textColor.withOpacity(0.2), textColor: myInvoicesVM.currentFilter == InvoiceFilterType.clinic ? AppColors.primaryRedColor : AppColors.blackColor, fontSize: 12, - fontWeight: FontWeight.w500, + fontWeight: FontWeight.w600, borderRadius: 10, padding: EdgeInsets.fromLTRB(10, 0, 10, 0), height: 40.h, @@ -175,14 +176,14 @@ class _MyInvoicesListState extends State { CustomButton( text: LocaleKeys.doctors.tr(context: context), onPressed: () { - if (myInvoicesVM.allInvoicesList.isEmpty) return; + if (myInvoicesVM.getOriginalInvoicesList().isEmpty) return; _showDoctorFilterBottomSheet(context); }, backgroundColor: myInvoicesVM.currentFilter == InvoiceFilterType.doctor ? AppColors.bgRedLightColor : AppColors.whiteColor, borderColor: myInvoicesVM.currentFilter == InvoiceFilterType.doctor ? AppColors.primaryRedColor : AppColors.textColor.withOpacity(0.2), textColor: myInvoicesVM.currentFilter == InvoiceFilterType.doctor ? AppColors.primaryRedColor : AppColors.blackColor, fontSize: 12, - fontWeight: FontWeight.w500, + fontWeight: FontWeight.w600, borderRadius: 10, padding: EdgeInsets.fromLTRB(10, 0, 10, 0), height: 40.h, @@ -215,23 +216,36 @@ class _MyInvoicesListState extends State { child: InvoiceListCard( getInvoicesListResponseModel: myInvoicesVM.allInvoicesList[index], onTap: () async { - myInvoicesVM.setInvoiceDetailLoading(); - LoaderBottomSheet.showLoader(loadingText: LocaleKeys.fetchingInvoiceDetails.tr(context: context)); - await myInvoicesVM.getInvoiceDetails( - appointmentNo: myInvoicesVM.allInvoicesList[index].appointmentNo!, + if (Utils.isVidaPlusProject(myInvoicesVM.allInvoicesList[index].projectId ?? 0)) { + LoaderBottomSheet.showLoader(loadingText: LocaleKeys.sendingEmailPleaseWait.tr(context: context)); + await myInvoicesViewModel.sendInvoiceEmail( + appointmentNo: myInvoicesVM.allInvoicesList[index].appointmentNo!, + projectID: myInvoicesVM.allInvoicesList[index].projectId!, + onSuccess: (val) { + LoaderBottomSheet.hideLoader(); + showCommonBottomSheetWithoutHeight(context, + child: Utils.getSuccessWidget(loadingText: LocaleKeys.emailSentSuccessfullyMessage.tr(context: context)), + callBackFunc: () {}, + isFullScreen: false, + isCloseButtonVisible: true, + isAutoDismiss: true); + }, + onError: (err) { + LoaderBottomSheet.hideLoader(); + showCommonBottomSheetWithoutHeight( + context, + child: Utils.getErrorWidget(loadingText: err), + callBackFunc: () {}, + isFullScreen: false, + isCloseButtonVisible: true, + ); + }); + } else { + LoaderBottomSheet.showLoader(loadingText: LocaleKeys.loadingText.tr(context: context)); + myInvoicesViewModel.downloadInvoicePDF( + setupId: myInvoicesVM.allInvoicesList[index].setupId!, invoiceNo: myInvoicesVM.allInvoicesList[index].invoiceNo!, - projectID: myInvoicesVM.allInvoicesList[index].projectID!, - onSuccess: (val) { - LoaderBottomSheet.hideLoader(); - Navigator.of(context).push( - CustomPageRoute( - page: MyInvoicesDetailsPage( - getInvoiceDetailsResponseModel: myInvoicesVM.invoiceDetailsResponseModel, - getInvoicesListResponseModel: myInvoicesVM.allInvoicesList[index], - ), - ), - ); - }, + projectID: myInvoicesVM.allInvoicesList[index].projectId!, onError: (err) { LoaderBottomSheet.hideLoader(); showCommonBottomSheetWithoutHeight( @@ -241,8 +255,56 @@ class _MyInvoicesListState extends State { isFullScreen: false, isCloseButtonVisible: true, ); - }); + }, + onSuccess: (value) async { + LoaderBottomSheet.hideLoader(); + if (myInvoicesViewModel.downloadInvoicePDFBase64!.isNotEmpty) { + String path = await Utils.createFileFromString(myInvoicesViewModel.downloadInvoicePDFBase64!, "pdf"); + try { + OpenFilex.open(path); + } catch (ex) { + showCommonBottomSheetWithoutHeight( + context, + child: Utils.getErrorWidget(loadingText: "Cannot open file"), + callBackFunc: () {}, + isFullScreen: false, + isCloseButtonVisible: true, + ); + } + } + }, + ); + } }, + // onTap: () async { + // myInvoicesVM.setInvoiceDetailLoading(); + // LoaderBottomSheet.showLoader(loadingText: LocaleKeys.fetchingInvoiceDetails.tr(context: context)); + // await myInvoicesVM.getInvoiceDetails( + // appointmentNo: myInvoicesVM.allInvoicesList[index].appointmentNo!, + // invoiceNo: myInvoicesVM.allInvoicesList[index].invoiceNo!, + // projectID: myInvoicesVM.allInvoicesList[index].projectId!, + // onSuccess: (val) { + // LoaderBottomSheet.hideLoader(); + // Navigator.of(context).push( + // CustomPageRoute( + // page: MyInvoicesDetailsPage( + // getInvoiceDetailsResponseModel: myInvoicesVM.invoiceDetailsResponseModel, + // getInvoicesListResponseModel: myInvoicesVM.allInvoicesList[index], + // ), + // ), + // ); + // }, + // onError: (err) { + // LoaderBottomSheet.hideLoader(); + // showCommonBottomSheetWithoutHeight( + // context, + // child: Utils.getErrorWidget(loadingText: err), + // callBackFunc: () {}, + // isFullScreen: false, + // isCloseButtonVisible: true, + // ); + // }); + // }, ), ), ), diff --git a/lib/presentation/my_invoices/widgets/invoice_list_card.dart b/lib/presentation/my_invoices/widgets/invoice_list_card.dart index f2a1c6df..42d2fd92 100644 --- a/lib/presentation/my_invoices/widgets/invoice_list_card.dart +++ b/lib/presentation/my_invoices/widgets/invoice_list_card.dart @@ -61,7 +61,7 @@ class InvoiceListCard extends StatelessWidget { Column( children: [ Image.network( - getInvoicesListResponseModel.doctorImageURL!, + getInvoicesListResponseModel.doctorImageUrl ?? 'https://hmgwebservices.com/Images/MobileImages/DUBAI/unkown_female.png', width: 63.h, height: 63.h, fit: BoxFit.cover, @@ -73,10 +73,10 @@ class InvoiceListCard extends StatelessWidget { height: 40.h, decoration: BoxDecoration( color: AppColors.whiteColor, - shape: BoxShape.circle, // Makes the container circular + shape: BoxShape.circle, border: Border.all( - color: AppColors.scaffoldBgColor, // Color of the border - width: 1.5.w, // Width of the border + color: AppColors.scaffoldBgColor, + width: 1.5.w, ), ), child: Column( @@ -84,7 +84,7 @@ class InvoiceListCard extends StatelessWidget { children: [ Utils.buildSvgWithAssets(icon: AppAssets.rating_icon, width: 15.w, height: 15.h, applyThemeColor: false), SizedBox(height: 2.h), - "${getInvoicesListResponseModel.decimalDoctorRate}".toText11(isBold: true, color: AppColors.textColor), + "${getInvoicesListResponseModel.decimalDoctorRate ?? '0.0'}".toText11(isBold: true, color: AppColors.textColor), ], ), ).circle(100), @@ -96,7 +96,10 @@ class InvoiceListCard extends StatelessWidget { child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ - (getIt().isArabic() ? getInvoicesListResponseModel.doctorNameN! : getInvoicesListResponseModel.doctorName ?? "HMG Doctor").toText16(isBold: true), + (getIt().isArabic() + ? (getInvoicesListResponseModel.doctorNameN ?? getInvoicesListResponseModel.doctorName ?? LocaleKeys.doctor.tr(context: context)) + : (getInvoicesListResponseModel.doctorName ?? getInvoicesListResponseModel.doctorNameN ?? LocaleKeys.doctor.tr(context: context)) + ).toText16(isBold: true), SizedBox(height: 8.h), Wrap( direction: Axis.horizontal, @@ -104,28 +107,30 @@ class InvoiceListCard extends StatelessWidget { runSpacing: 6.h, children: [ AppCustomChipWidget( - labelText: "${LocaleKeys.invoiceNo.tr(context: context)}: ${getInvoicesListResponseModel.invoiceNo!}", + labelText: "${LocaleKeys.invoiceNo.tr(context: context)}: ${getInvoicesListResponseModel.invoiceNo ?? '-'}", labelPadding: EdgeInsetsDirectional.only(start: 6.w, end: 6.w), isEnglishOnly: true, ), AppCustomChipWidget( - labelText: - (getInvoicesListResponseModel.clinicName!.length > 15 ? '${getInvoicesListResponseModel.clinicName!.substring(0, 12)}...' : getInvoicesListResponseModel.clinicName!), + labelText: ((getInvoicesListResponseModel.clinicName ?? LocaleKeys.clinic.tr(context: context)).length > 15 + ? '${(getInvoicesListResponseModel.clinicName ?? LocaleKeys.clinic.tr(context: context)).substring(0, 12)}...' + : (getInvoicesListResponseModel.clinicName ?? LocaleKeys.clinic.tr(context: context))), labelPadding: EdgeInsetsDirectional.only(start: 4.w, end: 4.w), ), AppCustomChipWidget( - labelText: getInvoicesListResponseModel.projectName!, + labelText: getInvoicesListResponseModel.projectName ?? LocaleKeys.hospital.tr(context: context), labelPadding: EdgeInsetsDirectional.only(start: 6.w, end: 6.w), ), - Directionality( - textDirection: ui.TextDirection.ltr, - child: AppCustomChipWidget( - labelPadding: EdgeInsetsDirectional.only(start: -4.w, end: 6.w), - icon: AppAssets.doctor_calendar_icon, - labelText: DateUtil.formatDateToDate(DateUtil.convertStringToDate(getInvoicesListResponseModel.appointmentDate), false), - isEnglishOnly: true, + if (getInvoicesListResponseModel.appointmentDate != null) + Directionality( + textDirection: ui.TextDirection.ltr, + child: AppCustomChipWidget( + labelPadding: EdgeInsetsDirectional.only(start: -4.w, end: 6.w), + icon: AppAssets.doctor_calendar_icon, + labelText: DateUtil.formatDateToDate(DateUtil.convertStringToDate(getInvoicesListResponseModel.appointmentDate), false), + isEnglishOnly: true, + ), ), - ), ], ), ], @@ -135,7 +140,8 @@ class InvoiceListCard extends StatelessWidget { ), SizedBox(height: 16.h), CustomButton( - text: LocaleKeys.viewInvoiceDetails.tr(context: context), + // text: LocaleKeys.viewInvoiceDetails.tr(context: context), + text: Utils.isVidaPlusProject(getInvoicesListResponseModel.projectId ?? 0) ? LocaleKeys.sendEmail.tr(context: context) : LocaleKeys.downloadInvoice.tr(context: context), onPressed: () { if (onTap != null) { onTap!(); @@ -145,7 +151,7 @@ class InvoiceListCard extends StatelessWidget { borderColor: AppColors.primaryRedColor.withValues(alpha: 0.01), textColor: AppColors.primaryRedColor, fontSize: 14.f, - fontWeight: FontWeight.w500, + fontWeight: FontWeight.w600, borderRadius: 12.r, padding: EdgeInsets.symmetric(horizontal: 10.w), height: 40.h, diff --git a/lib/presentation/notifications/notification_details_page.dart b/lib/presentation/notifications/notification_details_page.dart index 1607e8f5..abe8b521 100644 --- a/lib/presentation/notifications/notification_details_page.dart +++ b/lib/presentation/notifications/notification_details_page.dart @@ -73,7 +73,7 @@ class NotificationDetailsPage extends StatelessWidget { Icon(Icons.access_time, size: 12.w, color: AppColors.textColor), SizedBox(width: 4.w), _formatTime(notification.isSentOn).toText10( - weight: FontWeight.w500, + weight: FontWeight.w600, color: AppColors.textColor, ), ], @@ -93,7 +93,7 @@ class NotificationDetailsPage extends StatelessWidget { Icon(Icons.calendar_today, size: 12.w, color: AppColors.textColor), SizedBox(width: 4.w), _formatDate(notification.isSentOn).toText10( - weight: FontWeight.w500, + weight: FontWeight.w600, color: AppColors.textColor, ), ], diff --git a/lib/presentation/notifications/notifications_list_page.dart b/lib/presentation/notifications/notifications_list_page.dart index 99201688..e9a9447e 100644 --- a/lib/presentation/notifications/notifications_list_page.dart +++ b/lib/presentation/notifications/notifications_list_page.dart @@ -2,6 +2,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/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'; @@ -18,6 +19,7 @@ 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:intl/intl.dart'; +import 'dart:ui' as ui; class NotificationsListPage extends StatefulWidget { const NotificationsListPage({super.key}); @@ -134,16 +136,20 @@ class _NotificationsListPageState extends State { color: AppColors.greyColor, borderRadius: BorderRadius.circular(8), ), - child: Row( - mainAxisSize: MainAxisSize.min, - children: [ - Icon(Icons.access_time, size: 12.w, color: AppColors.textColor), - SizedBox(width: 4.w), - _formatTime(notificationsVM.notificationsList[index].isSentOn).toText10( - weight: FontWeight.w500, - color: AppColors.textColor, - ), - ], + child: Directionality( + textDirection: ui.TextDirection.ltr, + child: Row( + mainAxisSize: MainAxisSize.min, + children: [ + Icon(Icons.access_time, size: 12.w, color: AppColors.textColor), + SizedBox(width: 4.w), + _formatTime(notificationsVM.notificationsList[index].isSentOn).toText10( + weight: FontWeight.w600, + color: AppColors.textColor, + isEnglishOnly: true + ), + ], + ), ), ), SizedBox(width: 8.w), @@ -154,25 +160,32 @@ class _NotificationsListPageState extends State { color: AppColors.greyColor, borderRadius: BorderRadius.circular(8), ), - child: Row( - mainAxisSize: MainAxisSize.min, - children: [ - Icon(Icons.calendar_today, size: 12.w, color: AppColors.textColor), - SizedBox(width: 4.w), - _formatDate(notificationsVM.notificationsList[index].isSentOn).toText10( - weight: FontWeight.w500, - color: AppColors.textColor, - ), - ], + child: Directionality( + textDirection: ui.TextDirection.ltr, + child: Row( + mainAxisSize: MainAxisSize.min, + children: [ + Icon(Icons.calendar_today, size: 12.w, color: AppColors.textColor), + SizedBox(width: 4.w), + _formatDate(notificationsVM.notificationsList[index].isSentOn).toText10( + weight: FontWeight.w600, + color: AppColors.textColor, + isEnglishOnly: true + ), + ], + ), ), ), Spacer(), // Arrow icon - Utils.buildSvgWithAssets( - icon: AppAssets.arrow_forward, - width: 16.w, - height: 16.h, - iconColor: AppColors.greyTextColor, + Transform.flip( + flipX: getIt.get().isArabic(), + child: Utils.buildSvgWithAssets( + icon: AppAssets.arrow_forward, + width: 16.w, + height: 16.h, + iconColor: AppColors.greyTextColor, + ), ), ], ), @@ -194,8 +207,8 @@ class _NotificationsListPageState extends State { children: [ Icon(Icons.image_outlined, size: 12.w, color: AppColors.textColor), SizedBox(width: 4.w), - 'Contains Image'.toText10( - weight: FontWeight.w500, + LocaleKeys.containsImage.toText10( + weight: FontWeight.w600, color: AppColors.textColor, ), ], diff --git a/lib/presentation/onboarding/onboarding_screen.dart b/lib/presentation/onboarding/onboarding_screen.dart index 4855c0cf..0af63b40 100644 --- a/lib/presentation/onboarding/onboarding_screen.dart +++ b/lib/presentation/onboarding/onboarding_screen.dart @@ -143,7 +143,7 @@ class _OnboardingScreenState extends State { }) : CustomButton( text: LocaleKeys.getStarted.tr(context: context), - fontWeight: FontWeight.w500, + fontWeight: FontWeight.w600, fontSize: 16.f, height: 56.h, textOverflow: TextOverflow.ellipsis, @@ -178,7 +178,7 @@ class _OnboardingScreenState extends State { ), Text( body, - style: TextStyle(fontSize: 16.f, fontWeight: FontWeight.w500, color: AppColors.greyTextColor, letterSpacing: 0, height: 26 / 16), + style: TextStyle(fontSize: 16.f, fontWeight: FontWeight.w600, color: AppColors.greyTextColor, letterSpacing: 0, height: 26 / 16), ), ], ).paddingOnly(left: 24.w, right: 24.w); diff --git a/lib/presentation/parking/paking_page.dart b/lib/presentation/parking/paking_page.dart index 7673d15f..736ef2a0 100644 --- a/lib/presentation/parking/paking_page.dart +++ b/lib/presentation/parking/paking_page.dart @@ -104,7 +104,7 @@ class _ParkingPageState extends State { hasShadow: true, ), child: Padding( - padding: EdgeInsets.all(16.h), child: LocaleKeys.parkingDescription.tr(context: context).toText12(fontWeight: FontWeight.w500, color: AppColors.textColor)), + padding: EdgeInsets.all(16.h), child: LocaleKeys.parkingDescription.tr(context: context).toText12(isBold: true, color: AppColors.textColor)), ).paddingOnly(top: 16, bottom: 16), ClipRRect( borderRadius: BorderRadius.circular(24.r), diff --git a/lib/presentation/prescriptions/prescription_delivery_order_summary_page.dart b/lib/presentation/prescriptions/prescription_delivery_order_summary_page.dart index 070132ad..3a287a44 100644 --- a/lib/presentation/prescriptions/prescription_delivery_order_summary_page.dart +++ b/lib/presentation/prescriptions/prescription_delivery_order_summary_page.dart @@ -66,7 +66,7 @@ class PrescriptionDeliveryOrderSummaryPage extends StatelessWidget { Expanded( child: Padding( padding: EdgeInsets.all(8.h), - child: prescriptionsViewModel.prescriptionDetailsList[index].itemDescription!.trim().toText14(weight: FontWeight.w500), + child: prescriptionsViewModel.prescriptionDetailsList[index].itemDescription!.trim().toText14(isBold: true), ), ), ], @@ -157,7 +157,7 @@ class PrescriptionDeliveryOrderSummaryPage extends StatelessWidget { borderColor: AppColors.successColor.withOpacity(0.01), textColor: AppColors.whiteColor, fontSize: 16.f, - fontWeight: FontWeight.w500, + isBold: true, borderRadius: 12.r, padding: EdgeInsets.fromLTRB(10, 0, 10, 0), height: 50.h, diff --git a/lib/presentation/prescriptions/prescription_delivery_orders_list_page.dart b/lib/presentation/prescriptions/prescription_delivery_orders_list_page.dart index 7760fd2d..31c6ae95 100644 --- a/lib/presentation/prescriptions/prescription_delivery_orders_list_page.dart +++ b/lib/presentation/prescriptions/prescription_delivery_orders_list_page.dart @@ -70,7 +70,7 @@ class PrescriptionDeliveryOrdersListPage extends StatelessWidget { spacing: 8.h, children: [ RequestStatus(status: model.prescriptionsOrderList[index].statusId ?? 0), - "Req ID: ${model.prescriptionsOrderList[index].iD}".toText16(color: AppColors.textColor, weight: FontWeight.w600), + "Req ID: ${model.prescriptionsOrderList[index].iD}".toText16(color: AppColors.textColor, isBold: true), Row( spacing: 4.w, children: [ diff --git a/lib/presentation/prescriptions/prescription_detail_page.dart b/lib/presentation/prescriptions/prescription_detail_page.dart index 0d7d373a..715b6e4d 100644 --- a/lib/presentation/prescriptions/prescription_detail_page.dart +++ b/lib/presentation/prescriptions/prescription_detail_page.dart @@ -65,7 +65,7 @@ class _PrescriptionDetailPageState extends State { Expanded( child: CollapsingListView( title: LocaleKeys.prescriptions.tr(context: context), - instructions: widget.prescriptionsResponseModel.isInOutPatient! + instructions: widget.prescriptionsResponseModel.isInOutPatient ?? false ? () async { LoaderBottomSheet.showLoader(loadingText: LocaleKeys.fetchingPrescriptionPDFPleaseWait.tr(context: context)); await prescriptionsViewModel.getPrescriptionInstructionsPDF(widget.prescriptionsResponseModel, onSuccess: (val) { @@ -112,8 +112,9 @@ class _PrescriptionDetailPageState extends State { crossAxisAlignment: CrossAxisAlignment.start, children: [ AppCustomChipWidget( - labelText: - "${getIt.get().isArabic() ? widget.prescriptionsResponseModel.isInOutPatientDescriptionN : widget.prescriptionsResponseModel.isInOutPatientDescription}", + labelText: getIt.get().isArabic() + ? (widget.prescriptionsResponseModel.isInOutPatientDescriptionN ?? "مراجع للعيادات الخارجية") + : (widget.prescriptionsResponseModel.isInOutPatientDescription ?? "OutPatient"), backgroundColor: AppColors.warningColorYellow.withOpacity(0.1), textColor: AppColors.warningColorYellow, ), @@ -197,7 +198,7 @@ class _PrescriptionDetailPageState extends State { borderColor: AppColors.successColor.withValues(alpha: 0.01), textColor: AppColors.successColor, fontSize: 14.f, - fontWeight: FontWeight.w500, + fontWeight: FontWeight.w600, borderRadius: 12.r, padding: EdgeInsets.fromLTRB(10, 0, 10, 0), height: 40.h, @@ -250,6 +251,9 @@ class _PrescriptionDetailPageState extends State { await prescriptionsViewModel.getPrescriptionDetails(widget.prescriptionsResponseModel, onSuccess: (val) { LoaderBottomSheet.hideLoader(); prescriptionsViewModel.initiatePrescriptionDelivery(); + }, onError: (err) { + LoaderBottomSheet.hideLoader(); + print(err); }); } }, @@ -257,7 +261,7 @@ class _PrescriptionDetailPageState extends State { borderColor: AppColors.successColor.withOpacity(0.01), textColor: widget.prescriptionsResponseModel.isHomeMedicineDeliverySupported! ? Colors.white : AppColors.textColor.withOpacity(0.35), fontSize: 16, - fontWeight: FontWeight.w500, + fontWeight: FontWeight.w600, borderRadius: 12, padding: EdgeInsets.fromLTRB(10, 0, 10, 0), height: 50.h, diff --git a/lib/presentation/prescriptions/prescription_item_view.dart b/lib/presentation/prescriptions/prescription_item_view.dart index 5b52b99b..83c2f7f1 100644 --- a/lib/presentation/prescriptions/prescription_item_view.dart +++ b/lib/presentation/prescriptions/prescription_item_view.dart @@ -98,7 +98,7 @@ class PrescriptionItemView extends StatelessWidget { crossAxisAlignment: CrossAxisAlignment.start, children: [ LocaleKeys.setReminder.tr(context: context).toText13(isBold: true), - "Notify me before the consumption time".toText10(color: AppColors.textColorLight, isEnglishOnly: true), + LocaleKeys.notifyPrescription.tr(context: context).toText10(color: AppColors.textColorLight), ], ).toShimmer2(isShow: isLoading).expanded, Switch( @@ -183,7 +183,7 @@ class PrescriptionItemView extends StatelessWidget { // borderColor: AppColors.primaryRedColor.withOpacity(0.0), // textColor: AppColors.primaryRedColor, // fontSize: 14.f, - // fontWeight: FontWeight.w500, + // isBold: true, // borderRadius: 12.r, // padding: EdgeInsets.fromLTRB(10, 0, 10, 0), // height: 40.h, diff --git a/lib/presentation/prescriptions/prescription_reminder_view.dart b/lib/presentation/prescriptions/prescription_reminder_view.dart index d1dcd07a..0d4ec9f5 100644 --- a/lib/presentation/prescriptions/prescription_reminder_view.dart +++ b/lib/presentation/prescriptions/prescription_reminder_view.dart @@ -57,7 +57,7 @@ class _PrescriptionReminderViewState extends State { "${(_options[index] / 60).toInt()} ${LocaleKeys.hours.tr(context: context)}", style: TextStyle( fontSize: 16.h, - fontWeight: FontWeight.w500, + fontWeight: FontWeight.w600, ), ), value: index, diff --git a/lib/presentation/prescriptions/prescriptions_list_page.dart b/lib/presentation/prescriptions/prescriptions_list_page.dart index 3a54926f..b88eec30 100644 --- a/lib/presentation/prescriptions/prescriptions_list_page.dart +++ b/lib/presentation/prescriptions/prescriptions_list_page.dart @@ -80,7 +80,7 @@ class _PrescriptionsListPageState extends State { // borderColor: model.isSortByClinic ? AppColors.primaryRedColor : AppColors.textColor.withOpacity(0.2), // textColor: model.isSortByClinic ? AppColors.primaryRedColor : AppColors.blackColor, // fontSize: 12, - // fontWeight: FontWeight.w500, + // isBold: true, // borderRadius: 10, // padding: EdgeInsets.fromLTRB(10, 0, 10, 0), // height: 40.h, @@ -95,7 +95,7 @@ class _PrescriptionsListPageState extends State { // borderColor: model.isSortByClinic ? AppColors.textColor.withOpacity(0.2) : AppColors.primaryRedColor, // textColor: model.isSortByClinic ? AppColors.blackColor : AppColors.primaryRedColor, // fontSize: 12, - // fontWeight: FontWeight.w500, + // isBold: true, // borderRadius: 10, // padding: EdgeInsets.fromLTRB(10, 0, 10, 0), // height: 40.h, @@ -157,7 +157,7 @@ class _PrescriptionsListPageState extends State { fit: BoxFit.fill, ).circle(100), SizedBox(width: 8.h), - Expanded(child: model.patientPrescriptionOrders[index].doctorName!.toText14(weight: FontWeight.w500)), + Expanded(child: model.patientPrescriptionOrders[index].doctorName!.toText14(isBold: true)), ], ), SizedBox(height: 8.h), @@ -205,7 +205,7 @@ class _PrescriptionsListPageState extends State { borderColor: AppColors.successColor.withOpacity(0.01), textColor: model.patientPrescriptionOrders[index].isHomeMedicineDeliverySupported! ? AppColors.successColor : AppColors.textColor.withOpacity(0.35), fontSize: model.patientPrescriptionOrders[index].isHomeMedicineDeliverySupported! ? 14.f : 12.f, - fontWeight: FontWeight.w500, + fontWeight: FontWeight.w600, borderRadius: 12.r, padding: EdgeInsets.fromLTRB(10, 0, 10, 0), height: 40.h, @@ -271,7 +271,7 @@ class _PrescriptionsListPageState extends State { // fit: BoxFit.fill, // ).circle(100), // SizedBox(width: 8.h), - // Expanded(child: prescription.doctorName!.toText14(weight: FontWeight.w500)), + // Expanded(child: prescription.doctorName!.toText14(isBold: true)), // ], // ), // SizedBox(height: 8.h), @@ -315,7 +315,7 @@ class _PrescriptionsListPageState extends State { // borderColor: AppColors.successColor.withOpacity(0.01), // textColor: prescription.isHomeMedicineDeliverySupported! ? AppColors.successColor : AppColors.textColor.withOpacity(0.35), // fontSize: prescription.isHomeMedicineDeliverySupported! ? 14.f : 12.f, - // fontWeight: FontWeight.w500, + // isBold: true, // borderRadius: 12.r, // padding: EdgeInsets.fromLTRB(10, 0, 10, 0), // height: 40.h, @@ -393,7 +393,7 @@ class _PrescriptionsListPageState extends State { // borderColor: AppColors.greyColor, // textColor: AppColors.blackColor, // fontSize: 10, - // fontWeight: FontWeight.w500, + // isBold: true, // borderRadius: 8, // padding: EdgeInsets.fromLTRB(10, 0, 10, 0), // height: 30.h, @@ -441,7 +441,7 @@ class _PrescriptionsListPageState extends State { // // fit: BoxFit.fill, // // ).circle(100), // // SizedBox(width: 8.h), - // // Expanded(child: prescription.doctorName!.toText14(weight: FontWeight.w500)), + // // Expanded(child: prescription.doctorName!.toText14(isBold: true)), // // ], // // ), // // SizedBox(height: 8.h), @@ -485,7 +485,7 @@ class _PrescriptionsListPageState extends State { // // borderColor: AppColors.successColor.withOpacity(0.01), // // textColor: prescription.isHomeMedicineDeliverySupported! ? AppColors.successColor : AppColors.textColor.withOpacity(0.35), // // fontSize: prescription.isHomeMedicineDeliverySupported! ? 14.f : 12.f, - // // fontWeight: FontWeight.w500, + // // isBold: true, // // borderRadius: 12.r, // // padding: EdgeInsets.fromLTRB(10, 0, 10, 0), // // height: 40.h, @@ -543,7 +543,17 @@ class _PrescriptionsListPageState extends State { // ], // ), // ), - ), + ).onPress(() { + model.setPrescriptionsDetailsLoading(); + Navigator.of(context).push( + CustomPageRoute( + page: PrescriptionDetailPage( + prescriptionsResponseModel: model.patientPrescriptionOrders[index], + isFromAppointments: false, + ), + ), + ); + }), ), ), ) diff --git a/lib/presentation/profile_settings/profile_settings.dart b/lib/presentation/profile_settings/profile_settings.dart index fd88c049..377014ac 100644 --- a/lib/presentation/profile_settings/profile_settings.dart +++ b/lib/presentation/profile_settings/profile_settings.dart @@ -1,5 +1,4 @@ import 'dart:async'; -import 'dart:io'; import 'package:easy_localization/easy_localization.dart'; import 'package:flutter/material.dart'; @@ -8,8 +7,6 @@ import 'package:hmg_patient_app_new/core/app_assets.dart'; import 'package:hmg_patient_app_new/core/app_export.dart'; import 'package:hmg_patient_app_new/core/app_state.dart'; import 'package:hmg_patient_app_new/core/dependencies.dart'; -import 'package:hmg_patient_app_new/core/enums.dart'; -import 'package:hmg_patient_app_new/core/utils/date_util.dart'; 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'; @@ -17,31 +14,31 @@ 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/features/contact_us/models/feedback_type.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'; import 'package:hmg_patient_app_new/features/medical_file/models/family_file_response_model.dart'; import 'package:hmg_patient_app_new/features/profile_settings/profile_settings_view_model.dart'; import 'package:hmg_patient_app_new/generated/locale_keys.g.dart'; import 'package:hmg_patient_app_new/presentation/contact_us/feedback_page.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/presentation/insurance/widgets/insurance_update_details_card.dart'; +import 'package:hmg_patient_app_new/presentation/profile_settings/widgets/profile_picture_widget.dart'; import 'package:hmg_patient_app_new/presentation/profile_settings/widgets/update_email_widget.dart'; import 'package:hmg_patient_app_new/presentation/profile_settings/widgets/update_emergency_contact_widget.dart'; -import 'package:hmg_patient_app_new/services/dialog_service.dart'; import 'package:hmg_patient_app_new/theme/colors.dart'; import 'package:hmg_patient_app_new/widgets/app_language_change.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/user_avatar_widget.dart'; import 'package:hmg_patient_app_new/widgets/routes/custom_page_route.dart'; -import 'package:permission_handler/permission_handler.dart'; import 'package:provider/provider.dart'; +import 'package:permission_handler/permission_handler.dart'; import 'package:url_launcher/url_launcher.dart'; import 'package:in_app_review/in_app_review.dart'; +import 'dart:ui' as ui; + class ProfileSettings extends StatefulWidget { const ProfileSettings({super.key}); @@ -118,6 +115,9 @@ class ProfileSettingsState extends State { return Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ + // Profile Picture Section + ProfilePictureWidget(), + // SizedBox( // height: dynamicItemHeight(context) + 20 + 30, // itemHeight + shadow padding (10 top + 10 bottom) + pagination dots space // child: Swiper( @@ -180,7 +180,7 @@ class ProfileSettingsState extends State { // crossAxisAlignment: CrossAxisAlignment.center, // children: [ // Utils.buildSvgWithAssets(icon: AppAssets.wallet, width: 40.w, height: 40.h, applyThemeColor: false), - // LocaleKeys.habibWallet.tr(context: context).toText16(weight: FontWeight.w600, maxlines: 2).expanded, + // LocaleKeys.habibWallet.tr(context: context).toText16(isBold: true, maxlines: 2).expanded, // Utils.buildSvgWithAssets(icon: getIt.get().isArabic() ? AppAssets.arrow_back : AppAssets.arrow_forward), // ], // ), @@ -198,7 +198,7 @@ class ProfileSettingsState extends State { // textColor: AppColors.infoColor, // text: LocaleKeys.recharge.tr(context: context), // borderWidth: 0.w, - // fontWeight: FontWeight.w500, + // isBold: true, // borderColor: Colors.transparent, // backgroundColor: Color(0xff45A2F8).withValues(alpha: 0.08), // padding: EdgeInsets.all(8.w), @@ -214,11 +214,11 @@ class ProfileSettingsState extends State { // ), // ], // ), - LocaleKeys.quickActions.tr(context: context).toText18(weight: FontWeight.w600, textOverflow: TextOverflow.ellipsis, maxlines: 1).paddingOnly(left: 24.w, right: 24.w), + LocaleKeys.quickActions.tr(context: context).toText18(isBold: true, 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), + decoration: RoundedRectangleBorder().toSmoothCornerDecoration(color: AppColors.whiteColor, borderRadius: 24.r, hasShadow: false), child: Column( children: [ actionItem(AppAssets.language_change, LocaleKeys.language.tr(context: context), () { @@ -247,11 +247,11 @@ class ProfileSettingsState extends State { ], ), ), - LocaleKeys.personalInformation.tr(context: context).toText18(weight: FontWeight.w600, textOverflow: TextOverflow.ellipsis, maxlines: 1).paddingOnly(left: 24.w, right: 24.w), + LocaleKeys.personalInformation.tr(context: context).toText18(isBold: true, 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), + decoration: RoundedRectangleBorder().toSmoothCornerDecoration(color: AppColors.whiteColor, borderRadius: 24.r, hasShadow: false), child: Column( children: [ actionItem(AppAssets.email_transparent, LocaleKeys.updateEmailAddress.tr(context: context), () { @@ -288,20 +288,20 @@ class ProfileSettingsState extends State { ], ), ), - LocaleKeys.helpAndSupport.tr(context: context).toText18(weight: FontWeight.w600, textOverflow: TextOverflow.ellipsis, maxlines: 1).paddingOnly(left: 24.w, right: 24.w), + LocaleKeys.helpAndSupport.tr(context: context).toText18(isBold: true, 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), - decoration: RoundedRectangleBorder().toSmoothCornerDecoration(color: AppColors.whiteColor, borderRadius: 24.r, hasShadow: true), + decoration: RoundedRectangleBorder().toSmoothCornerDecoration(color: AppColors.whiteColor, borderRadius: 24.r, hasShadow: false), child: Column( children: [ actionItem(AppAssets.call_fill, LocaleKeys.contactUs.tr(context: context), () { launchUrl(Uri.parse("tel://" + "+966 92 006 6666")); - }, trailingLabel: "92 006 6666"), + }, trailingLabel: "92 006 6666", isEnglishOnly: true), 1.divider, actionItem(AppAssets.permission, LocaleKeys.permissionsProfile.tr(context: context), () { openAppSettings(); - }, trailingLabel: getCurrentPermissions()), + }, trailingLabel: getCurrentPermissions(), isEnglishOnly: true), actionItem(AppAssets.feedbackFill, LocaleKeys.feedback.tr(context: context), () { contactUsViewModel.setSelectedFeedbackType( FeedbackType(id: 5, nameEN: "Not classified", nameAR: 'غير محدد'), @@ -392,18 +392,18 @@ class ProfileSettingsState extends State { } Widget actionItem(String icon, String label, VoidCallback onPress, - {String trailingLabel = "", bool? switchValue, ValueChanged? onSwitchChanged, bool isExternalLink = false, bool isShowLoading = false}) { + {String trailingLabel = "", bool? switchValue, ValueChanged? onSwitchChanged, bool isExternalLink = false, bool isShowLoading = false, bool isEnglishOnly = false}) { return SizedBox( height: 56.h, child: Row( spacing: 8.w, children: [ Utils.buildSvgWithAssets(icon: icon, iconColor: AppColors.greyTextColor), - label.toText14(weight: FontWeight.w500, textOverflow: TextOverflow.ellipsis, maxlines: 1).expanded, + label.toText14(isBold: true, textOverflow: TextOverflow.ellipsis, maxlines: 1).expanded, if (trailingLabel.isNotEmpty) ConstrainedBox( constraints: BoxConstraints(maxWidth: MediaQuery.of(context).size.width * 0.35), - child: trailingLabel.toText14(color: AppColors.greyTextColor, weight: FontWeight.w500, textOverflow: TextOverflow.ellipsis, maxlines: 1).toShimmer2(isShow: isShowLoading), + child: isEnglishOnly ? Directionality(textDirection: ui.TextDirection.ltr, child: trailingLabel.toText14(color: AppColors.greyTextColor, isBold: true, textOverflow: TextOverflow.ellipsis, maxlines: 1, isEnglishOnly: isEnglishOnly).toShimmer2(isShow: isShowLoading)) : trailingLabel.toText14(color: AppColors.greyTextColor, isBold: true, textOverflow: TextOverflow.ellipsis, maxlines: 1, isEnglishOnly: isEnglishOnly).toShimmer2(isShow: isShowLoading), ), switchValue != null ? Switch( @@ -453,12 +453,19 @@ class FamilyCardWidget extends StatelessWidget { Row( crossAxisAlignment: CrossAxisAlignment.start, children: [ - Image.asset(profile.gender == 1 ? AppAssets.maleImg : AppAssets.femaleImg, width: 56.w, height: 56.h), + UserAvatarWidget( + width: 56.w, + height: 56.h, + gender: profile.gender, + age: profile.age, + fit: BoxFit.cover, + isCircular: true, + ), Column( crossAxisAlignment: CrossAxisAlignment.start, mainAxisSize: MainAxisSize.min, children: [ - "${profile.patientName}".toText18(isBold: true, weight: FontWeight.w600, textOverflow: TextOverflow.ellipsis, maxlines: 1, isEnglishOnly: true), + "${profile.patientName}".toText18(isBold: true, textOverflow: TextOverflow.ellipsis, maxlines: 1, isEnglishOnly: true), Wrap( direction: Axis.horizontal, spacing: 4.w, @@ -558,10 +565,10 @@ class FamilyCardWidget extends StatelessWidget { } }, backgroundColor: insuranceVM.isInsuranceExpired - ? AppColors.primaryRedColor.withOpacity(0.1) + ? AppColors.primaryRedColor.withValues(alpha: 0.1) : insuranceVM.isInsuranceActive - ? AppColors.successColor.withOpacity(0.1) - : AppColors.warningColorYellow.withOpacity(0.1), + ? AppColors.successColor.withValues(alpha: 0.1) + : AppColors.warningColorYellow.withValues(alpha: 0.1), labelPadding: EdgeInsetsDirectional.only(start: -4.w, end: insuranceVM.isInsuranceActive ? 6.w : 0.w), ).toShimmer2(isShow: insuranceVM.isInsuranceLoading) : SizedBox.shrink(); diff --git a/lib/presentation/profile_settings/widgets/family_card_widget.dart b/lib/presentation/profile_settings/widgets/family_card_widget.dart index b21c52d4..768fe538 100644 --- a/lib/presentation/profile_settings/widgets/family_card_widget.dart +++ b/lib/presentation/profile_settings/widgets/family_card_widget.dart @@ -14,6 +14,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 'package:hmg_patient_app_new/widgets/user_avatar_widget.dart'; import 'package:provider/provider.dart'; class FamilyCardWidget extends StatelessWidget { @@ -49,12 +50,20 @@ class FamilyCardWidget extends StatelessWidget { Row( crossAxisAlignment: CrossAxisAlignment.start, children: [ - Image.asset(profile.gender == 1 ? AppAssets.maleImg : AppAssets.femaleImg, width: 56.w, height: 56.h), + UserAvatarWidget( + width: 56.w, + height: 56.h, + gender: profile.gender, + age: profile.age, + fit: BoxFit.cover, + customProfileImageData: "", + isCircular: true,// Prevent showing logged-in user's image for family members + ), Column( crossAxisAlignment: CrossAxisAlignment.start, mainAxisSize: MainAxisSize.min, children: [ - "${profile.patientName}".toText18(isBold: true, weight: FontWeight.w600, textOverflow: TextOverflow.ellipsis, maxlines: 1), + "${profile.patientName}".toText18(isBold: true, textOverflow: TextOverflow.ellipsis, maxlines: 1), AppCustomChipWidget( icon: AppAssets.file_icon, labelText: "${LocaleKeys.fileNo.tr(context: context)}: ${profile.responseId}", diff --git a/lib/presentation/profile_settings/widgets/preferred_language_widget.dart b/lib/presentation/profile_settings/widgets/preferred_language_widget.dart index 9678349b..4cecc651 100644 --- a/lib/presentation/profile_settings/widgets/preferred_language_widget.dart +++ b/lib/presentation/profile_settings/widgets/preferred_language_widget.dart @@ -47,9 +47,9 @@ class _PreferredLanguageWidgetState extends State { decoration: RoundedRectangleBorder().toSmoothCornerDecoration(color: AppColors.whiteColor, borderRadius: 24.h, hasShadow: true), child: Column( children: [ - languageItem("English", "2"), - 1.divider, languageItem("العربية", "1"), + 1.divider, + languageItem("English", "2"), ], ), ), @@ -104,7 +104,7 @@ class _PreferredLanguageWidgetState extends State { }, materialTapTargetSize: MaterialTapTargetSize.shrinkWrap, ), - title.toText16(weight: FontWeight.w500, textOverflow: TextOverflow.ellipsis, maxlines: 1, fontFamily: _value == "1" ? 'GESSTwo' : 'Poppins').expanded, + title.toText16(isBold: true, textOverflow: TextOverflow.ellipsis, maxlines: 1, fontFamily: _value == "1" ? 'CairoArabic' : 'Poppins').expanded, ], ).paddingOnly(left: 16, right: 16).onPress(() { setState(() { diff --git a/lib/presentation/profile_settings/widgets/profile_picture_widget.dart b/lib/presentation/profile_settings/widgets/profile_picture_widget.dart new file mode 100644 index 00000000..1b63fef3 --- /dev/null +++ b/lib/presentation/profile_settings/widgets/profile_picture_widget.dart @@ -0,0 +1,511 @@ +import 'dart:convert'; +import 'dart:io'; + +import 'package:easy_localization/easy_localization.dart'; +import 'package:flutter/material.dart'; +import 'package:hmg_patient_app_new/core/app_assets.dart'; +import 'package:hmg_patient_app_new/core/app_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/core/utils/image_compression_helper.dart'; +import 'package:hmg_patient_app_new/extensions/widget_extensions.dart'; +import 'package:hmg_patient_app_new/features/profile_settings/profile_settings_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/image_picker.dart'; +import 'package:hmg_patient_app_new/widgets/common_bottom_sheet.dart'; +import 'package:permission_handler/permission_handler.dart'; +import 'package:provider/provider.dart'; + +class ProfilePictureWidget extends StatefulWidget { + const ProfilePictureWidget({super.key}); + + @override + State createState() => _ProfilePictureWidgetState(); +} + +class _ProfilePictureWidgetState extends State { + final AppState _appState = getIt.get(); + File? _selectedImage; + + @override + void initState() { + super.initState(); + // Use addPostFrameCallback to ensure widget is built before loading + WidgetsBinding.instance.addPostFrameCallback((_) { + if (!mounted) return; + + final profileVm = context.read(); + + // Only sync if AppState has data and ViewModel doesn't + if (_appState.getProfileImageData != null && + _appState.getProfileImageData!.isNotEmpty && + (profileVm.profileImageData == null || profileVm.profileImageData!.isEmpty)) { + profileVm.syncProfileImageFromAppState(); + } + + // Only load if no data exists in both AppState and ViewModel + if ((_appState.getProfileImageData == null || _appState.getProfileImageData!.isEmpty) && + (profileVm.profileImageData == null || profileVm.profileImageData!.isEmpty)) { + _loadProfileImage(); + } + }); + } + + void _loadProfileImage() { + // Check if profile image is already loaded in AppState + if (_appState.getProfileImageData != null && _appState.getProfileImageData!.isNotEmpty) { + // Image already loaded, no need to call API + return; + } + + final profileVm = context.read(); + final patientID = _appState.getAuthenticatedUser()?.patientId; + + if (patientID != null) { + profileVm.getProfileImage( + patientID: patientID, + onSuccess: (data) { + // Image loaded successfully + }, + onError: (error) { + // Error loading image + }, + ); + } + } + + void _pickImage() { + // Show image picker options without checking permissions first + ImageOptions.showImageOptionsNew( + context, + false, // Don't show files option, only camera and gallery + (base64String, file) async { + try { + print('=== Starting image processing ==='); + print('File path: ${file.path}'); + print('File exists: ${await file.exists()}'); + print('Original file size: ${await file.length() / 1024} KB'); + + // Compress and resize the image + print('Calling compressAndResizeImage...'); + final compressedFile = await ImageCompressionHelper.compressAndResizeImage(file); + + File finalFile; + String finalBase64; + + if (compressedFile == null) { + print('⚠️ Compression failed - using original file as fallback'); + + // Fallback: use original image if compression fails + final originalSize = await file.length(); + final maxSize = 1048576; // 1MB + + if (originalSize > maxSize) { + print('❌ Original file is too large: ${originalSize / 1024} KB'); + if (mounted) { + Utils.showToast( + LocaleKeys.imageSizeTooLarge.tr(context: context), + ); + } + return; + } + + print('✅ Using original file (${originalSize / 1024} KB)'); + finalFile = file; + var bytes = await file.readAsBytes(); + finalBase64 = base64.encode(bytes); + } else { + // Check compressed file size + final fileSize = await compressedFile.length(); + final maxSize = 1048576; // 1MB + print('✅ Compression successful: ${fileSize / 1024} KB'); + + if (fileSize > maxSize) { + print('❌ Compressed file still too large'); + if (mounted) { + Utils.showToast( + LocaleKeys.imageSizeTooLarge.tr(context: context), + ); + } + return; + } + + finalFile = compressedFile; + var bytes = await compressedFile.readAsBytes(); + finalBase64 = base64.encode(bytes); + } + + print('Converting to base64... Length: ${finalBase64.length}'); + + if (mounted) { + setState(() { + _selectedImage = finalFile; + }); + + print('📤 Starting upload...'); + // Upload the image + _uploadImage(finalBase64); + } + + print('=== Image processing complete ==='); + } catch (e, stackTrace) { + print('❌ Error in _pickImage: $e'); + print('Stack trace: $stackTrace'); + if (mounted) { + Utils.showToast( + LocaleKeys.failedToProcessImage.tr(context: context), + ); + } + } + }, + checkCameraPermission: _checkCameraPermission, + checkGalleryPermission: _checkGalleryPermission, + ); + } + + Future _checkCameraPermission() async { + try { + print('=== Checking camera permission ==='); + + // First check current status + PermissionStatus currentStatus = await Permission.camera.status; + print('Current camera permission status: $currentStatus'); + + // If already granted, return true + if (currentStatus.isGranted) { + print('✅ Camera permission already granted'); + return true; + } + + // If denied or permanently denied, show settings dialog + if (currentStatus.isDenied || currentStatus.isPermanentlyDenied) { + // Request permission first + PermissionStatus newStatus = await Permission.camera.request(); + print('Camera permission after request: $newStatus'); + + if (newStatus.isGranted) { + print('✅ Camera permission granted'); + return true; + } + + // Still denied - show settings dialog + print('⚠️ Camera permission denied - showing settings dialog'); + if (mounted) { + showCommonBottomSheetWithoutHeight( + title: LocaleKeys.notice.tr(context: context), + context, + child: Utils.getWarningWidget( + loadingText: LocaleKeys.cameraPermissionMessage.tr(context: context), + isShowActionButtons: true, + onCancelTap: () { + Navigator.pop(context); + }, + onConfirmTap: () async { + openAppSettings(); + }, + ), + callBackFunc: () {}, + isFullScreen: false, + isCloseButtonVisible: true, + ); + } + return false; + } + + // Request permission for the first time + PermissionStatus newStatus = await Permission.camera.request(); + print('Camera permission after request: $newStatus'); + + if (newStatus.isGranted) { + print('✅ Camera permission granted'); + return true; + } + + // Denied - show settings dialog + print('❌ Camera permission denied - showing settings dialog'); + if (mounted) { + showCommonBottomSheetWithoutHeight( + title: LocaleKeys.notice.tr(context: context), + context, + child: Utils.getWarningWidget( + loadingText: LocaleKeys.cameraPermissionMessage.tr(context: context), + isShowActionButtons: true, + onCancelTap: () { + Navigator.pop(context); + }, + onConfirmTap: () async { + openAppSettings(); + }, + ), + callBackFunc: () {}, + isFullScreen: false, + isCloseButtonVisible: true, + ); + } + return false; + } catch (e) { + print('❌ Error checking camera permission: $e'); + if (mounted) { + Utils.showToast( + LocaleKeys.failedToCheckPermissions.tr(context: context), + ); + } + return false; + } + } + + Future _checkGalleryPermission() async { + try { + print('=== Checking gallery permission ==='); + + // Determine which permission to check based on platform and Android version + Permission galleryPermission; + + if (Platform.isIOS) { + galleryPermission = Permission.photos; + } else { + // Android: use photos permission which handles API level differences automatically + galleryPermission = Permission.photos; + } + + // First check current status + PermissionStatus currentStatus = await galleryPermission.status; + print('Current gallery permission status: $currentStatus'); + + // If already granted, return true + if (currentStatus.isGranted || currentStatus.isLimited) { + print('✅ Gallery permission already granted'); + return true; + } + + // If denied or permanently denied, request permission first + if (currentStatus.isDenied || currentStatus.isPermanentlyDenied) { + // Request permission first + PermissionStatus newStatus = await galleryPermission.request(); + print('Gallery permission after request: $newStatus'); + + if (newStatus.isGranted || newStatus.isLimited) { + print('✅ Gallery permission granted'); + return true; + } + + // Still denied - show settings dialog + print('⚠️ Gallery permission denied - showing settings dialog'); + if (mounted) { + showCommonBottomSheetWithoutHeight( + title: LocaleKeys.notice.tr(context: context), + context, + child: Utils.getWarningWidget( + loadingText: LocaleKeys.galleryPermissionMessage.tr(context: context), + isShowActionButtons: true, + onCancelTap: () { + Navigator.pop(context); + }, + onConfirmTap: () async { + openAppSettings(); + }, + ), + callBackFunc: () {}, + isFullScreen: false, + isCloseButtonVisible: true, + ); + } + return false; + } + + // Request permission for the first time + PermissionStatus newStatus = await galleryPermission.request(); + print('Gallery permission after request: $newStatus'); + + if (newStatus.isGranted || newStatus.isLimited) { + print('✅ Gallery permission granted'); + return true; + } + + // Denied - show settings dialog + print('❌ Gallery permission denied - showing settings dialog'); + if (mounted) { + showCommonBottomSheetWithoutHeight( + title: LocaleKeys.notice.tr(context: context), + context, + child: Utils.getWarningWidget( + loadingText: LocaleKeys.galleryPermissionMessage.tr(context: context), + isShowActionButtons: true, + onCancelTap: () { + Navigator.pop(context); + }, + onConfirmTap: () async { + openAppSettings(); + }, + ), + callBackFunc: () {}, + isFullScreen: false, + isCloseButtonVisible: true, + ); + } + return false; + } catch (e) { + print('❌ Error checking gallery permission: $e'); + if (mounted) { + Utils.showToast( + LocaleKeys.failedToCheckPermissions.tr(context: context), + ); + } + return false; + } + } + + void _uploadImage(String base64String) { + final profileVm = context.read(); + final patientID = _appState.getAuthenticatedUser()?.patientId; + + if (patientID != null) { + profileVm.uploadProfileImage( + patientID: patientID, + imageData: base64String, + onSuccess: (data) { + if (mounted) { + setState(() { + _selectedImage = null; // Clear selected image after successful upload + }); + print( + LocaleKeys.profileImageUpdatedSuccessfully.tr(context: context), + ); + } + }, + onError: (error) { + Utils.showToast(error); + }, + ); + } + } + + Widget _buildProfileImage(ProfileSettingsViewModel profileVm) { + final gender = _appState.getAuthenticatedUser()?.gender ?? 1; + final age = _appState.getAuthenticatedUser()?.age ?? 0; + + // Determine the default image based on gender and age + final String defaultImage; + if (gender == 1) { + // Male + defaultImage = age < 7 ? AppAssets.babyBoyImg : AppAssets.maleImg; + } else { + // Female + defaultImage = age < 7 ? AppAssets.babyGirlImg : AppAssets.femaleImg; + } + + // Show selected image if available + if (_selectedImage != null) { + return ClipOval( + child: Image.file( + _selectedImage!, + width: 136.w, + height: 136.h, + fit: BoxFit.cover, + ), + ); + } + + // Check both ViewModel and AppState for profile image + final String? imageData = profileVm.profileImageData ?? _appState.getProfileImageData; + + // Show uploaded image if available + if (imageData != null && imageData.isNotEmpty) { + try { + final bytes = base64Decode(imageData); + return ClipOval( + child: Image.memory( + bytes, + width: 136.w, + height: 136.h, + fit: BoxFit.cover, + ), + ); + } catch (e) { + print('Error decoding profile image: $e'); + // If decoding fails, show default image + return Image.asset( + defaultImage, + width: 136.w, + height: 136.h, + ); + } + } + + // Show default image + return Image.asset( + defaultImage, + width: 136.w, + height: 136.h, + ); + } + + @override + Widget build(BuildContext context) { + return Consumer( + builder: (context, profileVm, _) { + return Center( + child: Stack( + children: [ + // Profile Image + profileVm.isProfileImageLoading + ? Container( + width: 136.w, + height: 136.h, + decoration: BoxDecoration( + shape: BoxShape.circle, + color: AppColors.greyTextColor.withValues(alpha: 0.2), + ), + ).toShimmer2(isShow: true) + : _buildProfileImage(profileVm), + + // Edit button + Positioned( + right: 0, + top: 0, + child: GestureDetector( + onTap: () { + if (!profileVm.isUploadingProfileImage) { + _pickImage(); + } + }, + child: Container( + width: 32.w, + height: 32.h, + decoration: BoxDecoration( + color: AppColors.primaryRedColor, + shape: BoxShape.circle, + border: Border.all( + color: AppColors.whiteColor, + width: 2.w, + ), + ), + child: profileVm.isUploadingProfileImage + ? SizedBox( + width: 16.w, + height: 16.h, + child: CircularProgressIndicator( + strokeWidth: 2.w, + valueColor: AlwaysStoppedAnimation( + AppColors.whiteColor, + ), + ).paddingAll(8.w), + ) + : Icon( + Icons.camera_alt, + color: AppColors.whiteColor, + size: 16.w, + ), + ), + ), + ), + ], + ).paddingOnly(bottom: 24.h), + ); + }, + ); + } +} + diff --git a/lib/presentation/profile_settings/widgets/update_email_widget.dart b/lib/presentation/profile_settings/widgets/update_email_widget.dart index 0b27dc82..d2633b93 100644 --- a/lib/presentation/profile_settings/widgets/update_email_widget.dart +++ b/lib/presentation/profile_settings/widgets/update_email_widget.dart @@ -56,7 +56,7 @@ class _UpdateEmailDialogState extends State { child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ - "Enter the new email address to be updated: ".toText16(textAlign: TextAlign.start, weight: FontWeight.w500), + "Enter the new email address to be updated: ".toText16(textAlign: TextAlign.start, isBold: true), SizedBox(height: 12.h), TextInputWidget( labelText: LocaleKeys.email.tr(), diff --git a/lib/presentation/profile_settings/widgets/update_emergency_contact_widget.dart b/lib/presentation/profile_settings/widgets/update_emergency_contact_widget.dart index 71505dff..e1bb59ad 100644 --- a/lib/presentation/profile_settings/widgets/update_emergency_contact_widget.dart +++ b/lib/presentation/profile_settings/widgets/update_emergency_contact_widget.dart @@ -56,7 +56,7 @@ class _UpdateEmergencyContactDialogState extends State { // borderColor: model.isSortByClinic ? AppColors.primaryRedColor : AppColors.textColor.withValues(alpha: 0.2), // textColor: model.isSortByClinic ? AppColors.primaryRedColor : AppColors.blackColor, // fontSize: 12, - // fontWeight: FontWeight.w500, + // isBold: true, // borderRadius: 10, // padding: EdgeInsets.fromLTRB(10, 0, 10, 0), // height: 40.h, @@ -123,7 +123,7 @@ class _RadiologyOrdersPageState extends State { // borderColor: model.isSortByClinic ? AppColors.textColor.withValues(alpha: 0.2) : AppColors.primaryRedColor, // textColor: model.isSortByClinic ? AppColors.blackColor : AppColors.primaryRedColor, // fontSize: 12, - // fontWeight: FontWeight.w500, + // isBold: true, // borderRadius: 10, // padding: EdgeInsets.fromLTRB(10, 0, 10, 0), // height: 40.h, @@ -146,6 +146,7 @@ class _RadiologyOrdersPageState extends State { model.filterRadiologyReports(''); }); }, + isEnglishOnly: true // chipType: ChipTypeEnum.alert, // isSelected: true, ) @@ -236,7 +237,7 @@ class _RadiologyOrdersPageState extends State { // SizedBox(height: 8.h), // Text( // displayName, - // style: TextStyle(fontSize: 16.h, fontWeight: FontWeight.w600), + // style: TextStyle(fontSize: 16.h, isBold: true), // overflow: TextOverflow.ellipsis, // ), // ], @@ -278,7 +279,7 @@ class _RadiologyOrdersPageState extends State { // ).circle(100), // SizedBox(width: 8.h), // Expanded( - // child: (order.doctorName ?? '').toString().toText14(weight: FontWeight.w500), + // child: (order.doctorName ?? '').toString().toText14(isBold: true), // ), // ], // ), @@ -328,7 +329,7 @@ class _RadiologyOrdersPageState extends State { // borderColor: AppColors.secondaryLightRedColor, // textColor: AppColors.primaryRedColor, // fontSize: 14, - // fontWeight: FontWeight.w500, + // isBold: true, // borderRadius: 12, // padding: const EdgeInsets.fromLTRB(10, 0, 10, 0), // height: 40.h, @@ -406,7 +407,7 @@ class _RadiologyOrdersPageState extends State { fit: BoxFit.cover, ).circle(100), SizedBox(width: 8.h), - Expanded(child: (group.doctorName ?? "").toString().toText14(weight: FontWeight.w500)), + Expanded(child: (group.doctorName ?? "").toString().toText14(isBold: true)), ], ), SizedBox(height: 8.h), @@ -433,8 +434,8 @@ class _RadiologyOrdersPageState extends State { Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ - "• ${group.procedureName.toString().trim() ?? ""}".toText14(weight: FontWeight.w500), - // "Lorem ipsum text".toText12(fontWeight: FontWeight.w500, color: AppColors.textColorLight), + "• ${group.procedureName.toString().trim() ?? ""}".toText14(isBold: true, isEnglishOnly: true), + // "Lorem ipsum text".toText12(isBold: true, color: AppColors.textColorLight), SizedBox(height: 16.h), CustomButton( text: LocaleKeys.viewReport.tr(), @@ -449,7 +450,7 @@ class _RadiologyOrdersPageState extends State { borderColor: AppColors.infoColor.withAlpha(0), textColor: AppColors.infoColor, fontSize: (isFoldable || isTablet) ? 12.f : 14.f, - fontWeight: FontWeight.w500, + fontWeight: FontWeight.w600, borderRadius: 12.r, padding: EdgeInsets.fromLTRB(10.w, 0, 10.w, 0), height: 40.h, @@ -491,10 +492,10 @@ class _RadiologyOrdersPageState extends State { // return Column( // crossAxisAlignment: CrossAxisAlignment.start, // children: [ - // "• ${order.procedureName ?? ""}".toText14(weight: FontWeight.w500), - // "Lorem ipsum text".toText12(fontWeight: FontWeight.w500, color: AppColors.textColorLight), + // "• ${order.procedureName ?? ""}".toText14(isBold: true), + // "Lorem ipsum text".toText12(isBold: true, color: AppColors.textColorLight), // // SizedBox(height: 4.h), - // // order.testDetails![index].testDescriptionEn!.toText12(fontWeight: FontWeight.w500, color: AppColors.textColorLight), + // // order.testDetails![index].testDescriptionEn!.toText12(isBold: true, color: AppColors.textColorLight), // // Row( // // mainAxisSize: MainAxisSize.min, // // children: [ @@ -505,7 +506,7 @@ class _RadiologyOrdersPageState extends State { // // fit: BoxFit.cover, // // ).circle(100), // // SizedBox(width: 8.h), - // // Expanded(child: (order.doctorName ?? order.doctorNameEnglish ?? "").toString().toText14(weight: FontWeight.w500)), + // // Expanded(child: (order.doctorName ?? order.doctorNameEnglish ?? "").toString().toText14(isBold: true)), // // ], // // ), // // SizedBox(height: 8.h), @@ -537,7 +538,7 @@ class _RadiologyOrdersPageState extends State { // // // borderColor: AppColors.greyColor, // // // textColor: AppColors.blackColor, // // // fontSize: 10, - // // // fontWeight: FontWeight.w500, + // // // isBold: true, // // // borderRadius: 8, // // // padding: EdgeInsets.fromLTRB(10, 0, 10, 0), // // // height: 24.h, @@ -550,7 +551,7 @@ class _RadiologyOrdersPageState extends State { // // // borderColor: AppColors.greyColor, // // // textColor: AppColors.blackColor, // // // fontSize: 10, - // // // fontWeight: FontWeight.w500, + // // // isBold: true, // // // borderRadius: 8, // // // padding: EdgeInsets.fromLTRB(10, 0, 10, 0), // // // height: 24.h, @@ -567,7 +568,7 @@ class _RadiologyOrdersPageState extends State { // // // borderColor: AppColors.greyColor, // // // textColor: AppColors.blackColor, // // // fontSize: 10, - // // // fontWeight: FontWeight.w500, + // // // isBold: true, // // // borderRadius: 8, // // // padding: EdgeInsets.fromLTRB(10, 0, 10, 0), // // // height: 24.h, @@ -627,7 +628,7 @@ class _RadiologyOrdersPageState extends State { // // borderColor: AppColors.secondaryLightRedColor, // // textColor: AppColors.primaryRedColor, // // fontSize: 14, - // // fontWeight: FontWeight.w500, + // // isBold: true, // // borderRadius: 12, // // padding: EdgeInsets.fromLTRB(10, 0, 10, 0), // // height: 40.h, @@ -657,7 +658,7 @@ class _RadiologyOrdersPageState extends State { // borderColor: AppColors.infoColor.withAlpha(0), // textColor: AppColors.infoColor, // fontSize: (isFoldable || isTablet) ? 12.f : 14.f, - // fontWeight: FontWeight.w500, + // isBold: true, // borderRadius: 12.r, // padding: EdgeInsets.fromLTRB(10.w, 0, 10.w, 0), // height: 40.h, diff --git a/lib/presentation/radiology/radiology_result_page.dart b/lib/presentation/radiology/radiology_result_page.dart index 8fbdd8e6..445bbd45 100644 --- a/lib/presentation/radiology/radiology_result_page.dart +++ b/lib/presentation/radiology/radiology_result_page.dart @@ -7,11 +7,15 @@ 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/radiology/models/resp_models/patient_radiology_response_model.dart'; +import 'package:hmg_patient_app_new/features/radiology/models/resp_models/radiology_ai_analysis_response_model.dart'; import 'package:hmg_patient_app_new/features/radiology/radiology_view_model.dart'; import 'package:hmg_patient_app_new/generated/locale_keys.g.dart'; +import 'package:hmg_patient_app_new/presentation/radiology/radiology_ai_analysis_widget.dart'; +import 'package:hmg_patient_app_new/services/dialog_service.dart'; import 'package:hmg_patient_app_new/widgets/appbar/collapsing_list_view.dart'; import 'package:hmg_patient_app_new/theme/colors.dart'; import 'package:hmg_patient_app_new/widgets/buttons/custom_button.dart'; @@ -23,9 +27,9 @@ import 'package:url_launcher/url_launcher.dart'; import 'dart:ui' as ui; class RadiologyResultPage extends StatefulWidget { - RadiologyResultPage({super.key, required this.patientRadiologyResponseModel}); + const RadiologyResultPage({super.key, required this.patientRadiologyResponseModel}); - PatientRadiologyResponseModel patientRadiologyResponseModel; + final PatientRadiologyResponseModel patientRadiologyResponseModel; @override State createState() => _RadiologyResultPageState(); @@ -33,6 +37,8 @@ class RadiologyResultPage extends StatefulWidget { class _RadiologyResultPageState extends State { late RadiologyViewModel radiologyViewModel; + final GlobalKey _aiAnalysisKey = GlobalKey(); + RadiologyAiAnalysisResponse? _previousAiData; @override void initState() { @@ -42,183 +48,239 @@ class _RadiologyResultPageState extends State { super.initState(); } + @override + void dispose() { + // Clear AI analysis data when leaving the screen + radiologyViewModel.closeRadiologyAIAnalysis(); + radiologyViewModel.setIsRadiologyAIAnalysisNeedsToBeShown(true); + super.dispose(); + } + @override Widget build(BuildContext context) { - radiologyViewModel = Provider.of(context); + radiologyViewModel = Provider.of(context, listen: false); AppState _appState = getIt.get(); return Scaffold( backgroundColor: AppColors.bgScaffoldColor, - body: Column( - children: [ - Expanded( - child: CollapsingListView( - title: LocaleKeys.radiologyResult.tr(context: context), - downloadReport: () async { - LoaderBottomSheet.showLoader(); - await radiologyViewModel - .getRadiologyPDF( - patientRadiologyResponseModel: widget.patientRadiologyResponseModel, - authenticatedUser: _appState.getAuthenticatedUser()!, - onError: (err) { - LoaderBottomSheet.hideLoader(); - showCommonBottomSheetWithoutHeight( - context, - child: Utils.getErrorWidget(loadingText: err), - callBackFunc: () {}, - isFullScreen: false, - isCloseButtonVisible: true, - ); - }) - .then((val) async { - LoaderBottomSheet.hideLoader(); - if (radiologyViewModel.patientRadiologyReportPDFBase64.isNotEmpty) { - String path = await Utils.createFileFromString(radiologyViewModel.patientRadiologyReportPDFBase64, "pdf"); - try { - OpenFilex.open(path); - } catch (ex) { - showCommonBottomSheetWithoutHeight( - context, - child: Utils.getErrorWidget(loadingText: "Cannot open file"), - callBackFunc: () {}, - isFullScreen: false, - isCloseButtonVisible: true, - ); + body: Consumer(builder: (context, radVM, child) { + // Detect changes in radiologyAiAnalysisResponse + WidgetsBinding.instance.addPostFrameCallback((_) { + if (radVM.radiologyAiAnalysisResponse != null && _previousAiData == null) { + // AI data just became available — scroll to it + final ctx = _aiAnalysisKey.currentContext; + if (ctx != null) { + Scrollable.ensureVisible( + ctx, + duration: const Duration(milliseconds: 400), + curve: Curves.easeOut, + ); + } + } else if (radVM.radiologyAiAnalysisResponse == null && _previousAiData != null) { + // AI data just became null — scroll to top + final scrollable = Scrollable.maybeOf(context); + scrollable?.position.animateTo( + 0, + duration: const Duration(milliseconds: 400), + curve: Curves.easeOut, + ); + } + _previousAiData = radVM.radiologyAiAnalysisResponse; + }); + + return Column( + children: [ + Expanded( + child: CollapsingListView( + title: LocaleKeys.radiologyResult.tr(context: context), + downloadReport: () async { + LoaderBottomSheet.showLoader(); + await radiologyViewModel + .getRadiologyPDF( + patientRadiologyResponseModel: widget.patientRadiologyResponseModel, + authenticatedUser: _appState.getAuthenticatedUser()!, + onError: (err) { + LoaderBottomSheet.hideLoader(); + showCommonBottomSheetWithoutHeight( + context, + child: Utils.getErrorWidget(loadingText: err), + callBackFunc: () {}, + isFullScreen: false, + isCloseButtonVisible: true, + ); + }) + .then((val) async { + LoaderBottomSheet.hideLoader(); + if (radiologyViewModel.patientRadiologyReportPDFBase64.isNotEmpty) { + String path = await Utils.createFileFromString(radiologyViewModel.patientRadiologyReportPDFBase64, "pdf"); + try { + OpenFilex.open(path); + } catch (ex) { + showCommonBottomSheetWithoutHeight( + context, + child: Utils.getErrorWidget(loadingText: "Cannot open file"), + callBackFunc: () {}, + isFullScreen: false, + isCloseButtonVisible: true, + ); + } } - } - }); - }, - // viewImage: () { - // if (radiologyViewModel.radiologyImageURL.isNotEmpty) { - // Uri uri = Uri.parse(radiologyViewModel.radiologyImageURL); - // launchUrl(uri, mode: LaunchMode.platformDefault, webOnlyWindowName: ""); - // } else { - // Utils.showToast("Radiology image not available"); - // } - // }, - child: SingleChildScrollView( - child: Padding( - padding: EdgeInsets.symmetric(horizontal: 24.h), - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - SizedBox(height: 24.h), - Container( - decoration: RoundedRectangleBorder().toSmoothCornerDecoration( - color: AppColors.whiteColor, - borderRadius: 20.h, - hasShadow: true, + }); + }, + // viewImage: () { + // if (radiologyViewModel.radiologyImageURL.isNotEmpty) { + // Uri uri = Uri.parse(radiologyViewModel.radiologyImageURL); + // launchUrl(uri, mode: LaunchMode.platformDefault, webOnlyWindowName: ""); + // } else { + // Utils.showToast("Radiology image not available"); + // } + // }, + child: SingleChildScrollView( + child: Padding( + padding: EdgeInsets.symmetric(horizontal: 24.h), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + SizedBox(height: 24.h), + Container( + decoration: RoundedRectangleBorder().toSmoothCornerDecoration( + color: AppColors.whiteColor, + borderRadius: 20.h, + hasShadow: true, + ), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + SizedBox(height: 16.h), + // widget.patientRadiologyResponseModel.description!.toText16(isBold: true), + SizedBox(height: 8.h), + Directionality( + textDirection: ui.TextDirection.ltr, + child: widget.patientRadiologyResponseModel.reportData!.trim().toText12(isBold: true, color: AppColors.textColorLight, isEnglishOnly: true), + ), + SizedBox(height: 16.h), + // CustomButton( + // 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"); + // } + // }, + // backgroundColor: AppColors.primaryRedColor, + // borderColor: AppColors.primaryRedColor, + // textColor: Colors.white, + // fontSize: 14, + // isBold: true, + // borderRadius: 12, + // padding: EdgeInsets.fromLTRB(10, 0, 10, 0), + // height: 40.h, + // icon: AppAssets.download, + // iconColor: Colors.white, + // iconSize: 20.h, + // ), + // SizedBox(height: 16.h), + ], + ).paddingSymmetrical(16.h, 0.h), ), - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - SizedBox(height: 16.h), - // widget.patientRadiologyResponseModel.description!.toText16(isBold: true), - SizedBox(height: 8.h), - Directionality( - textDirection: ui.TextDirection.ltr, - child: widget.patientRadiologyResponseModel.reportData!.trim().toText12(isBold: true, color: AppColors.textColorLight, isEnglishOnly: true), - ), - SizedBox(height: 16.h), - // CustomButton( - // 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"); - // } - // }, - // backgroundColor: AppColors.primaryRedColor, - // borderColor: AppColors.primaryRedColor, - // textColor: Colors.white, - // fontSize: 14, - // fontWeight: FontWeight.w500, - // borderRadius: 12, - // padding: EdgeInsets.fromLTRB(10, 0, 10, 0), - // height: 40.h, - // icon: AppAssets.download, - // iconColor: Colors.white, - // iconSize: 20.h, - // ), - // SizedBox(height: 16.h), - ], - ).paddingSymmetrical(16.h, 0.h), - ), - SizedBox(height: 24.h), - ], + SizedBox(height: 24.h), + if (radVM.radiologyAiAnalysisResponse != null) + RadiologyAiAnalysisWidget( + key: _aiAnalysisKey, + data: radVM.radiologyAiAnalysisResponse!, + ), + ], + ), ), ), ), ), - ), - Container( - decoration: RoundedRectangleBorder().toSmoothCornerDecoration( - color: AppColors.whiteColor, - borderRadius: 24.h, - hasShadow: true, - ), - child: Column( - children: [ - SizedBox(height: 24.h), - widget.patientRadiologyResponseModel.dIAPACSURL != "" - ? CustomButton( - text: LocaleKeys.openRad.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"); - } - }, - backgroundColor: AppColors.primaryRedColor, - borderColor: AppColors.primaryRedColor, - textColor: Colors.white, - fontSize: 16, - fontWeight: FontWeight.w500, - borderRadius: 12, - padding: EdgeInsets.fromLTRB(10, 0, 10, 0), - height: 56.h, - icon: AppAssets.imageIcon, - iconColor: Colors.white, - iconSize: 20.h, - ).paddingSymmetrical(24.h, 0.h) - : SizedBox.shrink(), - Container( - height: 56.h, - decoration: BoxDecoration( - borderRadius: BorderRadius.circular(12.r), - gradient: LinearGradient( - begin: Alignment.centerLeft, - end: Alignment.centerRight, - stops: [0.236, 1.0], // 53.6% and 100% - colors: [ - Color(0xFF8A38F5), // Transparent - Color(0xFFE20BBB), // Solid #F8F8F8 + Container( + decoration: RoundedRectangleBorder().toSmoothCornerDecoration( + color: AppColors.whiteColor, + borderRadius: 24.h, + hasShadow: true, + ), + child: Column( + children: [ + SizedBox(height: 24.h), + widget.patientRadiologyResponseModel.dIAPACSURL != "" + ? CustomButton( + text: LocaleKeys.openRad.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"); + } + }, + backgroundColor: AppColors.primaryRedColor, + borderColor: AppColors.primaryRedColor, + textColor: Colors.white, + fontSize: 16, + isBold: true, + borderRadius: 12, + padding: EdgeInsets.fromLTRB(10, 0, 10, 0), + height: 56.h, + icon: AppAssets.imageIcon, + iconColor: Colors.white, + iconSize: 20.h, + ).paddingSymmetrical(24.h, 0.h) + : SizedBox.shrink(), + Utils.havePrivilege(116) ? radVM.radiologyAiAnalysisResponse == null + ? Container( + height: 56.h, + decoration: BoxDecoration( + borderRadius: BorderRadius.circular(12.r), + gradient: LinearGradient( + begin: Alignment.centerLeft, + end: Alignment.centerRight, + stops: [0.236, 1.0], // 53.6% and 100% + colors: [ + Color(0xFF8A38F5), // Transparent + Color(0xFFE20BBB), // Solid #F8F8F8 + ], + ), + ), + child: Row( + crossAxisAlignment: CrossAxisAlignment.center, + mainAxisAlignment: MainAxisAlignment.center, + children: [ + Padding( + padding: EdgeInsets.only(right: 4.w, left: 4.w), + child: Utils.buildSvgWithAssets(icon: AppAssets.aiOverView, width: 16.h, height: 16.h, iconColor: Colors.white), + ), + LocaleKeys.generateAiAnalysisRadResult.tr(context: context).toText16(isBold: true, color: Colors.white) + ], + ), + ).paddingSymmetrical(24.h, 12.h).onPress(() async { + final _dialogService = getIt.get(); + await _dialogService.showCommonBottomSheetWithoutH( + message: LocaleKeys.aiDisclaimerRad.tr(), + label: LocaleKeys.consent.tr(), + okLabel: LocaleKeys.acceptLbl.tr(), + cancelLabel: LocaleKeys.rejectView.tr(), + onOkPressed: () { + context.pop(); + radiologyViewModel.getRadiologyAiAnalysis( + reportData: widget.patientRadiologyResponseModel.reportData ?? "", + loadingText: LocaleKeys.loadingAIAnalysis.tr(context: context), + langId: _appState.getLanguageID(), + ); + }, + onCancelPressed: () { + context.pop(); + }, + ); + }) : SizedBox.shrink() : SizedBox(height: 24.h), ], ), ), - child: Row( - crossAxisAlignment: CrossAxisAlignment.center, - mainAxisAlignment: MainAxisAlignment.center, - children: [ - Padding( - padding: EdgeInsets.only(right: 4.w, left: 4.w), - child: Utils.buildSvgWithAssets(icon: AppAssets.aiOverView, width: 16.h, height: 16.h, iconColor: Colors.white), - ), - LocaleKeys.generateAiAnalysisResult.tr(context: context).toText16(isBold: true, color: Colors.white) - ], - ), - ).paddingSymmetrical(24.h, 12.h).onPress(() { - // Need to implement the Radiology AI analysis result generation functionality here - Utils.showToast("Radiology AI analysis result generation functionality is not implemented yet"); - }), - ], - ), - ), - ], - ), + ], + ); + }), ); } } diff --git a/lib/presentation/radiology/search_radiology.dart b/lib/presentation/radiology/search_radiology.dart index 7c14fd1d..6f78713c 100644 --- a/lib/presentation/radiology/search_radiology.dart +++ b/lib/presentation/radiology/search_radiology.dart @@ -78,8 +78,9 @@ class _SearchRadiologyContentContentState extends State crossAxisAlignment: CrossAxisAlignment.start, children: [ TextInputWidget( - labelText: "Search radiology results", - hintText: "Type test description", + fontFamily: "Poppins", + labelText: LocaleKeys.search.tr(context: context), + hintText: "", controller: searchEditingController, isEnable: true, prefix: null, @@ -93,7 +94,7 @@ class _SearchRadiologyContentContentState extends State ), SizedBox(height: ResponsiveExtension(20).h), if (filteredSuggestions.isNotEmpty) ...[ - "Suggestions".toText16(isBold: true), + LocaleKeys.suggestions.tr(context: context).toText16(isBold: true), ], ], ), @@ -146,8 +147,8 @@ class SuggestionChip extends StatelessWidget { child: label.toText12( // color: isSelected ? Colors.white : Colors.black87, color: AppColors.textColor, - fontWeight: FontWeight.w500, - ), + isBold: true, + isEnglishOnly: true), ), ); } diff --git a/lib/presentation/rate_appointment/rate_appointment_doctor.dart b/lib/presentation/rate_appointment/rate_appointment_doctor.dart index dfc15cd3..9a3925a1 100644 --- a/lib/presentation/rate_appointment/rate_appointment_doctor.dart +++ b/lib/presentation/rate_appointment/rate_appointment_doctor.dart @@ -1,5 +1,7 @@ import 'package:easy_localization/easy_localization.dart'; import 'package:flutter/material.dart'; +import 'package:hmg_patient_app_new/core/app_state.dart'; +import 'package:hmg_patient_app_new/core/dependencies.dart'; import 'package:hmg_patient_app_new/core/utils/size_utils.dart'; import 'package:hmg_patient_app_new/extensions/string_extensions.dart'; import 'package:hmg_patient_app_new/extensions/widget_extensions.dart'; @@ -41,7 +43,6 @@ class _RateAppointmentDoctorState extends State { @override Widget build(BuildContext context) { - myAppointmentsViewModel = Provider.of(context, listen: false); appointmentRatingViewModel = Provider.of(context, listen: false); @@ -91,7 +92,7 @@ class _RateAppointmentDoctorState extends State { child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ - "Please rate the doctor".toText16(isBold: true), + LocaleKeys.rateTheDoctor.tr(context: context).toText16(isBold: true), SizedBox(height: 12), Row( mainAxisAlignment: MainAxisAlignment.center, @@ -140,16 +141,23 @@ class _RateAppointmentDoctorState extends State { child: Padding( padding: EdgeInsets.all(16.0), child: TextField( - maxLines: 4, + // style: TextStyle( + // fontSize: 14.f, + // fontWeight: FontWeight.w400, + // color: Color(0xff2B353E), + // letterSpacing: 0, + // fontFamily: getIt.get().getLanguageCode() == "ar" ? 'CairoArabic' : 'Poppins', + // ), decoration: InputDecoration.collapsed( hintText: LocaleKeys.notes.tr(context: context), hintStyle: TextStyle( - fontSize: 16.f, - fontWeight: FontWeight.w600, - color: Color(0xff2B353E), - letterSpacing: -0.64, - height: 23 / 16)), + fontSize: 16.f, + fontWeight: FontWeight.w600, + letterSpacing: -0.4, + fontFamily: getIt.get().getLanguageCode() == "ar" ? 'CairoArabic' : 'Poppins', + ), + ), onChanged: (value) { setState(() { note = value; @@ -185,20 +193,15 @@ class _RateAppointmentDoctorState extends State { Expanded( child: CustomButton( text: LocaleKeys.submit.tr(context: context), - onPressed: () async { - // Set up clinic rating and show clinic rating view - // appointmentRatingViewModel!.setTitle(LocaleKeys.rateDoctor.tr(context: context),); - // appointmentRatingViewModel!.setSubTitle(LocaleKeys.howWasYourLastVisitWithDoctor.tr(context: context),); - // appointmentRatingViewModel!.setClinicOrDoctor(true); - - LoaderBottomSheet.showLoader(); - await appointmentRatingViewModel!.submitDoctorRating(docRate: rating, docNote: note); - // await appointmentRatingViewModel!.submitClinicRating(clinicRate: rating, clinicNote: note); - LoaderBottomSheet.hideLoader(); - Navigator.pop(context); - - setState(() {}); - }, + onPressed: rating > 0 + ? () { + LoaderBottomSheet.showLoader(); + appointmentRatingViewModel!.submitDoctorRating(docRate: rating, docNote: note).then((_) { + LoaderBottomSheet.hideLoader(); + Navigator.pop(context); + }); + } + : null, ), ), ], diff --git a/lib/presentation/servicesPriceList/services_price_list_page.dart b/lib/presentation/servicesPriceList/services_price_list_page.dart index 0ee81ae3..a2e4fbb6 100644 --- a/lib/presentation/servicesPriceList/services_price_list_page.dart +++ b/lib/presentation/servicesPriceList/services_price_list_page.dart @@ -57,7 +57,7 @@ class _ServicesPriceListPageState extends State { ), child: Padding( padding: EdgeInsets.symmetric(horizontal: 16.w, vertical: 16.h), - child: LocaleKeys.servicePriceListDesc.tr(context: context).toText12(fontWeight: FontWeight.w500, color: AppColors.textColorLight), + child: LocaleKeys.servicePriceListDesc.tr(context: context).toText12(isBold: true, color: AppColors.textColorLight), ), ), SizedBox(height: 16.h), @@ -119,7 +119,7 @@ class _ServicesPriceListPageState extends State { children: [ Expanded( child: (appState.isArabic() ? service.nameAR ?? service.nameEN ?? '' : service.nameEN ?? service.nameAR ?? '').toText16( - weight: FontWeight.w500, + weight: FontWeight.w600, color: AppColors.textColor, ), ), @@ -146,7 +146,7 @@ class _ServicesPriceListPageState extends State { ), child: Padding( padding: EdgeInsets.symmetric(horizontal: 16.w, vertical: 16.h), - child: LocaleKeys.servicePriceListRights.tr(context: context).toText12(fontWeight: FontWeight.w500, color: AppColors.textColorLight), + child: LocaleKeys.servicePriceListRights.tr(context: context).toText12(isBold: true, color: AppColors.textColorLight), ), ), ], diff --git a/lib/presentation/smartwatches/activity_detail.dart b/lib/presentation/smartwatches/activity_detail.dart index 78527140..2d2532b5 100644 --- a/lib/presentation/smartwatches/activity_detail.dart +++ b/lib/presentation/smartwatches/activity_detail.dart @@ -113,8 +113,8 @@ class _ActivityDetailsState extends State { Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ - widget.sectionName.capitalizeFirstofEach.toText32(weight: FontWeight.w600, color: AppColors.textColor), - "Average".toText12(fontWeight: FontWeight.w500, color: AppColors.greyTextColor) + widget.sectionName.capitalizeFirstofEach.toText32(isBold: true, color: AppColors.textColor), + "Average".toText12(isBold: true, color: AppColors.greyTextColor) ], ), Selector>( @@ -127,10 +127,10 @@ class _ActivityDetailsState extends State { textBaseline: TextBaseline.alphabetic, spacing: 4.w, children: [ - (averageAsDouble?.toStringAsFixed(2) ?? averageAsString ?? "N/A").toText24(color: AppColors.textGreenColor, fontWeight: FontWeight.w600), + (averageAsDouble?.toStringAsFixed(2) ?? averageAsString ?? "N/A").toText24(color: AppColors.textGreenColor, isBold: true), Visibility( visible: averageAsDouble != null || averageAsString != null, - child: widget.uom.toText12(color: AppColors.greyTextColor, fontWeight: FontWeight.w500) + child: widget.uom.toText12(color: AppColors.greyTextColor, isBold: true) ) ], ); @@ -279,7 +279,7 @@ class _ActivityDetailsState extends State { TextStyle( color: Colors.black, fontSize: 12.f, - fontWeight: FontWeight.w500, + fontWeight: FontWeight.w600, ), ); } diff --git a/lib/presentation/smartwatches/huawei_health_example.dart b/lib/presentation/smartwatches/huawei_health_example.dart index bf569a6a..857d26aa 100644 --- a/lib/presentation/smartwatches/huawei_health_example.dart +++ b/lib/presentation/smartwatches/huawei_health_example.dart @@ -14,7 +14,7 @@ // class _HuaweiHealthExampleState extends State { // /// Styles // static const TextStyle cardTitleTextStyle = TextStyle( -// fontWeight: FontWeight.w500, +// isBold: true, // fontSize: 18, // ); // static const EdgeInsets componentPadding = EdgeInsets.all(8.0); diff --git a/lib/presentation/smartwatches/smart_watch_activity.dart b/lib/presentation/smartwatches/smart_watch_activity.dart index 678fd715..f6c1baa6 100644 --- a/lib/presentation/smartwatches/smart_watch_activity.dart +++ b/lib/presentation/smartwatches/smart_watch_activity.dart @@ -216,14 +216,14 @@ class SmartWatchActivity extends StatelessWidget { title.toText16( weight: FontWeight.w600, color: AppColors.textColor), ], ), - description.toText12(fontWeight: FontWeight.w500, color: AppColors.greyTextColor), + description.toText12(isBold: true, color: AppColors.greyTextColor), Row( crossAxisAlignment: CrossAxisAlignment.baseline, textBaseline: TextBaseline.alphabetic, spacing: 2.h, children: [ - result.toText21(weight: FontWeight.w600, color: AppColors.textColor), - unitsOfMeasure.toText10(weight: FontWeight.w500, color:AppColors.greyTextColor ), + result.toText21(isBold: true, color: AppColors.textColor), + unitsOfMeasure.toText10(isBold: true, color:AppColors.greyTextColor ), if(resultSecondValue != null) Visibility( visible: resultSecondValue != null , @@ -233,8 +233,8 @@ class SmartWatchActivity extends StatelessWidget { spacing: 2.h, children: [ SizedBox(width: 2.w,), - resultSecondValue.toText21(weight: FontWeight.w600, color: AppColors.textColor), - unitOfSecondMeasure!.toText10(weight: FontWeight.w500, color:AppColors.greyTextColor ) + resultSecondValue.toText21(isBold: true, color: AppColors.textColor), + unitOfSecondMeasure!.toText10(isBold: true, color:AppColors.greyTextColor ) ], ), ) diff --git a/lib/presentation/smartwatches/smartwatch_home_page.dart b/lib/presentation/smartwatches/smartwatch_home_page.dart index 800ff030..b6c98ee9 100644 --- a/lib/presentation/smartwatches/smartwatch_home_page.dart +++ b/lib/presentation/smartwatches/smartwatch_home_page.dart @@ -52,7 +52,7 @@ class SmartwatchHomePage extends StatelessWidget { borderColor: AppColors.infoColor.withAlpha(0), textColor: AppColors.infoColor, fontSize: 16.f, - fontWeight: FontWeight.w500, + isBold: true, borderRadius: 12.r, height: 50.h, icon: AppAssets.ask_doctor_icon, @@ -101,7 +101,7 @@ class SmartwatchHomePage extends StatelessWidget { borderColor: AppColors.primaryRedColor.withAlpha(0), textColor: AppColors.primaryRedColor, fontSize: 16.f, - fontWeight: FontWeight.w500, + isBold: true, borderRadius: 12.r, height: 40.h, ).paddingSymmetrical(16.w, 12.h), @@ -133,7 +133,7 @@ class SmartwatchHomePage extends StatelessWidget { borderColor: AppColors.primaryRedColor.withAlpha(0), textColor: AppColors.primaryRedColor, fontSize: 16.f, - fontWeight: FontWeight.w500, + isBold: true, borderRadius: 12.r, height: 40.h, ).paddingSymmetrical(16.w, 12.h), @@ -167,7 +167,7 @@ class SmartwatchHomePage extends StatelessWidget { borderColor: AppColors.primaryRedColor.withAlpha(0), textColor: AppColors.primaryRedColor, fontSize: 16.f, - fontWeight: FontWeight.w500, + isBold: true, borderRadius: 12.r, height: 40.h, ).paddingSymmetrical(16.w, 12.h), @@ -202,7 +202,7 @@ class SmartwatchHomePage extends StatelessWidget { borderColor: AppColors.primaryRedColor.withAlpha(0), textColor: AppColors.primaryRedColor, fontSize: 16.f, - fontWeight: FontWeight.w500, + isBold: true, 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 fa5f3135..6a2aae29 100644 --- a/lib/presentation/smartwatches/smartwatch_instructions_page.dart +++ b/lib/presentation/smartwatches/smartwatch_instructions_page.dart @@ -42,7 +42,7 @@ class SmartwatchInstructionsPage extends StatelessWidget { borderColor: AppColors.primaryRedColor, textColor: AppColors.whiteColor, fontSize: 16.f, - fontWeight: FontWeight.w500, + isBold: true, borderRadius: 12.r, height: 50.h, ).paddingSymmetrical(24.w, 30.h), @@ -93,8 +93,8 @@ class SmartwatchInstructionsPage extends StatelessWidget { ), - title.toText16(weight: FontWeight.w600, color: AppColors.textColor), - description.toText12(fontWeight: FontWeight.w500, color: descriptionTextColor) + title.toText16(isBold: true, color: AppColors.textColor), + description.toText12(isBold: true, color: descriptionTextColor) ], ); } diff --git a/lib/presentation/smartwatches/widgets/supported_watches_list.dart b/lib/presentation/smartwatches/widgets/supported_watches_list.dart index 839af802..f28d57c5 100644 --- a/lib/presentation/smartwatches/widgets/supported_watches_list.dart +++ b/lib/presentation/smartwatches/widgets/supported_watches_list.dart @@ -25,11 +25,11 @@ class SupportedWatchesList extends StatelessWidget { ], ), 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), + "• Apple Watch Ultra".needTranslation.toText12(isBold: true, color: AppColors.greyTextColor), + "• Apple Watch 11".needTranslation.toText12(isBold: true, color: AppColors.greyTextColor), + "• Apple Watch 10".needTranslation.toText12(isBold: true, color: AppColors.greyTextColor), + "• Apple Watch 9".needTranslation.toText12(isBold: true, color: AppColors.greyTextColor), + "• Apple Watch 8".needTranslation.toText12(isBold: true, color: AppColors.greyTextColor), SizedBox(height: 16.h), Row( children: [ @@ -37,12 +37,12 @@ class SupportedWatchesList extends StatelessWidget { ], ), 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), + "• Samsung Watch Ultra".needTranslation.toText12(isBold: true, color: AppColors.greyTextColor), + "• Galaxy Watch 8".needTranslation.toText12(isBold: true, color: AppColors.greyTextColor), + "• Galaxy Watch 7".needTranslation.toText12(isBold: true, color: AppColors.greyTextColor), + "• Galaxy Watch 6".needTranslation.toText12(isBold: true, color: AppColors.greyTextColor), + "• Galaxy Watch FE".needTranslation.toText12(isBold: true, color: AppColors.greyTextColor), + "• Galaxy Watch Fit".needTranslation.toText12(isBold: true, color: AppColors.greyTextColor), SizedBox(height: 16.h), Row( children: [ @@ -50,10 +50,10 @@ class SupportedWatchesList extends StatelessWidget { ], ), 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), + "• Huawei Watch Ultimate Series".needTranslation.toText12(isBold: true, color: AppColors.greyTextColor), + "• Huawei Watch Series".needTranslation.toText12(isBold: true, color: AppColors.greyTextColor), + "• Huawei Watch Fit Series".needTranslation.toText12(isBold: true, color: AppColors.greyTextColor), + "• Huawei Watch Bands".needTranslation.toText12(isBold: true, color: AppColors.greyTextColor), SizedBox(height: 16.h), Row( children: [ @@ -61,8 +61,8 @@ class SupportedWatchesList extends StatelessWidget { ], ), SizedBox(height: 4.h), - "• Whoop 1".needTranslation.toText12(fontWeight: FontWeight.w500, color: AppColors.greyTextColor), - "• Whoop 2".needTranslation.toText12(fontWeight: FontWeight.w500, color: AppColors.greyTextColor), + "• Whoop 1".needTranslation.toText12(isBold: true, color: AppColors.greyTextColor), + "• Whoop 2".needTranslation.toText12(isBold: true, color: AppColors.greyTextColor), ], ), ), diff --git a/lib/presentation/symptoms_checker/possible_conditions_screen.dart b/lib/presentation/symptoms_checker/possible_conditions_screen.dart index 79e28a05..fa5d77d0 100644 --- a/lib/presentation/symptoms_checker/possible_conditions_screen.dart +++ b/lib/presentation/symptoms_checker/possible_conditions_screen.dart @@ -172,9 +172,9 @@ class PossibleConditionsPage extends StatelessWidget { child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ - title.toText14(weight: FontWeight.w600, color: AppColors.textColor), + title.toText14(isBold: true, color: AppColors.textColor), SizedBox(height: 4.h), - description.toText12(fontWeight: FontWeight.w500, color: AppColors.greyTextColor), + description.toText12(isBold: true, color: AppColors.greyTextColor), ], ), ), @@ -277,7 +277,7 @@ class PossibleConditionsPage extends StatelessWidget { getTitle(AppointmentViaRegionViewmodel data, BuildContext context) { if (data.selectedRegionId == null) { - return LocaleKeys.selectRegion.tr(context: context).toText20(weight: FontWeight.w600); + return LocaleKeys.selectRegion.tr(context: context).toText20(isBold: true); } else { return Transform.flip( flipX: data.isArabic ? true : false, diff --git a/lib/presentation/symptoms_checker/risk_factors_screen.dart b/lib/presentation/symptoms_checker/risk_factors_screen.dart index 462cffbd..969a884d 100644 --- a/lib/presentation/symptoms_checker/risk_factors_screen.dart +++ b/lib/presentation/symptoms_checker/risk_factors_screen.dart @@ -84,7 +84,7 @@ class _RiskFactorsScreenState extends State { Expanded( child: optionText.toText14( color: riskFactorId == "not_applicable" ? AppColors.errorColor : AppColors.textColor, - weight: FontWeight.w500, + weight: FontWeight.w600, )), ], ), @@ -123,7 +123,7 @@ class _RiskFactorsScreenState extends State { style: TextStyle( height: 1.3, fontSize: 13.f, - fontWeight: FontWeight.w500, + fontWeight: FontWeight.w600, color: AppColors.greyInfoTextColor, fontFamily: isArabic ? 'GESSTwo' : 'Poppins' ), @@ -135,7 +135,7 @@ class _RiskFactorsScreenState extends State { text: LocaleKeys.readMore.tr(context: context), style: TextStyle( color: AppColors.primaryRedColor, - fontWeight: FontWeight.w500, + fontWeight: FontWeight.w600, ), recognizer: TapGestureRecognizer() ..onTap = () { diff --git a/lib/presentation/symptoms_checker/suggestions_screen.dart b/lib/presentation/symptoms_checker/suggestions_screen.dart index cbfc15a1..8fc58fd7 100644 --- a/lib/presentation/symptoms_checker/suggestions_screen.dart +++ b/lib/presentation/symptoms_checker/suggestions_screen.dart @@ -86,7 +86,7 @@ class _SuggestionsScreenState extends State { Expanded( child: optionText.toText14( color: suggestionId == "not_applicable" ? AppColors.errorColor : AppColors.textColor, - weight: FontWeight.w500, + weight: FontWeight.w600, )), ], ), diff --git a/lib/presentation/symptoms_checker/triage_screen.dart b/lib/presentation/symptoms_checker/triage_screen.dart index b4ceff4c..da35a805 100644 --- a/lib/presentation/symptoms_checker/triage_screen.dart +++ b/lib/presentation/symptoms_checker/triage_screen.dart @@ -123,7 +123,7 @@ class _TriagePageState extends State { SizedBox(height: 8.h), LocaleKeys.emergencyTriage.tr(context: context).toText28(color: AppColors.whiteColor, isBold: true), SizedBox(height: 8.h), - LocaleKeys.emergencyEvidenceDetected.tr(context: context).toText14(color: AppColors.whiteColor, weight: FontWeight.w500), + LocaleKeys.emergencyEvidenceDetected.tr(context: context).toText14(color: AppColors.whiteColor, isBold: true), SizedBox(height: 24.h), CustomButton( text: LocaleKeys.confirm.tr(context: context), @@ -333,7 +333,7 @@ class _TriagePageState extends State { if (viewModel.currentTriageQuestion == null) { return Center( - child: LocaleKeys.noQuestionAvailable.tr(context: context).toText16(weight: FontWeight.w500), + child: LocaleKeys.noQuestionAvailable.tr(context: context).toText16(isBold: true), ); } @@ -373,7 +373,7 @@ class _TriagePageState extends State { crossAxisAlignment: CrossAxisAlignment.start, children: [ // Main question text - (question.text ?? "").toText16(weight: FontWeight.w600, color: AppColors.textColor), + (question.text ?? "").toText16(isBold: true, color: AppColors.textColor), SizedBox(height: 24.h), // Show all items with dividers @@ -386,7 +386,7 @@ class _TriagePageState extends State { crossAxisAlignment: CrossAxisAlignment.start, children: [ // Item name (sub-question) - (item.name ?? "").toText14(weight: FontWeight.w600, color: AppColors.textColor), + (item.name ?? "").toText14(isBold: true, color: AppColors.textColor), SizedBox(height: 8.h), // Choices for this item ...List.generate(choices.length, (choiceIndex) { @@ -430,7 +430,7 @@ class _TriagePageState extends State { child: selected ? Icon(Icons.check, size: 16.f, color: AppColors.whiteColor) : null, ), SizedBox(width: 12.w), - Expanded(child: optionText.toText13(weight: FontWeight.w500)), + Expanded(child: optionText.toText13(isBold: true)), ], ), ), @@ -474,7 +474,7 @@ class _TriagePageState extends State { text: suggestedCondition, style: TextStyle( color: AppColors.textColor, - fontWeight: FontWeight.w600, + fontWeight: FontWeight.w600, fontSize: 14.f, fontFamily: isArabic ? 'GESSTwo' : 'Poppins'), ), ], @@ -491,7 +491,7 @@ class _TriagePageState extends State { text: TextSpan( text: "${probability.toStringAsFixed(1)}% ", style: TextStyle( - fontFamily: isArabic ? 'GESSTwo' : 'Poppins', + fontFamily: 'Poppins', color: AppColors.primaryRedColor, fontWeight: FontWeight.w600, fontSize: 14.f, @@ -501,7 +501,7 @@ class _TriagePageState extends State { text: LocaleKeys.symptomsCheckerFindingScore.tr(context: context), style: TextStyle( color: AppColors.textColor, - fontWeight: FontWeight.w500, + fontWeight: FontWeight.w600, fontSize: 13.f, fontFamily: isArabic ? 'GESSTwo' : 'Poppins', ), diff --git a/lib/presentation/symptoms_checker/user_info_selection.dart b/lib/presentation/symptoms_checker/user_info_selection.dart index 8d58e049..bd19f31b 100644 --- a/lib/presentation/symptoms_checker/user_info_selection.dart +++ b/lib/presentation/symptoms_checker/user_info_selection.dart @@ -98,9 +98,9 @@ class _UserInfoSelectionPageState extends State { Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ - title.toText14(weight: FontWeight.w500), + title.toText14(isBold: true), subTitle - .toText12(color: AppColors.primaryRedColor, fontWeight: FontWeight.w500, isEnglishOnly: true) + .toText12(color: AppColors.primaryRedColor, isBold: true, isEnglishOnly: true) .toShimmer2(isShow: (leadingIcon == AppAssets.rulerIcon || leadingIcon == AppAssets.weightScale) && hmgServicesVM.isVitalSignLoading), ], ), 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 37802a27..63cff2e4 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 @@ -27,7 +27,7 @@ class AgeSelectionPage extends StatelessWidget { builder: (BuildContext context, symptomsViewModel, Widget? child) { return Column( children: [ - LocaleKeys.dateOfBirthSymptoms.tr(context: context).toText18(weight: FontWeight.w600, color: AppColors.textColor).paddingAll(24.w), + LocaleKeys.dateOfBirthSymptoms.tr(context: context).toText18(isBold: true, 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 07f44f16..401f8529 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 @@ -37,7 +37,7 @@ class GenderSelectionPage extends StatelessWidget { Utils.buildImgWithAssets(icon: iconPng, height: 80.h, width: 80.h, fit: BoxFit.contain), SizedBox(height: 8.h), label.toText16( - weight: FontWeight.w500, + weight: FontWeight.w600, ) ], ), @@ -74,7 +74,7 @@ class GenderSelectionPage extends StatelessWidget { builder: (BuildContext context, symptomsViewModel, Widget? child) { return Column( children: [ - LocaleKeys.genderSymptoms.tr(context: context).toText18(weight: FontWeight.w600, color: AppColors.textColor), + LocaleKeys.genderSymptoms.tr(context: context).toText18(isBold: true, color: AppColors.textColor), SizedBox(height: 70.h), Row( children: [ 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 ce8087d4..28de7894 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 @@ -49,14 +49,13 @@ class HeightSelectionPage extends StatelessWidget { }, child: Container( alignment: Alignment.center, - color: Colors.transparent, + color: Colors.white, child: Text( 'CM', style: TextStyle( fontWeight: FontWeight.w700, fontSize: 14.f, - color: viewModel.isHeightCm ? AppColors.textColor : AppColors.textColor.withValues(alpha: 0.6), - ), + color: viewModel.isHeightCm ? AppColors.primaryRedColor : AppColors.textColor.withValues(alpha: 0.6), fontFamily: "Poppins"), ), ), ), @@ -71,14 +70,13 @@ class HeightSelectionPage extends StatelessWidget { }, child: Container( alignment: Alignment.center, - color: Colors.transparent, + color: Colors.white, child: Text( 'FT', style: TextStyle( fontWeight: FontWeight.w700, fontSize: 14.f, - color: !viewModel.isHeightCm ? AppColors.textColor : AppColors.textColor.withValues(alpha: 0.6), - ), + color: !viewModel.isHeightCm ? AppColors.primaryRedColor : AppColors.textColor.withValues(alpha: 0.6), fontFamily: "Poppins"), ), ), ), @@ -133,8 +131,7 @@ class HeightSelectionPage extends StatelessWidget { style: TextStyle( fontSize: 90.f, color: AppColors.textColor, - height: 1, - ), + height: 1, fontFamily: "Poppins"), ), TextSpan( text: viewModel.isHeightCm ? 'cm' : 'ft', 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 6e68eef9..fefc9abe 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 @@ -57,13 +57,14 @@ class WeightSelectionPage extends StatelessWidget { }, child: Container( alignment: Alignment.center, - color: Colors.transparent, + color: Colors.white, child: Text( 'KG', style: TextStyle( fontWeight: FontWeight.w700, fontSize: 14.f, - color: isKg ? AppColors.textColor : AppColors.textColor.withValues(alpha: 0.6), + color: isKg ? AppColors.primaryRedColor : AppColors.textColor.withValues(alpha: 0.6), + fontFamily: "Poppins" ), ), ), @@ -86,7 +87,8 @@ class WeightSelectionPage extends StatelessWidget { style: TextStyle( fontWeight: FontWeight.w700, fontSize: 14.f, - color: !isKg ? AppColors.textColor : AppColors.textColor.withValues(alpha: 0.6), + color: !isKg ? AppColors.primaryRedColor : AppColors.textColor.withValues(alpha: 0.6), + fontFamily: "Poppins" ), ), ), @@ -133,12 +135,12 @@ class WeightSelectionPage extends StatelessWidget { children: [ Text( viewModel.selectedWeight!.round().toString(), - style: TextStyle(fontSize: 100.f, color: AppColors.textColor, height: 1), + style: TextStyle(fontSize: 100.f, color: AppColors.textColor, height: 1, fontFamily: "Poppins"), ), SizedBox(width: 8.w), Text( isKg ? 'kg' : 'lbs', - style: TextStyle(fontWeight: FontWeight.w700, fontSize: 24.f), + style: TextStyle(fontWeight: FontWeight.w700, fontSize: 24.f, fontFamily: "Poppins"), ).paddingOnly(bottom: 10.h), ], ), diff --git a/lib/presentation/symptoms_checker/user_info_selection/widgets/custom_date_picker.dart b/lib/presentation/symptoms_checker/user_info_selection/widgets/custom_date_picker.dart index d2b82ef5..c0b76be7 100644 --- a/lib/presentation/symptoms_checker/user_info_selection/widgets/custom_date_picker.dart +++ b/lib/presentation/symptoms_checker/user_info_selection/widgets/custom_date_picker.dart @@ -146,7 +146,7 @@ class _ThreeColumnDatePickerState extends State { textAlign: TextAlign.center, style: TextStyle( fontSize: selected ? 22.f : 20.f, - fontWeight: selected ? FontWeight.w600 : FontWeight.w500, + fontWeight: selected ? FontWeight.w600 : FontWeight.w600, color: selected ? AppColors.textColor : AppColors.greyTextColor.withValues(alpha: 0.9), height: 1.0, fontFamily: "Poppins", 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 e09380eb..6695bbd4 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 @@ -133,8 +133,9 @@ class _HeightScaleState extends State { style: TextStyle( fontSize: 11.f, color: AppColors.greyTextColor, - fontWeight: FontWeight.w500, + fontWeight: FontWeight.w600, height: 1, + fontFamily: "Poppins" ), textAlign: TextAlign.right, ), diff --git a/lib/presentation/symptoms_checker/user_info_selection/widgets/weight_scale.dart b/lib/presentation/symptoms_checker/user_info_selection/widgets/weight_scale.dart index 8526a526..9ab2d73c 100644 --- a/lib/presentation/symptoms_checker/user_info_selection/widgets/weight_scale.dart +++ b/lib/presentation/symptoms_checker/user_info_selection/widgets/weight_scale.dart @@ -152,8 +152,9 @@ class _WeightScaleState extends State { style: TextStyle( fontSize: 11.f, color: AppColors.greyTextColor, - fontWeight: FontWeight.w500, + fontWeight: FontWeight.w600, height: 1, + fontFamily: "Poppins" ), textAlign: TextAlign.center, maxLines: 1, diff --git a/lib/presentation/symptoms_checker/widgets/condition_card.dart b/lib/presentation/symptoms_checker/widgets/condition_card.dart index d753b780..d231f52f 100644 --- a/lib/presentation/symptoms_checker/widgets/condition_card.dart +++ b/lib/presentation/symptoms_checker/widgets/condition_card.dart @@ -168,7 +168,7 @@ class ConditionCard extends StatelessWidget { crossAxisAlignment: WrapCrossAlignment.center, children: [ for (int i = 0; i < symptoms.length; i++) ...[ - "● ${symptoms[i]}".toText12(fontWeight: FontWeight.w500, color: AppColors.greyTextColor, isEnglishOnly: true), + "● ${symptoms[i]}".toText12(isBold: true, color: AppColors.greyTextColor, isEnglishOnly: true), if (i != symptoms.length - 1) Padding( padding: EdgeInsets.symmetric(horizontal: 2.w), @@ -212,7 +212,7 @@ class ConditionCard extends StatelessWidget { mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ Expanded( - child: title.toText18(weight: FontWeight.w600, color: AppColors.textColor), + child: title.toText18(isBold: true, color: AppColors.textColor), ), AppCustomChipWidget( labelText: tagText, @@ -241,12 +241,12 @@ class ConditionCard extends StatelessWidget { Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ - LocaleKeys.commonSymptom.tr(context: context).toText14(weight: FontWeight.w600, color: AppColors.textColor), + LocaleKeys.commonSymptom.tr(context: context).toText14(isBold: true, color: AppColors.textColor), _buildSymptomsRow(), SizedBox(height: 16.h), LocaleKeys.description.tr(context: context).toText14(weight: FontWeight.bold, color: AppColors.textColor), SizedBox(height: 2.h), - description.toText12(fontWeight: FontWeight.w500, color: AppColors.greyTextColor), + description.toText12(isBold: true, color: AppColors.greyTextColor), if (severityEnum != PossibleConditionsSeverityEnum.monitorOnly) CustomButton( text: LocaleKeys.bookAppointment.tr(context: context), @@ -259,7 +259,7 @@ class ConditionCard extends StatelessWidget { borderColor: Colors.transparent, textColor: AppColors.whiteColor, fontSize: 14.f, - fontWeight: FontWeight.w500, + fontWeight: FontWeight.w600, padding: EdgeInsets.symmetric(horizontal: 10.w), icon: AppAssets.add_icon, height: 48.h, diff --git a/lib/presentation/symptoms_checker/widgets/organ_tooltip_widget.dart b/lib/presentation/symptoms_checker/widgets/organ_tooltip_widget.dart index 6c2625b3..2852d930 100644 --- a/lib/presentation/symptoms_checker/widgets/organ_tooltip_widget.dart +++ b/lib/presentation/symptoms_checker/widgets/organ_tooltip_widget.dart @@ -28,7 +28,7 @@ class OrganTooltipWidget extends StatelessWidget { style: TextStyle( color: AppColors.textColor, fontSize: 12.f, - fontWeight: FontWeight.w500, + fontWeight: FontWeight.w600, ), ), ), diff --git a/lib/presentation/symptoms_checker/widgets/selected_organs_section.dart b/lib/presentation/symptoms_checker/widgets/selected_organs_section.dart index b66ef037..e2b342b6 100644 --- a/lib/presentation/symptoms_checker/widgets/selected_organs_section.dart +++ b/lib/presentation/symptoms_checker/widgets/selected_organs_section.dart @@ -57,6 +57,7 @@ class _SelectedOrgansSectionState extends State { LocaleKeys.selectedOrgans.tr(context: context), style: TextStyle( fontSize: 16.f, + // isBold: true, fontWeight: FontWeight.w600, color: AppColors.textColor, ), diff --git a/lib/presentation/tele_consultation/zoom/call_screen.dart b/lib/presentation/tele_consultation/zoom/call_screen.dart index 59be70e0..21836a8a 100644 --- a/lib/presentation/tele_consultation/zoom/call_screen.dart +++ b/lib/presentation/tele_consultation/zoom/call_screen.dart @@ -897,7 +897,7 @@ class _CallScreenState extends State { // style: GoogleFonts.lato( // textStyle: const TextStyle( // fontSize: 18, - // fontWeight: FontWeight.w600, + // isBold: true, // color: Colors.black, // ), // ), @@ -1388,7 +1388,7 @@ class _CallScreenState extends State { 'Change Name', style: const TextStyle( fontSize: 18, - fontWeight: FontWeight.w500, + fontWeight: FontWeight.w600, ), ), ), @@ -1598,7 +1598,7 @@ class _CallScreenState extends State { // style: GoogleFonts.lato( // textStyle: const TextStyle( // fontSize: 18, - // fontWeight: FontWeight.w600, + // isBold: true, // ), // ), // ), @@ -1678,7 +1678,7 @@ class _CallScreenState extends State { // style: GoogleFonts.lato( // textStyle: const TextStyle( // fontSize: 14, - // fontWeight: FontWeight.w600, + // isBold: true, // color: Colors.white, // ), // ), @@ -1692,7 +1692,7 @@ class _CallScreenState extends State { // style: GoogleFonts.lato( // textStyle: const TextStyle( // fontSize: 14, - // fontWeight: FontWeight.w600, + // isBold: true, // color: Colors.white, // ), // ), diff --git a/lib/presentation/todo_section/ancillary_procedures_details_page.dart b/lib/presentation/todo_section/ancillary_procedures_details_page.dart index b65d26df..4d2034ba 100644 --- a/lib/presentation/todo_section/ancillary_procedures_details_page.dart +++ b/lib/presentation/todo_section/ancillary_procedures_details_page.dart @@ -388,7 +388,7 @@ class _AncillaryOrderDetailsListState extends State { children: [ LocaleKeys.procedures.tr(context: context).toText12( color: AppColors.textColorLight, - fontWeight: FontWeight.w600, + isBold: true, ), "$selectedCount of $totalProcedures selected".toText14( isBold: true, @@ -401,7 +401,7 @@ class _AncillaryOrderDetailsListState extends State { children: [ LocaleKeys.totalAmount.tr(context: context).toText12( color: AppColors.textColorLight, - fontWeight: FontWeight.w600, + isBold: true, ), Row( children: [ @@ -439,12 +439,12 @@ class _AncillaryOrderDetailsListState extends State { children: [ Expanded( flex: 2, - child: "$label:".toText12(color: AppColors.textColorLight, fontWeight: FontWeight.w600), + child: "$label:".toText12(color: AppColors.textColorLight, isBold: true), ), SizedBox(width: 8.w), Expanded( flex: 3, - child: value.toText12(color: AppColors.textColor, fontWeight: FontWeight.w600), + child: value.toText12(color: AppColors.textColor, isBold: true), ), ], ), @@ -570,7 +570,7 @@ class _AncillaryOrderDetailsListState extends State { Row( children: [ Utils.getPaymentAmountWithSymbol( - (procedure.patientShare ?? 0).toStringAsFixed(2).toText14(weight: FontWeight.w600, isEnglishOnly: true), + (procedure.patientShare ?? 0).toStringAsFixed(2).toText14(isBold: true, isEnglishOnly: true), AppColors.textColorLight, 13, isSaudiCurrency: true, @@ -589,7 +589,7 @@ class _AncillaryOrderDetailsListState extends State { Row( children: [ Utils.getPaymentAmountWithSymbol( - (procedure.patientTaxAmount ?? 0).toStringAsFixed(2).toText12(fontWeight: FontWeight.w600, isEnglishOnly: true), + (procedure.patientTaxAmount ?? 0).toStringAsFixed(2).toText12(isBold: true, isEnglishOnly: true), AppColors.textColorLight, 13, isSaudiCurrency: true, @@ -608,7 +608,7 @@ class _AncillaryOrderDetailsListState extends State { Row( children: [ Utils.getPaymentAmountWithSymbol( - (procedure.patientShareWithTax ?? 0).toStringAsFixed(2).toText12(fontWeight: FontWeight.w600, isEnglishOnly: true), + (procedure.patientShareWithTax ?? 0).toStringAsFixed(2).toText12(isBold: true, isEnglishOnly: true), AppColors.textColorLight, 13, isSaudiCurrency: true, @@ -645,7 +645,7 @@ class _AncillaryOrderDetailsListState extends State { Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ - Expanded(child: LocaleKeys.upcomingPaymentNow.tr(context: context).toText12(fontWeight: FontWeight.w500, color: AppColors.greyTextColor)), + Expanded(child: LocaleKeys.upcomingPaymentNow.tr(context: context).toText12(isBold: true, color: AppColors.greyTextColor)), ], ), SizedBox(height: 18.h), diff --git a/lib/presentation/todo_section/widgets/ancillary_orders_list.dart b/lib/presentation/todo_section/widgets/ancillary_orders_list.dart index c05d7fc9..627642ef 100644 --- a/lib/presentation/todo_section/widgets/ancillary_orders_list.dart +++ b/lib/presentation/todo_section/widgets/ancillary_orders_list.dart @@ -236,7 +236,7 @@ class AncillaryOrderCard extends StatelessWidget { borderColor: AppColors.primaryRedColor, textColor: AppColors.whiteColor, fontSize: (isFoldable || isTablet) ? 12.f : 14.f, - fontWeight: FontWeight.w500, + fontWeight: FontWeight.w600, borderRadius: 10.r, padding: EdgeInsets.fromLTRB(10.h, 0, 10.h, 0), height: 40.h, @@ -260,7 +260,7 @@ class AncillaryOrderCard extends StatelessWidget { borderColor: Color(0xffFEE9EA), textColor: Color(0xffED1C2B), fontSize: (isFoldable || isTablet) ? 12.f : 14.f, - fontWeight: FontWeight.w500, + fontWeight: FontWeight.w600, borderRadius: 10.r, padding: EdgeInsets.fromLTRB(10.h, 0, 10.h, 0), height: 40.h, diff --git a/lib/presentation/todo_section/widgets/ancillary_procedures_list.dart b/lib/presentation/todo_section/widgets/ancillary_procedures_list.dart index 2d99aa0f..54c92789 100644 --- a/lib/presentation/todo_section/widgets/ancillary_procedures_list.dart +++ b/lib/presentation/todo_section/widgets/ancillary_procedures_list.dart @@ -121,7 +121,7 @@ class AncillaryOrderCard extends StatelessWidget { children: [ LocaleKeys.orderNumber.tr(context: context).toText14( color: AppColors.textColorLight, - weight: FontWeight.w500, + weight: FontWeight.w600, ), SizedBox(width: 4.w), (isLoading ? "12345" : "${order.orderNo ?? '-'}").toText16(isBold: true).toShimmer2(isShow: isLoading), @@ -158,7 +158,7 @@ class AncillaryOrderCard extends StatelessWidget { (isLoading ? "Cardiology Clinic" : order.clinicName!) .toString() .toText12( - fontWeight: FontWeight.w500, + isBold: true, color: AppColors.greyTextColor, maxLine: 2, ) @@ -232,7 +232,7 @@ class AncillaryOrderCard extends StatelessWidget { borderColor: AppColors.primaryRedColor, textColor: AppColors.whiteColor, fontSize: 14.f, - fontWeight: FontWeight.w500, + isBold: true, borderRadius: 10.r, padding: EdgeInsets.fromLTRB(10.h, 0, 10.h, 0), height: 40.h, @@ -256,7 +256,7 @@ class AncillaryOrderCard extends StatelessWidget { borderColor: Color(0xffFEE9EA), textColor: Color(0xffED1C2B), fontSize: 14.f, - fontWeight: FontWeight.w500, + isBold: true, borderRadius: 10.r, padding: EdgeInsets.fromLTRB(10.h, 0, 10.h, 0), height: 40.h, diff --git a/lib/presentation/vital_sign/vital_sign_details_page.dart b/lib/presentation/vital_sign/vital_sign_details_page.dart index 3d6f97be..eb137e47 100644 --- a/lib/presentation/vital_sign/vital_sign_details_page.dart +++ b/lib/presentation/vital_sign/vital_sign_details_page.dart @@ -2,7 +2,9 @@ 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/common_models/data_points.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'; @@ -119,7 +121,7 @@ class _VitalSignDetailsPageState extends State { required String title, required String icon, required String valueText, - required String? status, + required VitalSignStatus? status, required VitalSignUiModel scheme, required DateTime? latestDate, }) { @@ -147,7 +149,7 @@ class _VitalSignDetailsPageState extends State { (latestDate != null ? LocaleKeys.resultOf.tr(namedArgs: {'date': latestDate.toString().split(' ').first}) : LocaleKeys.resultOfNoDate.tr(context: context)) - .toText11(weight: FontWeight.w500, color: AppColors.greyTextColor, isEnglishOnly: true), + .toText11(isBold: true, color: AppColors.greyTextColor, isEnglishOnly: true), ], ), Row( @@ -168,17 +170,20 @@ class _VitalSignDetailsPageState extends State { ), SizedBox(width: 4.h), if (status != null) - Column( - spacing: 6.h, - children: [ - status.toText10(weight: FontWeight.w500, color: AppColors.greyTextColor), - Utils.buildSvgWithAssets( - icon: AppAssets.lab_result_indicator, - width: 21, - height: 23, - iconColor: scheme.iconFg, - ), - ], + Directionality( + textDirection: ui.TextDirection.ltr, + child: Column( + spacing: 6.h, + children: [ + VitalSignUiModel.statusToString(status).toText10(isBold: true, color: AppColors.greyTextColor), + Utils.buildSvgWithAssets( + icon: AppAssets.lab_result_indicator, + width: 21, + height: 23, + iconColor: scheme.iconFg, + ), + ], + ), ), ], ), @@ -190,7 +195,7 @@ class _VitalSignDetailsPageState extends State { _referenceText(context), style: TextStyle( fontSize: 12.f, - fontWeight: FontWeight.w500, + fontWeight: FontWeight.w600, fontFamily: 'Poppins', color: AppColors.greyTextColor, ), @@ -236,8 +241,8 @@ class _VitalSignDetailsPageState extends State { crossAxisAlignment: CrossAxisAlignment.start, spacing: 8.h, children: [ - LocaleKeys.whatIsThisResult.tr(context: context).toText16(weight: FontWeight.w600, color: AppColors.textColor), - _descriptionText(context).toText12(fontWeight: FontWeight.w500, color: AppColors.textColorLight), + LocaleKeys.whatIsThisResult.tr(context: context).toText16(isBold: true, color: AppColors.textColor), + _descriptionText(context).toText12(isBold: true, color: AppColors.textColorLight), ], ), ); @@ -331,6 +336,7 @@ class _VitalSignDetailsPageState extends State { final minY = _minY(history, secondaryHistory: secondaryHistory); final maxY = _maxY(history, secondaryHistory: secondaryHistory); final scheme = VitalSignUiModel.scheme(status: _statusForLatest(null), label: args.title); + final isRTL = getIt.get().isArabic(); return CustomGraph( dataPoints: history, @@ -339,6 +345,7 @@ class _VitalSignDetailsPageState extends State { leftLabelReservedSize: 40, showGridLines: true, showShadow: true, + isRTL: isRTL, leftLabelInterval: _leftInterval(history, secondaryHistory: secondaryHistory), maxY: maxY, minY: minY, @@ -430,12 +437,12 @@ class _VitalSignDetailsPageState extends State { child: Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ - dp.displayTime.toText12(color: AppColors.greyTextColor, fontWeight: FontWeight.w500, isEnglishOnly: true), + dp.displayTime.toText12(color: AppColors.greyTextColor, isBold: true, isEnglishOnly: true), Directionality( textDirection: ui.TextDirection.ltr, child: valueText.toText12( color: AppColors.textColor, - fontWeight: FontWeight.w600, + isBold: true, isEnglishOnly: true ), ), @@ -663,7 +670,7 @@ class _VitalSignDetailsPageState extends State { } } - String? _statusForLatest(VitalSignResModel? latest) { + VitalSignStatus? _statusForLatest(VitalSignResModel? latest) { if (latest == null) return null; switch (args.metric) { @@ -674,14 +681,15 @@ class _VitalSignDetailsPageState extends State { case VitalSignMetric.height: return null; case VitalSignMetric.weight: - // return latest.weightKg != null ? 'Normal' : null; - return VitalSignUiModel.bmiStatus(latest.bodyMassIndex); + return VitalSignUiModel.weightStatus(latest.bodyMassIndex); case VitalSignMetric.temperature: return null; case VitalSignMetric.heartRate: - return (latest.heartRate ?? latest.pulseBeatPerMinute) != null ? 'Normal' : null; + final heartRateValue = _toDouble(latest.heartRate ?? latest.pulseBeatPerMinute); + return (heartRateValue != null && heartRateValue > 0) ? VitalSignStatus.normal : null; case VitalSignMetric.respiratoryRate: - return latest.respirationBeatPerMinute != null ? 'Normal' : null; + final respiratoryValue = _toDouble(latest.respirationBeatPerMinute); + return (respiratoryValue != null && respiratoryValue > 0) ? VitalSignStatus.normal : null; } } @@ -734,9 +742,9 @@ class _VitalSignDetailsPageState extends State { child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ - LocaleKeys.whatShouldIDoNext.tr(context: context).toText16(weight: FontWeight.w600), + LocaleKeys.whatShouldIDoNext.tr(context: context).toText16(isBold: true), SizedBox(height: 8.h), - _nextStepsText(context).toText12(color: AppColors.greyTextColor, fontWeight: FontWeight.w500, maxLine: 10), + _nextStepsText(context).toText12(color: AppColors.greyTextColor, isBold: true, maxLine: 10), ], ), ); @@ -756,11 +764,11 @@ class _VitalSignDetailsPageState extends State { Widget _bottomLabel(String label, {bool isLast = false}) { return Padding( - padding: EdgeInsets.only( + padding: EdgeInsetsDirectional.only( top: 8.0, - right: isLast ? 16.h : 0, + end: isLast ? 16.h : 0, ), - child: label.toText10(weight: FontWeight.w500, isEnglishOnly: true), + child: label.toText10(isBold: true, isEnglishOnly: true), ); } diff --git a/lib/presentation/vital_sign/vital_sign_page.dart b/lib/presentation/vital_sign/vital_sign_page.dart index 4fa90233..f712aa77 100644 --- a/lib/presentation/vital_sign/vital_sign_page.dart +++ b/lib/presentation/vital_sign/vital_sign_page.dart @@ -41,6 +41,18 @@ class _VitalSignPageState extends State { return false; } + // Helper function to format vital sign values, showing '--' instead of 0 or null + String _formatValue(dynamic value) { + if (value == null) return '--'; + if (value is num && value == 0) return '--'; + if (value is String) { + if (value.trim().isEmpty) return '--'; + final parsed = double.tryParse(value); + if (parsed == null || parsed == 0) return '--'; + } + return value.toString(); + } + void _openDetails(VitalSignDetailsArgs args) { Navigator.of(context).push( CustomPageRoute( @@ -100,7 +112,7 @@ class _VitalSignPageState extends State { _buildVitalSignCard( icon: AppAssets.bmiVital, label: LocaleKeys.bmi.tr(context: context), - value: latestVitalSign?.bodyMassIndex?.toString() ?? '--', + value: _formatValue(latestVitalSign?.bodyMassIndex), unit: '', status: VitalSignUiModel.bmiStatus(latestVitalSign?.bodyMassIndex), onTap: () => _openDetails( @@ -118,7 +130,7 @@ class _VitalSignPageState extends State { _buildVitalSignCard( icon: AppAssets.heightVital, label: LocaleKeys.height.tr(context: context), - value: latestVitalSign?.heightCm?.toString() ?? '--', + value: _formatValue(latestVitalSign?.heightCm), unit: 'cm', status: null, onTap: () => _openDetails( @@ -136,10 +148,10 @@ class _VitalSignPageState extends State { _buildVitalSignCard( icon: AppAssets.weightVital, label: LocaleKeys.weight.tr(context: context), - value: latestVitalSign?.weightKg?.toString() ?? '--', + value: _formatValue(latestVitalSign?.weightKg), unit: 'kg', status: (latestVitalSign?.weightKg != null) - ? VitalSignUiModel.bmiStatus(latestVitalSign?.bodyMassIndex) + ? VitalSignUiModel.weightStatus(latestVitalSign?.bodyMassIndex) : null, onTap: () => _openDetails( VitalSignDetailsArgs( @@ -162,10 +174,14 @@ class _VitalSignPageState extends State { ? '${latestVitalSign.bloodPressureHigher}/${latestVitalSign.bloodPressureLower}' : '--', unit: '', - status: VitalSignUiModel.bloodPressureStatus( - systolic: latestVitalSign?.bloodPressureHigher, - diastolic: latestVitalSign?.bloodPressureLower, - ), + status: (latestVitalSign != null && + _isValidValue(latestVitalSign.bloodPressureHigher) && + _isValidValue(latestVitalSign.bloodPressureLower)) + ? VitalSignUiModel.bloodPressureStatus( + systolic: latestVitalSign.bloodPressureHigher, + diastolic: latestVitalSign.bloodPressureLower, + ) + : null, onTap: () => _openDetails( VitalSignDetailsArgs( metric: VitalSignMetric.bloodPressure, @@ -183,7 +199,7 @@ class _VitalSignPageState extends State { _buildVitalSignCard( icon: AppAssets.temperature, label: LocaleKeys.temperature.tr(context: context), - value: latestVitalSign?.temperatureCelcius?.toString() ?? '--', + value: _formatValue(latestVitalSign?.temperatureCelcius), unit: '°C', status: null, onTap: () => _openDetails( @@ -258,9 +274,9 @@ class _VitalSignPageState extends State { child: _buildVitalSignCard( icon: AppAssets.heart, label: LocaleKeys.heart.tr(context: context), - value: latestVitalSign?.heartRate?.toString() ?? latestVitalSign?.pulseBeatPerMinute?.toString() ?? '--', + value: _formatValue(latestVitalSign?.heartRate ?? latestVitalSign?.pulseBeatPerMinute), unit: 'bpm', - status: 'Normal', + status: _isValidValue(latestVitalSign?.heartRate ?? latestVitalSign?.pulseBeatPerMinute) ? VitalSignStatus.normal : null, onTap: () => _openDetails( VitalSignDetailsArgs( metric: VitalSignMetric.heartRate, @@ -282,9 +298,9 @@ class _VitalSignPageState extends State { _buildVitalSignCard( icon: AppAssets.respRate, label: LocaleKeys.respirationRate.tr(context: context), - value: latestVitalSign?.respirationBeatPerMinute?.toString() ?? '--', + value: _formatValue(latestVitalSign?.respirationBeatPerMinute), unit: 'bpm', - status: 'Normal', + status: _isValidValue(latestVitalSign?.respirationBeatPerMinute) ? VitalSignStatus.normal : null, onTap: () => _openDetails( VitalSignDetailsArgs( metric: VitalSignMetric.respiratoryRate, @@ -318,7 +334,7 @@ class _VitalSignPageState extends State { required String label, required String value, required String unit, - required String? status, + required VitalSignStatus? status, required VoidCallback onTap, }) { final VitalSignUiModel scheme = VitalSignUiModel.scheme(status: status, label: label); @@ -387,18 +403,18 @@ class _VitalSignPageState extends State { color: AppColors.textColor, isEnglishOnly: true ), - if (unit.isNotEmpty) ...[ + if (unit.isNotEmpty && value != '--' && value != '0') ...[ SizedBox(width: 3.w), unit.toText12( color: AppColors.textColor, - fontWeight: FontWeight.w500, + isBold: true, ), ], ], ), if (status != null) AppCustomChipWidget( - labelText: status, + labelText: VitalSignUiModel.statusToString(status), backgroundColor: scheme.chipBg, textColor: scheme.chipFg, diff --git a/lib/presentation/water_monitor/water_consumption_page.dart b/lib/presentation/water_monitor/water_consumption_page.dart index 7e6d1d5e..27d60d5b 100644 --- a/lib/presentation/water_monitor/water_consumption_page.dart +++ b/lib/presentation/water_monitor/water_consumption_page.dart @@ -75,8 +75,8 @@ class _WaterConsumptionPageState extends State { Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ - title.toText14(weight: FontWeight.w500, color: AppColors.labelTextColor), - subTitle.toText18(weight: FontWeight.w600), + title.toText14(isBold: true, color: AppColors.labelTextColor), + subTitle.toText18(isBold: true), ], ), Utils.buildSvgWithAssets(icon: AppAssets.greenTickIcon, applyThemeColor: false) @@ -115,7 +115,7 @@ class _WaterConsumptionPageState extends State { ), child: Row( children: [ - viewModel.selectedDurationFilter.toText12(fontWeight: FontWeight.w500), + viewModel.selectedDurationFilter.toText12(isBold: true), Utils.buildSvgWithAssets(icon: AppAssets.arrow_down, height: 16.h), ], ), @@ -502,16 +502,16 @@ class _WaterConsumptionPageState extends State { for (var position in positions) { if ((value - position).abs() < 1) { - return '${value.toInt()}ml'.toText10(weight: FontWeight.w600); + return '${value.toInt()}ml'.toText10(isBold: true); } } } else { // Show percentage labels - if (value == 0) return '0%'.toText10(weight: FontWeight.w600); - if (value == 25) return '25%'.toText10(weight: FontWeight.w600); - if (value == 50) return '50%'.toText10(weight: FontWeight.w600); - if (value == 75) return '75%'.toText10(weight: FontWeight.w600); - if (value == 100) return '100%'.toText10(weight: FontWeight.w600); + if (value == 0) return '0%'.toText10(isBold: true); + if (value == 25) return '25%'.toText10(isBold: true); + if (value == 50) return '50%'.toText10(isBold: true); + if (value == 75) return '75%'.toText10(isBold: true); + if (value == 100) return '100%'.toText10(isBold: true); } return SizedBox.shrink(); }, @@ -533,7 +533,7 @@ class _WaterConsumptionPageState extends State { return Padding( padding: EdgeInsets.only(top: 10.h), child: data[index].label.toText8( - fontWeight: FontWeight.w600, + isBold: true, color: AppColors.labelTextColor, ), ); @@ -554,7 +554,7 @@ class _WaterConsumptionPageState extends State { if (selectedDuration == 'Monthly' && index < 7) { return Padding( padding: EdgeInsets.only(top: 10.h), - child: data[index].label.toText10(weight: FontWeight.w600, color: AppColors.labelTextColor), + child: data[index].label.toText10(isBold: true, color: AppColors.labelTextColor), ); } @@ -588,7 +588,7 @@ class _WaterConsumptionPageState extends State { materialTapTargetSize: MaterialTapTargetSize.shrinkWrap, ), (useUpperCase ? value.toUpperCase() : value.toCamelCase) - .toText16(weight: FontWeight.w500, textOverflow: TextOverflow.ellipsis, maxlines: 1) + .toText16(isBold: true, textOverflow: TextOverflow.ellipsis, maxlines: 1) .expanded, ], ).onPress(onTap), diff --git a/lib/presentation/water_monitor/water_monitor_settings_page.dart b/lib/presentation/water_monitor/water_monitor_settings_page.dart index 81157c8a..5c47bc38 100644 --- a/lib/presentation/water_monitor/water_monitor_settings_page.dart +++ b/lib/presentation/water_monitor/water_monitor_settings_page.dart @@ -54,7 +54,7 @@ class _WaterMonitorSettingsPageState extends State { materialTapTargetSize: MaterialTapTargetSize.shrinkWrap, ), (useUpperCase ? value.toUpperCase() : value.toCamelCase) - .toText16(weight: FontWeight.w500, textOverflow: TextOverflow.ellipsis, maxlines: 1) + .toText16(isBold: true, textOverflow: TextOverflow.ellipsis, maxlines: 1) .expanded, ], ).onPress(onTap), @@ -167,7 +167,7 @@ class _WaterMonitorSettingsPageState extends State { ), style: TextStyle( fontSize: 14.f, - fontWeight: FontWeight.w500, + fontWeight: FontWeight.w600, color: AppColors.textColor, ), ); @@ -206,7 +206,7 @@ class _WaterMonitorSettingsPageState extends State { crossAxisAlignment: CrossAxisAlignment.start, mainAxisAlignment: MainAxisAlignment.start, children: [ - label.toText12(fontWeight: FontWeight.w500, color: AppColors.inputLabelTextColor), + label.toText12(isBold: true, color: AppColors.inputLabelTextColor), if (inputField != null) Container( height: 20.w, @@ -214,7 +214,7 @@ class _WaterMonitorSettingsPageState extends State { child: inputField, ) else if (value != null) - value.toCamelCase.toText12(fontWeight: FontWeight.w500, color: AppColors.textColor), + value.toCamelCase.toText12(isBold: true, color: AppColors.textColor), ], ), ), @@ -231,8 +231,8 @@ class _WaterMonitorSettingsPageState extends State { crossAxisAlignment: CrossAxisAlignment.start, mainAxisAlignment: MainAxisAlignment.start, children: [ - "Unit".toText12(fontWeight: FontWeight.w500, color: AppColors.inputLabelTextColor), - unit.toText12(fontWeight: FontWeight.w500, color: AppColors.textColor), + "Unit".toText12(isBold: true, color: AppColors.inputLabelTextColor), + unit.toText12(isBold: true, color: AppColors.textColor), ], ), SizedBox(width: 12.w), diff --git a/lib/presentation/water_monitor/widgets/cup_bottomsheet_widgets.dart b/lib/presentation/water_monitor/widgets/cup_bottomsheet_widgets.dart index 4ee6170e..40018b61 100644 --- a/lib/presentation/water_monitor/widgets/cup_bottomsheet_widgets.dart +++ b/lib/presentation/water_monitor/widgets/cup_bottomsheet_widgets.dart @@ -22,8 +22,8 @@ void showSwitchCupBottomSheet(BuildContext context) { titleWidget: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ - "Switch Cups".toText20(weight: FontWeight.w600), - "Select your preferred cup size".toText16(weight: FontWeight.w500, color: AppColors.greyTextColor), + "Switch Cups".toText20(isBold: true), + "Select your preferred cup size".toText16(isBold: true, color: AppColors.greyTextColor), ], ), child: SwitchCupBottomSheet(), @@ -125,7 +125,7 @@ class SwitchCupBottomSheet extends StatelessWidget { ], ), SizedBox(height: 2.h), - '${cup.capacityMl}ml'.toText10(weight: FontWeight.w500), + '${cup.capacityMl}ml'.toText10(isBold: true), ], ), ); @@ -145,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), - LocaleKeys.add.tr(context: context).toText10(weight: FontWeight.w500), + LocaleKeys.add.tr(context: context).toText10(isBold: true), ], ), ); @@ -159,7 +159,7 @@ void showCustomizeCupBottomSheet(BuildContext context, {WaterCupModel? cupToEdit titleWidget: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ - LocaleKeys.customizeDrinkCup.tr(context: context).toText20(weight: FontWeight.w600), + LocaleKeys.customizeDrinkCup.tr(context: context).toText20(isBold: true), ], ), child: CustomizeCupBottomSheet(cupToEdit: cupToEdit), @@ -252,7 +252,7 @@ class _CustomizeCupBottomSheetState extends State { 'ml', style: TextStyle( fontSize: 16.f, - fontWeight: FontWeight.w500, + fontWeight: FontWeight.w600, color: AppColors.greyTextColor, height: 1.0, ), diff --git a/lib/presentation/water_monitor/widgets/hydration_tips_widget.dart b/lib/presentation/water_monitor/widgets/hydration_tips_widget.dart index 7c49550c..af714bd7 100644 --- a/lib/presentation/water_monitor/widgets/hydration_tips_widget.dart +++ b/lib/presentation/water_monitor/widgets/hydration_tips_widget.dart @@ -37,22 +37,22 @@ class HydrationTipsWidget extends StatelessWidget { ), SizedBox(height: 8.h), " • ${LocaleKeys.drinkBeforeYouFeelThirsty.tr(context: context)}".toText12( - fontWeight: FontWeight.w500, + isBold: true, color: AppColors.textColorLight, ), SizedBox(height: 4.h), " • ${LocaleKeys.keepRefillableBottleNextToYou.tr(context: context)}".toText12( - fontWeight: FontWeight.w500, + isBold: true, color: AppColors.textColorLight, ), SizedBox(height: 4.h), " • ${LocaleKeys.trackYourDailyIntakeToStayMotivated.tr(context: context)}".toText12( - fontWeight: FontWeight.w500, + isBold: true, color: AppColors.textColorLight, ), SizedBox(height: 4.h), " • ${LocaleKeys.chooseSparklingWaterInsteadOfSoda.tr(context: context)}".toText12( - fontWeight: FontWeight.w500, + isBold: true, color: AppColors.textColorLight, ), SizedBox(height: 8.h), 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 115dd212..42ffc6f5 100644 --- a/lib/presentation/water_monitor/widgets/water_action_buttons_widget.dart +++ b/lib/presentation/water_monitor/widgets/water_action_buttons_widget.dart @@ -54,7 +54,7 @@ class WaterActionButtonsWidget extends StatelessWidget { hasShadow: true, ), child: (cupAmount > 0 ? "+ $cupAmount ml" : "+ 0ml").toText12( - fontWeight: FontWeight.w600, + isBold: true, color: AppColors.whiteColor, ), ), 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 ec796abc..4426375a 100644 --- a/lib/presentation/water_monitor/widgets/water_intake_summary_widget.dart +++ b/lib/presentation/water_monitor/widgets/water_intake_summary_widget.dart @@ -53,13 +53,13 @@ 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") - .toText18(weight: FontWeight.w600, color: AppColors.textColor), + .toText18(isBold: true, color: AppColors.textColor), // Extract only time if "tomorrow" is present, otherwise show as is (vm.nextDrinkTime.toLowerCase().contains('tomorrow') ? vm.nextDrinkTime.replaceAll(RegExp(r'tomorrow', caseSensitive: false), '').trim() : vm.nextDrinkTime) - .toText32(weight: FontWeight.w600, color: AppColors.blueColor), + .toText32(isBold: true, color: AppColors.blueColor), SizedBox(height: 12.h), Row( @@ -97,9 +97,9 @@ class WaterIntakeSummaryWidget extends StatelessWidget { return Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ - "$title: ".toText16(weight: FontWeight.w500, color: AppColors.textColor), + "$title: ".toText16(isBold: true, color: AppColors.textColor), subTitle.toText12( - fontWeight: FontWeight.w600, + isBold: true, color: subTitleColor ?? AppColors.greyTextColor, ), ], diff --git a/lib/theme/app_theme.dart b/lib/theme/app_theme.dart index 51d2cbc2..c676819f 100644 --- a/lib/theme/app_theme.dart +++ b/lib/theme/app_theme.dart @@ -4,7 +4,13 @@ import 'package:hmg_patient_app_new/theme/colors.dart'; class AppTheme { static ThemeData getTheme(isArabic) => ThemeData( - fontFamily: isArabic ? 'GESSTwo' : 'Poppins', + fontFamily: isArabic ? 'CairoArabic' : 'Poppins', + // fontFamily: 'CairoArabic', + textTheme: const TextTheme( + displayLarge: TextStyle(fontFamily: 'CairoArabic'), + bodyLarge: TextStyle(fontFamily: 'CairoArabic'), + // Add others as needed + ), primarySwatch: Colors.red, visualDensity: VisualDensity.adaptivePlatformDensity, brightness: Brightness.light, @@ -20,8 +26,7 @@ class AppTheme { canvasColor: Colors.white, highlightColor: Colors.grey[100]!.withOpacity(0.4), splashColor: Colors.transparent, - bottomSheetTheme: BottomSheetThemeData( - backgroundColor: Colors.black.withOpacity(0)), + bottomSheetTheme: BottomSheetThemeData(backgroundColor: Colors.black.withOpacity(0)), floatingActionButtonTheme: const FloatingActionButtonThemeData(highlightElevation: 2, disabledElevation: 0, elevation: 2), appBarTheme: AppBarTheme( color: const Color(0xff515A5D), @@ -29,13 +34,19 @@ class AppTheme { actionsIconTheme: IconThemeData( color: Colors.grey[800], ), - systemOverlayStyle: SystemUiOverlayStyle.light, + systemOverlayStyle: SystemUiOverlayStyle.dark, surfaceTintColor: Colors.transparent, ), ); static ThemeData getDarkTheme(isArabic) => ThemeData( - fontFamily: isArabic ? 'GESSTwo' : 'Poppins', + fontFamily: isArabic ? 'CairoArabic' : 'Poppins', + // fontFamily: 'CairoArabic', + textTheme: const TextTheme( + displayLarge: TextStyle(fontFamily: 'CairoArabic'), + bodyLarge: TextStyle(fontFamily: 'CairoArabic'), + // Add others as needed + ), primarySwatch: Colors.red, visualDensity: VisualDensity.adaptivePlatformDensity, brightness: Brightness.dark, diff --git a/lib/widgets/app_language_change.dart b/lib/widgets/app_language_change.dart index 4a589ae7..f041cd85 100644 --- a/lib/widgets/app_language_change.dart +++ b/lib/widgets/app_language_change.dart @@ -103,7 +103,7 @@ class _AppLanguageChangeState extends State { }, materialTapTargetSize: MaterialTapTargetSize.shrinkWrap, ), - title.toText16(weight: FontWeight.w500, textOverflow: TextOverflow.ellipsis, maxlines: 1, fontFamily: _value == "ar" ? 'GESSTwo' : 'Poppins').expanded, + title.toText16(isBold: true, textOverflow: TextOverflow.ellipsis, maxlines: 1, fontFamily: _value == "ar" ? 'CairoArabic' : 'Poppins').expanded, ], ).paddingOnly(left: 16, right: 16).onPress(() { setState(() { diff --git a/lib/widgets/appbar/collapsing_list_view.dart b/lib/widgets/appbar/collapsing_list_view.dart index 4c754b65..d641cb77 100644 --- a/lib/widgets/appbar/collapsing_list_view.dart +++ b/lib/widgets/appbar/collapsing_list_view.dart @@ -28,6 +28,7 @@ class CollapsingListView extends StatelessWidget { VoidCallback? downloadReport; VoidCallback? viewImage; VoidCallback? location; + VoidCallback? downloadInvoice; Widget? bottomChild; Widget? trailing; bool isClose; @@ -53,6 +54,7 @@ class CollapsingListView extends StatelessWidget { this.downloadReport, this.viewImage, this.location, + this.downloadInvoice, this.isLeading = true, this.trailing, this.leadingCallback, @@ -98,6 +100,7 @@ class CollapsingListView extends StatelessWidget { downloadReport: downloadReport, viewImage: viewImage, location: location, + downloadInvoice: downloadInvoice, bottomChild: bottomChild, trailing: trailing, aiOverview: aiOverview, @@ -178,7 +181,7 @@ class CollapsingListView extends StatelessWidget { // fontSize: (12 - (2 * (1 - t))).f, // fontWeight: FontWeight.lerp( // FontWeight.w300, -// FontWeight.w500, +// FontWeight.w600, // t, // )!, // ), @@ -212,6 +215,7 @@ class ScrollAnimatedTitle extends StatefulWidget implements PreferredSizeWidget VoidCallback? downloadReport; VoidCallback? viewImage; VoidCallback? location; + VoidCallback? downloadInvoice; Widget? bottomChild; Widget? trailing; @@ -232,6 +236,7 @@ class ScrollAnimatedTitle extends StatefulWidget implements PreferredSizeWidget this.downloadReport, this.viewImage, this.location, + this.downloadInvoice, this.bottomChild, this.trailing, }); @@ -313,6 +318,7 @@ class _ScrollAnimatedTitleState extends State { if (widget.downloadReport != null) actionButton(context, t, title: LocaleKeys.downloadReport.tr(context: context), icon: AppAssets.download).onPress(widget.downloadReport!), if (widget.viewImage != null) actionButton(context, t, title: LocaleKeys.viewRadiologyImage.tr(context: context), icon: AppAssets.download).onPress(widget.viewImage!), if (widget.location != null) actionButton(context, t, title: LocaleKeys.sortByLocation.tr(context: context), icon: AppAssets.location).onPress(widget.location!), + if (widget.downloadInvoice != null) actionButton(context, t, title: LocaleKeys.downloadInvoice.tr(context: context), icon: AppAssets.download).onPress(widget.downloadInvoice!), if (widget.trailing != null) widget.trailing!, ] ], @@ -347,7 +353,7 @@ class _ScrollAnimatedTitleState extends State { fontSize: (14 - (2 * (1 - t))).f, fontWeight: FontWeight.lerp( FontWeight.w400, - FontWeight.w500, + FontWeight.w600, t, )!, ), @@ -382,8 +388,8 @@ class _ScrollAnimatedTitleState extends State { letterSpacing: -0.4, fontSize: (12 - (2 * (1 - t))).f, fontWeight: FontWeight.lerp( - FontWeight.w400, FontWeight.w500, + FontWeight.w700, t, )!, ), diff --git a/lib/widgets/appbar/collapsing_toolbar.dart b/lib/widgets/appbar/collapsing_toolbar.dart index 9b1d4854..f7b3edbb 100644 --- a/lib/widgets/appbar/collapsing_toolbar.dart +++ b/lib/widgets/appbar/collapsing_toolbar.dart @@ -194,7 +194,7 @@ class _CollapsingToolbarState extends State { fontSize: (14 - (2 * (1 - t))).f, fontWeight: FontWeight.lerp( FontWeight.w300, - FontWeight.w500, + FontWeight.w600, t, )!, ), diff --git a/lib/widgets/attachment_options.dart b/lib/widgets/attachment_options.dart index be443f81..4d52d1e2 100644 --- a/lib/widgets/attachment_options.dart +++ b/lib/widgets/attachment_options.dart @@ -1,60 +1,105 @@ +import 'package:easy_localization/easy_localization.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/generated/locale_keys.g.dart'; import 'package:hmg_patient_app_new/theme/colors.dart'; import 'package:flutter/material.dart'; -import 'package:flutter_svg/flutter_svg.dart'; class AttachmentOptions extends StatelessWidget { - VoidCallback onCameraTap; - VoidCallback onGalleryTap; - VoidCallback onFilesTap; - bool showFilesOption; + final Future Function() onCameraTap; + final Future Function() onGalleryTap; + final VoidCallback onFilesTap; + final bool showFilesOption; - AttachmentOptions({Key? key, required this.onCameraTap, required this.onGalleryTap, required this.onFilesTap, this.showFilesOption = true}) : super(key: key); + const AttachmentOptions({ + Key? key, + required this.onCameraTap, + required this.onGalleryTap, + required this.onFilesTap, + this.showFilesOption = true + }) : super(key: key); @override Widget build(BuildContext context) { - return SizedBox( - width: double.infinity, - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - "Upload Attachment".toSectionHeading(), - "Select from gallery or open camera".toText11(weight: FontWeight.w500), - GridView( - gridDelegate: const SliverGridDelegateWithFixedCrossAxisCount(crossAxisCount: 3, childAspectRatio: 105 / 105, crossAxisSpacing: 9, mainAxisSpacing: 9), - physics: const NeverScrollableScrollPhysics(), - padding: const EdgeInsets.only(top: 21, bottom: 14), - shrinkWrap: true, - children: [ - itemView("open_camera.svg", "Open\nCamera", onCameraTap), - itemView("gallery.svg", "Upload from\nGallery", onGalleryTap), - if (showFilesOption) itemView("files.svg", "Upload from\nFiles", onFilesTap), - ], - ) - ], - ).paddingOnly(left: 21, right: 21, bottom: 21), + final appState = getIt.get(); + + return Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + checkInOptionCard( + appState, + AppAssets.open_camera, + LocaleKeys.openCamera.tr(context: context), + LocaleKeys.takePhotoWithCamera.tr(context: context), + ).onPress(() async { + await onCameraTap(); + }), + SizedBox(height: 16.h), + checkInOptionCard( + appState, + AppAssets.gallery, + LocaleKeys.openGallery.tr(context: context), + LocaleKeys.selectFromPhotoLibrary.tr(context: context), + ).onPress(() async { + await onGalleryTap(); + }), + if (showFilesOption) SizedBox(height: 16.h), + if (showFilesOption) + checkInOptionCard( + appState, + AppAssets.files, + LocaleKeys.openFiles.tr(context: context), + LocaleKeys.selectDocumentFiles.tr(context: context), + ).onPress(() async { + onFilesTap(); + }), + ], ); } - Widget itemView(String icon, String title, VoidCallback onTap) { - return InkWell( - onTap: onTap, + Widget checkInOptionCard(AppState appState, String icon, String title, String subTitle) { + return Container( + decoration: RoundedRectangleBorder().toSmoothCornerDecoration( + color: AppColors.whiteColor, + borderRadius: 20.h, + hasShadow: false, + ), child: Column( crossAxisAlignment: CrossAxisAlignment.start, - mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ - SvgPicture.asset( - "assets/images/svg/$icon", + Utils.buildSvgWithAssets(icon: icon, width: 40.h, height: 40.h, fit: BoxFit.fill), + SizedBox(height: 16.h), + Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Expanded( + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + title.toText16(isBold: true, color: AppColors.textColor), + subTitle.toText12(isBold: true, color: AppColors.greyTextColor), + ], + ), + ), + Transform.flip( + flipX: appState.isArabic(), + child: Utils.buildSvgWithAssets( + icon: AppAssets.forward_arrow_icon_small, + iconColor: AppColors.blackColor, + width: 18.h, + height: 13.h, + fit: BoxFit.contain, + ), + ), + ], ), - title.toText11(isBold: true), ], - ).paddingOnly(left: 13, right: 13, top: 16, bottom: 12).expanded.objectContainerBorderView( - disablePadding: true, - radius: 10, - color: AppColors.greyF7Color.withOpacity(.48), - borderColor: AppColors.lightGreyEFColor.withOpacity(.48), - ), + ).paddingAll(16.h), ); } } diff --git a/lib/widgets/bottom_navigation/bottom_navigation.dart b/lib/widgets/bottom_navigation/bottom_navigation.dart index 9d390388..2518375a 100644 --- a/lib/widgets/bottom_navigation/bottom_navigation.dart +++ b/lib/widgets/bottom_navigation/bottom_navigation.dart @@ -80,7 +80,7 @@ class BottomNavigation extends StatelessWidget { ), // const SizedBox(height: 10), SizedBox(height: item.isSpecial ? 0 : 8.h), - item.label.toText11(weight: FontWeight.w500), + item.label.toText11(isBold: true), // SizedBox(height: item.isSpecial ? 5 : 0) ], ), diff --git a/lib/widgets/bottomsheet/generic_bottom_sheet.dart b/lib/widgets/bottomsheet/generic_bottom_sheet.dart index 21cc076f..a161c35f 100644 --- a/lib/widgets/bottomsheet/generic_bottom_sheet.dart +++ b/lib/widgets/bottomsheet/generic_bottom_sheet.dart @@ -1,4 +1,5 @@ import 'dart:io'; +import 'dart:ui' as ui; import 'package:easy_localization/easy_localization.dart'; import 'package:flutter/material.dart'; @@ -82,7 +83,7 @@ class GenericBottomSheetState extends State { FocusScope.of(context).unfocus(); }, child: Directionality( - textDirection: Directionality.of(context), + textDirection: ui.TextDirection.ltr, child: Container( padding: EdgeInsets.all(24.h), decoration: RoundedRectangleBorder().toSmoothCornerDecoration( diff --git a/lib/widgets/buttons/custom_button.dart b/lib/widgets/buttons/custom_button.dart index de3b598a..d2534587 100644 --- a/lib/widgets/buttons/custom_button.dart +++ b/lib/widgets/buttons/custom_button.dart @@ -8,7 +8,7 @@ import 'package:hmg_patient_app_new/theme/colors.dart'; class CustomButton extends StatelessWidget { final String text; final String? icon; - final VoidCallback onPressed; + final VoidCallback? onPressed; final Color backgroundColor; final Color borderColor; final Color textColor; @@ -26,6 +26,7 @@ class CustomButton extends StatelessWidget { final TextOverflow? textOverflow; final BorderSide? borderSide; final bool applyThemeColor; + final bool isBold; const CustomButton({ super.key, @@ -39,8 +40,9 @@ class CustomButton extends StatelessWidget { this.padding = const EdgeInsets.fromLTRB(8, 10, 8, 10), this.fontSize, this.fontFamily, - this.fontWeight = FontWeight.w500, + this.fontWeight = FontWeight.w700, this.isDisabled = false, + this.isBold = false, this.icon, this.iconColor = Colors.white, this.height, @@ -56,8 +58,10 @@ class CustomButton extends StatelessWidget { final radius = borderRadius ?? (12.r); final iconS = iconSize ?? (24.h); final fontS = fontSize ?? (16.f); + final isButtonDisabled = isDisabled || onPressed == null; + return GestureDetector( - onTap: isDisabled ? null : onPressed, + onTap: isButtonDisabled ? null : onPressed, child: Container( height: height == null ? 56.h @@ -67,10 +71,10 @@ class CustomButton extends StatelessWidget { width: width, padding: padding, decoration: RoundedRectangleBorder().toSmoothCornerDecoration( - color: isDisabled ? AppColors.inputLabelTextColor.withValues(alpha: 0.3) : backgroundColor, + color: isButtonDisabled ? AppColors.inputLabelTextColor.withValues(alpha: 0.3) : backgroundColor, borderRadius: radius, customBorder: BorderRadius.circular(radius), - side: borderSide ?? BorderSide(width: borderWidth.h, color: isDisabled ? Colors.transparent : borderColor)), + side: borderSide ?? BorderSide(width: borderWidth.h, color: isButtonDisabled ? Colors.transparent : borderColor)), child: Row( crossAxisAlignment: CrossAxisAlignment.center, mainAxisAlignment: MainAxisAlignment.center, @@ -78,9 +82,24 @@ class CustomButton extends StatelessWidget { if (icon != null) Padding( 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, applyThemeColor: applyThemeColor), + child: Utils.buildSvgWithAssets(icon: icon!, iconColor: iconColor, isDisabled: isButtonDisabled, width: iconS, height: iconS, applyThemeColor: applyThemeColor), + ), + if (text.isNotEmpty && icon != null) + Flexible( + child: Text( + text, + overflow: textOverflow ?? TextOverflow.ellipsis, + maxLines: 1, + textAlign: TextAlign.center, + style: context.dynamicTextStyle( + fontSize: fontS, + color: isButtonDisabled ? AppColors.greyTextColor : textColor, + letterSpacing: 0, + fontWeight: isBold ? FontWeight.w700 : fontWeight, + ), + ), ), - if (text.isNotEmpty) + if (text.isNotEmpty && icon == null) Text( text, overflow: textOverflow ?? TextOverflow.ellipsis, @@ -88,9 +107,9 @@ class CustomButton extends StatelessWidget { textAlign: TextAlign.center, style: context.dynamicTextStyle( fontSize: fontS, - color: isDisabled ? AppColors.greyTextColor : textColor, + color: isButtonDisabled ? AppColors.greyTextColor : textColor, letterSpacing: 0, - fontWeight: fontWeight, + fontWeight: isBold ? FontWeight.w700 : fontWeight, ), ), ], diff --git a/lib/widgets/chip/app_custom_chip_widget.dart b/lib/widgets/chip/app_custom_chip_widget.dart index b2ae4288..58ce21a0 100644 --- a/lib/widgets/chip/app_custom_chip_widget.dart +++ b/lib/widgets/chip/app_custom_chip_widget.dart @@ -89,7 +89,7 @@ class AppCustomChipWidget extends StatelessWidget { applyThemeColor: applyThemeColor, ) : SizedBox.shrink(), - label: richText ?? (labelText?? "").toText10(weight: FontWeight.w500, letterSpacing: 0, color: resolvedTextColor, isEnglishOnly: isEnglishOnly), + label: richText ?? (labelText?? "").toText10(isBold: true, letterSpacing: 0, color: resolvedTextColor, isEnglishOnly: isEnglishOnly), padding: padding, materialTapTargetSize: MaterialTapTargetSize.shrinkWrap, labelPadding: labelPadding ?? EdgeInsetsDirectional.only(end: deleteIcon?.isNotEmpty == true ? 2.w : 8.w), @@ -119,7 +119,7 @@ class AppCustomChipWidget extends StatelessWidget { ) : Chip( materialTapTargetSize: MaterialTapTargetSize.shrinkWrap, - label: richText ?? (labelText?? "").toText10(weight: FontWeight.w500, letterSpacing: 0, color: resolvedTextColor, isCenter: true, isEnglishOnly: isEnglishOnly), + label: richText ?? (labelText?? "").toText10(isBold: true, letterSpacing: 0, color: resolvedTextColor, isCenter: true, isEnglishOnly: isEnglishOnly), padding: EdgeInsets.zero, backgroundColor: resolvedBackgroundColor, shape: shape ?? diff --git a/lib/widgets/chip/custom_chip_widget.dart b/lib/widgets/chip/custom_chip_widget.dart index b60db577..afa00efa 100644 --- a/lib/widgets/chip/custom_chip_widget.dart +++ b/lib/widgets/chip/custom_chip_widget.dart @@ -71,7 +71,7 @@ class CustomChipWidget extends StatelessWidget { isBold: true, color: isSelected ? Colors.white : textColor ?? chipType.color, maxlines: 1, - weight: FontWeight.w500, + weight: FontWeight.w600, letterSpacing: -0.5), ], ), diff --git a/lib/widgets/chip/custom_selectable_chip.dart b/lib/widgets/chip/custom_selectable_chip.dart index 06ccb95f..cfefbbd2 100644 --- a/lib/widgets/chip/custom_selectable_chip.dart +++ b/lib/widgets/chip/custom_selectable_chip.dart @@ -44,7 +44,7 @@ class CustomSelectableChip extends StatelessWidget { label, style: TextStyle( fontSize: 12.f, - fontWeight: FontWeight.w500, + fontWeight: FontWeight.w600, color: selected ? activeTextColor : resolvedInactiveTextColor, letterSpacing: -0.02 * 18, height: 1.0, diff --git a/lib/widgets/common_bottom_sheet.dart b/lib/widgets/common_bottom_sheet.dart index 9b5fa025..d47971d7 100644 --- a/lib/widgets/common_bottom_sheet.dart +++ b/lib/widgets/common_bottom_sheet.dart @@ -238,7 +238,7 @@ 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.of(context).size.width //MediaQuery.of(context).size.width, // Full width ), context: context, isScrollControlled: true, @@ -292,7 +292,7 @@ void showCommonBottomSheetWithoutHeight( children: [ titleWidget ?? Expanded( - child: title.toText20(weight: FontWeight.w600), + child: title.toText20(isBold: true), ), if (isCloseButtonVisible) ...[ Utils.buildSvgWithAssets( @@ -363,7 +363,7 @@ void showCommonBottomSheetWithoutHeight( // mainAxisAlignment: MainAxisAlignment.spaceBetween, // crossAxisAlignment: CrossAxisAlignment.center, // children: [ -// titleWidget ?? Expanded(child: title.toText20(weight: FontWeight.w600)), +// titleWidget ?? Expanded(child: title.toText20(isBold: true)), // Utils.buildSvgWithAssets(icon: AppAssets.close_bottom_sheet_icon, iconColor: Color(0xff2B353E)).onPress(() { // Navigator.of(context).pop(); // }), diff --git a/lib/widgets/countdown_timer.dart b/lib/widgets/countdown_timer.dart index 86249fc8..8cd3fa73 100644 --- a/lib/widgets/countdown_timer.dart +++ b/lib/widgets/countdown_timer.dart @@ -88,7 +88,7 @@ Widget buildDigit(String digit) { style: TextStyle( fontWeight: FontWeight.bold, color: Colors.black, - fontSize: 14.f, + fontSize: 10.f, ), ), ), diff --git a/lib/widgets/custom_tab_bar.dart b/lib/widgets/custom_tab_bar.dart index 79741add..c2d5d537 100644 --- a/lib/widgets/custom_tab_bar.dart +++ b/lib/widgets/custom_tab_bar.dart @@ -127,10 +127,7 @@ class CustomTabBarState extends State { width: 18.w, iconColor: isSelected ? activeTextColor : inActiveTextColor, ), - tabBar.title.toText13( - weight: isSelected ? FontWeight.w600 : FontWeight.w500, - color: isSelected ? activeTextColor : inActiveTextColor, - letterSpacing: isSelected ? -0.3 : -0.1), + tabBar.title.toText13(color: isSelected ? activeTextColor : inActiveTextColor, letterSpacing: isSelected ? -0.3 : -0.1, isBold: true), ], )).onPress(() { setState(() { diff --git a/lib/widgets/date_range_selector/date_range_calender.dart b/lib/widgets/date_range_selector/date_range_calender.dart index d6e2eadf..fd37d0cb 100644 --- a/lib/widgets/date_range_selector/date_range_calender.dart +++ b/lib/widgets/date_range_selector/date_range_calender.dart @@ -75,7 +75,7 @@ class _DateRangeSelectorState extends State { style: TextStyle( color: AppColors.calenderTextColor, fontSize: 14.f, - fontWeight: FontWeight.w500, + fontWeight: FontWeight.w600, letterSpacing: -.2 ), ).paddingSymmetrical(24.h,0.h), @@ -109,7 +109,7 @@ class _DateRangeSelectorState extends State { viewHeaderStyle: DateRangePickerViewHeaderStyle( backgroundColor: AppColors.whiteColor, textStyle: TextStyle( - fontSize: 14.f, + fontSize: 12.f, fontWeight: FontWeight.w600, letterSpacing: -0.46, color: AppColors.textColor, @@ -121,17 +121,25 @@ class _DateRangeSelectorState extends State { selectionShape: DateRangePickerSelectionShape.rectangle, selectionRadius: 12.h, selectionColor: AppColors.transparent, + selectionTextStyle: TextStyle( + fontFamily: "Poppins", + ), startRangeSelectionColor: AppColors.primaryRedColor, endRangeSelectionColor: AppColors.primaryRedColor, rangeSelectionColor: AppColors.primaryRedColor.withOpacity(0.1), + rangeTextStyle: TextStyle( + fontFamily: "Poppins", + ), todayHighlightColor: Colors.transparent, monthCellStyle: DateRangePickerMonthCellStyle( textStyle: TextStyle( + fontFamily: "Poppins", fontSize: 12.f, color: AppColors.textColor, ), todayTextStyle: TextStyle( + fontFamily: "Poppins", color: AppColors.textColor, fontWeight: FontWeight.bold, ), @@ -248,7 +256,7 @@ class _DateRangeSelectorState extends State { style: TextStyle( color: AppColors.inputLabelTextColor, fontSize: 12.f, - fontWeight: FontWeight.w500, + fontWeight: FontWeight.w600, ), ), Text( @@ -256,7 +264,7 @@ class _DateRangeSelectorState extends State { style: TextStyle( color: AppColors.textColor, fontSize: 14.f, - fontWeight: FontWeight.w500, + fontWeight: FontWeight.w600, ), ) ], @@ -318,6 +326,7 @@ class _DateRangeSelectorState extends State { width: 1, ), borderRadius: BorderRadius.circular(10)), + isEnglishOnly: true, ).onPress((){ _calendarController.selectedRange = null; model.currentlySelectedRange = Range.LAST_6MONTH; @@ -325,7 +334,13 @@ class _DateRangeSelectorState extends State { }), AppCustomChipWidget( - labelText: "Year ${model.getCurrentYear}", + richText: Row( + children: [ + "${LocaleKeys.year.tr(context: context)} ".toText10(), + model.getCurrentYear.toString().toText10(isEnglishOnly: true), + ], + ), + labelText: "${LocaleKeys.year.tr(context: context)} ${model.getCurrentYear}", backgroundColor: model.currentlySelectedRange == Range.THIS_YEAR ? AppColors.primaryRedColor.withOpacity(0.1) : AppColors.whiteColor, @@ -337,6 +352,7 @@ class _DateRangeSelectorState extends State { width: 1, ), borderRadius: BorderRadius.circular(10)), + isEnglishOnly: true, ).onPress((){ _calendarController.selectedRange = null; model.currentlySelectedRange = Range.THIS_YEAR; diff --git a/lib/widgets/datepicker_widget.dart b/lib/widgets/datepicker_widget.dart index bac05512..364897df 100644 --- a/lib/widgets/datepicker_widget.dart +++ b/lib/widgets/datepicker_widget.dart @@ -55,7 +55,7 @@ class DatePickerWidget extends StatelessWidget { labelText, style: TextStyle( fontSize: 12.f, - fontWeight: FontWeight.w500, + // isBold: true, color: Color(0xff898A8D), letterSpacing: -0.2, height: 18 / 12, @@ -102,7 +102,7 @@ class DatePickerWidget extends StatelessWidget { style: TextStyle( fontSize: 14.f, height: 21 / 14, - fontWeight: FontWeight.w500, + // isBold: true, color: (selectedValue != null && selectedValue!.isNotEmpty) ? const Color(0xff2E3039) : const Color(0xffB0B0B0), letterSpacing: -0.2, ), diff --git a/lib/widgets/dialogs/confirm_dialog.dart b/lib/widgets/dialogs/confirm_dialog.dart index 5f7782fa..10889b5d 100644 --- a/lib/widgets/dialogs/confirm_dialog.dart +++ b/lib/widgets/dialogs/confirm_dialog.dart @@ -35,7 +35,7 @@ class ConfirmDialog extends StatelessWidget { Expanded( child: Text( title ?? LocaleKeys.confirm.tr(), - style: TextStyle(fontSize: 24, fontWeight: FontWeight.w600, color: AppColors.blackColor, height: 35 / 24, letterSpacing: -0.96), + style: TextStyle(fontSize: 24, fontWeight: FontWeight.w700, color: AppColors.blackColor, height: 35 / 24, letterSpacing: -0.96), ).paddingOnly(top: 16), ), IconButton( diff --git a/lib/widgets/dropdown/country_dropdown_widget.dart b/lib/widgets/dropdown/country_dropdown_widget.dart index c0efbc98..ce1a660f 100644 --- a/lib/widgets/dropdown/country_dropdown_widget.dart +++ b/lib/widgets/dropdown/country_dropdown_widget.dart @@ -93,7 +93,7 @@ class CustomCountryDropdownState extends State { children: [ Text( LocaleKeys.phoneNumber.tr(), - style: TextStyle(fontSize: 12.f, height: 1.5, fontWeight: FontWeight.w500, letterSpacing: -1), + style: TextStyle(fontSize: 12.f, height: 1.5, fontWeight: FontWeight.w600, letterSpacing: -1), ), Row( mainAxisAlignment: MainAxisAlignment.start, @@ -138,7 +138,7 @@ class CustomCountryDropdownState extends State { ? selectedCountry!.nameArabic : selectedCountry!.displayName : LocaleKeys.selectCountry.tr(), - style: TextStyle(fontSize: 14.f, height: 21 / 14, fontWeight: FontWeight.w500, letterSpacing: -0.2), + style: TextStyle(fontSize: 14.f, height: 21 / 14, fontWeight: FontWeight.w600, letterSpacing: -0.2), ), ], ); @@ -201,7 +201,7 @@ class CustomCountryDropdownState extends State { if (!widget.isFromBottomSheet) SizedBox(width: 12.h), if (!widget.isFromBottomSheet) Text(appState.getLanguageCode() == "ar" ? country.nameArabic : country.displayName, - style: TextStyle(fontSize: 14.f, height: 21 / 14, fontWeight: FontWeight.w500, letterSpacing: -0.2)), + style: TextStyle(fontSize: 14.f, height: 21 / 14, fontWeight: FontWeight.w600, letterSpacing: -0.2)), ], ), )), @@ -266,7 +266,7 @@ class CustomCountryDropdownState extends State { // if (!widget.isFromBottomSheet) SizedBox(width: 12.h), // if (!widget.isFromBottomSheet) // Text(appState.getLanguageCode() == "ar" ? country.nameArabic : country.displayName, - // style: TextStyle(fontSize: 14.fSize, height: 21 / 14, fontWeight: FontWeight.w500, letterSpacing: -0.2)), + // style: TextStyle(fontSize: 14.fSize, height: 21 / 14, isBold: true, letterSpacing: -0.2)), // ], // ), // )), diff --git a/lib/widgets/dropdown/dropdown_widget.dart b/lib/widgets/dropdown/dropdown_widget.dart index a78b8fa5..7f76d8a6 100644 --- a/lib/widgets/dropdown/dropdown_widget.dart +++ b/lib/widgets/dropdown/dropdown_widget.dart @@ -101,7 +101,7 @@ class DropdownWidget extends StatelessWidget { labelText, style: TextStyle( fontSize: 12.f, - fontWeight: FontWeight.w500, + fontWeight: FontWeight.w600, color: labelColor ?? Color(0xff898A8D), letterSpacing: -0.2, height: 18 / 12, @@ -132,7 +132,7 @@ class DropdownWidget extends StatelessWidget { style: TextStyle( fontSize: 14.f, height: 21 / 14, - fontWeight: FontWeight.w500, + fontWeight: FontWeight.w600, letterSpacing: -0.2, ), ), @@ -163,7 +163,7 @@ class DropdownWidget extends StatelessWidget { style: TextStyle( fontSize: 14.f, height: 21 / 14, - fontWeight: FontWeight.w500, + fontWeight: FontWeight.w600, // color: (selectedValue != null && selectedValue!.isNotEmpty) ? const Color(0xff2E3039) : const Color(0xffB0B0B0), color: AppColors.textColor, letterSpacing: -0.2, diff --git a/lib/widgets/family_files/family_file_add_widget.dart b/lib/widgets/family_files/family_file_add_widget.dart index c4f43248..ff19fa28 100644 --- a/lib/widgets/family_files/family_file_add_widget.dart +++ b/lib/widgets/family_files/family_file_add_widget.dart @@ -36,7 +36,7 @@ class _FamilyFileAddWidgetState extends State { mainAxisAlignment: MainAxisAlignment.start, mainAxisSize: MainAxisSize.min, children: [ - widget.message.toText16(color: AppColors.textColor, weight: FontWeight.w500), + widget.message.toText16(color: AppColors.textColor, isBold: true), SizedBox(height: 20.h), Container( decoration: BoxDecoration(color: AppColors.whiteColor, borderRadius: BorderRadius.circular(24)), @@ -108,7 +108,7 @@ class _FamilyFileAddWidgetState extends State { }, icon: AppAssets.add_icon, height: 56.h, - fontWeight: FontWeight.w600), + isBold: true), SizedBox(height: 20.h), ], ); diff --git a/lib/widgets/graph/CustomBarGraph.dart b/lib/widgets/graph/CustomBarGraph.dart index 2f50f20d..2b3026d1 100644 --- a/lib/widgets/graph/CustomBarGraph.dart +++ b/lib/widgets/graph/CustomBarGraph.dart @@ -85,7 +85,7 @@ class CustomBarChart extends StatelessWidget { this.showBottomTitleDates = true, this.isFullScreeGraph = false, this.secondaryBarColor, - this.bottomLabelFontWeight = FontWeight.w500, + this.bottomLabelFontWeight = FontWeight.w600, this.bottomLabelSize, this.leftLabelInterval, this.leftLabelReservedSize, @@ -139,7 +139,7 @@ class CustomBarChart extends StatelessWidget { TextStyle( color: Colors.black, fontSize: 12.f, - fontWeight: FontWeight.w500, + fontWeight: FontWeight.w600, ), ); }, diff --git a/lib/widgets/graph/custom_graph.dart b/lib/widgets/graph/custom_graph.dart index 898dc5ea..a5c9f897 100644 --- a/lib/widgets/graph/custom_graph.dart +++ b/lib/widgets/graph/custom_graph.dart @@ -100,7 +100,7 @@ class CustomGraph extends StatelessWidget { this.graphShadowColor, this.graphGridColor, this.bottomLabelColor, - this.bottomLabelFontWeight = FontWeight.w500, + this.bottomLabelFontWeight = FontWeight.w600, this.bottomLabelSize, this.leftLabelInterval, this.leftLabelReservedSize, @@ -192,7 +192,7 @@ class CustomGraph extends StatelessWidget { return LineTooltipItem( '${dataPoint.actualValue} ${dataPoint.unitOfMeasurement ?? ""} - ${dataPoint.displayTime}', - TextStyle(color: Colors.black, fontSize: 12.f, fontWeight: FontWeight.w500, fontFamily: "Poppins"), + TextStyle(color: Colors.black, fontSize: 12.f, fontWeight: FontWeight.w600, fontFamily: "Poppins"), ); }).toList(); }, @@ -281,7 +281,17 @@ class CustomGraph extends StatelessWidget { begin: Alignment.centerLeft, end: Alignment.centerRight, ), - dotData: FlDotData(show: showLinePoints), + dotData: FlDotData( + show: true, + getDotPainter: (spot, percent, barData, idx) { + return FlDotCirclePainter( + radius: 6, + color: resolvedGraphColor, + strokeWidth: 2, + strokeColor: Colors.white, + ); + }, + ), belowBarData: BarAreaData( show: showShadow, applyCutOffY: cutOffY != null, @@ -317,7 +327,17 @@ class CustomGraph extends StatelessWidget { begin: Alignment.centerLeft, end: Alignment.centerRight, ), - dotData: FlDotData(show: showLinePoints), + dotData: FlDotData( + show: true, + getDotPainter: (spot, percent, barData, idx) { + return FlDotCirclePainter( + radius: 6, + color: secondaryGraphColor ?? AppColors.blueColor, + strokeWidth: 2, + strokeColor: Colors.white, + ); + }, + ), belowBarData: BarAreaData(show: false), ), ); diff --git a/lib/widgets/image_picker.dart b/lib/widgets/image_picker.dart index eb3021f5..f7665a56 100644 --- a/lib/widgets/image_picker.dart +++ b/lib/widgets/image_picker.dart @@ -1,48 +1,77 @@ import 'dart:convert'; import 'dart:io'; +import 'package:easy_localization/easy_localization.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/attachment_options.dart'; -import 'package:hmg_patient_app_new/widgets/bottom_sheet.dart'; import 'package:file_picker/file_picker.dart'; import 'package:flutter/material.dart'; +import 'package:hmg_patient_app_new/widgets/common_bottom_sheet.dart'; import 'package:image_picker/image_picker.dart'; final ImagePicker picker = ImagePicker(); class ImageOptions { - static void showImageOptionsNew(BuildContext context, bool showFilesOption, Function(String, File) image) { - showMyBottomSheet( + static void showImageOptionsNew( + BuildContext context, + bool showFilesOption, + Function(String, File) image, { + Future Function()? checkCameraPermission, + Future Function()? checkGalleryPermission, + }) { + showCommonBottomSheetWithoutHeight( context, - callBackFunc: () {}, + title: LocaleKeys.profilePicture.tr(context: context), child: AttachmentOptions( showFilesOption: showFilesOption, onCameraTap: () async { + // Check camera permission if callback provided + if (checkCameraPermission != null) { + final hasPermission = await checkCameraPermission(); + if (!hasPermission) { + return; // Permission denied, don't proceed + } + } + + Navigator.pop(context); // Close bottom sheet + if (Platform.isAndroid) { cameraImageAndroid(image); } else { - File _image = File((await ImagePicker.platform.pickImage(source: ImageSource.camera, imageQuality: 20))?.path ?? ""); - // XFile? media = await picker.pickMedia(); - String? fileName = _image.path; - var bytes = File(fileName!).readAsBytesSync(); + XFile? pickedFile = await picker.pickImage(source: ImageSource.camera, imageQuality: 20); + if (pickedFile == null) return; + + File _image = File(pickedFile.path); + String fileName = _image.path; + var bytes = File(fileName).readAsBytesSync(); String base64Encode = base64.encode(bytes); - if (base64Encode != null) { - image(base64Encode, _image); - } + image(base64Encode, _image); } }, onGalleryTap: () async { + // Check gallery permission if callback provided + if (checkGalleryPermission != null) { + final hasPermission = await checkGalleryPermission(); + if (!hasPermission) { + return; // Permission denied, don't proceed + } + } + + Navigator.pop(context); // Close bottom sheet + if (Platform.isAndroid) { galleryImageAndroid(image); } else { - File _image = File((await picker.pickMedia())?.path ?? ""); + XFile? pickedFile = await picker.pickImage(source: ImageSource.gallery, imageQuality: 20); + if (pickedFile == null) return; + + File _image = File(pickedFile.path); String fileName = _image.path; var bytes = File(fileName).readAsBytesSync(); String base64Encode = base64.encode(bytes); - if (base64Encode != null) { - image(base64Encode, _image); - } + image(base64Encode, _image); } }, onFilesTap: () async { @@ -66,7 +95,63 @@ class ImageOptions { image(result.files.first.path.toString(), files.first); }, ), + callBackFunc: () {}, + isFullScreen: false, ); + // showMyBottomSheet( + // context, + // callBackFunc: () {}, + // child: AttachmentOptions( + // showFilesOption: showFilesOption, + // onCameraTap: () async { + // if (Platform.isAndroid) { + // cameraImageAndroid(image); + // } else { + // File _image = File((await ImagePicker.platform.pickImage(source: ImageSource.camera, imageQuality: 20))?.path ?? ""); + // // XFile? media = await picker.pickMedia(); + // String? fileName = _image.path; + // var bytes = File(fileName!).readAsBytesSync(); + // String base64Encode = base64.encode(bytes); + // if (base64Encode != null) { + // image(base64Encode, _image); + // } + // } + // }, + // onGalleryTap: () async { + // if (Platform.isAndroid) { + // galleryImageAndroid(image); + // } else { + // File _image = File((await picker.pickMedia())?.path ?? ""); + // String fileName = _image.path; + // var bytes = File(fileName).readAsBytesSync(); + // String base64Encode = base64.encode(bytes); + // if (base64Encode != null) { + // image(base64Encode, _image); + // } + // } + // }, + // onFilesTap: () async { + // FilePickerResult? result = await FilePicker.platform.pickFiles( + // type: FileType.custom, + // allowedExtensions: [ + // 'jpg', + // 'jpeg ', + // 'pdf', + // 'txt', + // 'docx', + // 'doc', + // 'pptx', + // 'xlsx', + // 'png', + // 'rar', + // 'zip', + // ], + // ); + // List files = result!.paths.map((path) => File(path!)).toList(); + // image(result.files.first.path.toString(), files.first); + // }, + // ), + // ); } // static void showImageOptions(BuildContext context, Function(String, File) image) { @@ -129,25 +214,25 @@ class ImageOptions { } void galleryImageAndroid(Function(String, File) image) async { - File _image = File((await picker.pickMedia())?.path ?? ""); + XFile? pickedFile = await picker.pickImage(source: ImageSource.gallery, imageQuality: 20); + if (pickedFile == null) return; + File _image = File(pickedFile.path); String fileName = _image.path; var bytes = File(fileName).readAsBytesSync(); String base64Encode = base64.encode(bytes); - if (base64Encode != null) { - image(base64Encode, _image); - } + image(base64Encode, _image); } void cameraImageAndroid(Function(String, File) image) async { - File _image = File((await picker.pickMedia())?.path ?? ""); + XFile? pickedFile = await picker.pickImage(source: ImageSource.camera, imageQuality: 20); + if (pickedFile == null) return; + + File _image = File(pickedFile.path); String fileName = _image.path; var bytes = File(fileName).readAsBytesSync(); String base64Encode = base64.encode(bytes); - if (base64Encode != null) { - image(base64Encode, _image); - } - + image(base64Encode, _image); } class _BottomSheet extends StatelessWidget { diff --git a/lib/widgets/input_widget.dart b/lib/widgets/input_widget.dart index a64775aa..a134ac79 100644 --- a/lib/widgets/input_widget.dart +++ b/lib/widgets/input_widget.dart @@ -12,7 +12,6 @@ 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; @@ -110,7 +109,7 @@ class TextInputWidget extends StatelessWidget { // onTap: () => node.unfocus(), // child: Container( // padding: const EdgeInsets.all(12.0), - // child: "Done".toText16(weight: FontWeight.w500, color: AppColors.infoColor), + // child: "Done".toText16(isBold: true, color: AppColors.infoColor), // ), // ); // } @@ -290,57 +289,75 @@ class TextInputWidget extends StatelessWidget { } Widget _buildLabelText(Color? labelColor) { - return labelText.toText12(fontWeight: FontWeight.w500, color: labelColor ?? AppColors.inputLabelTextColor); + return labelText.toText12(isBold: true, color: labelColor ?? AppColors.inputLabelTextColor); } Widget _buildTextField(BuildContext context) { double fontS = fontSize ?? 14.f; + final isArabic = getIt.get().isArabic(); + 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, + return Directionality( + textDirection: isArabic ? TextDirection.rtl : TextDirection.ltr, + child: 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 : (isWalletAmountInput! ? const TextInputType.numberWithOptions(decimal: true) : keyboardType), + controller: controller, + readOnly: isReadOnly, + textAlignVertical: TextAlignVertical.top, + textAlign: isArabic ? TextAlign.right : TextAlign.left, + textDirection: TextDirection.ltr, + onChanged: onChange, + focusNode: focusNode ?? _focusNode, + autofocus: autoFocus, + textInputAction: TextInputAction.done, + cursorHeight: isWalletAmountInput! ? 40.h : 20.h, + maxLength: isWalletAmountInput! ? 7 : 100, + inputFormatters: isWalletAmountInput! + ? [ + _ThousandSeparatorInputFormatter(), + ] + : null, + 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.w600, + color: AppColors.textColor, + letterSpacing: -1, + fontFamily: fontFamily, + locale: const Locale('en', 'US'), // Force English locale for text style + ), + decoration: InputDecoration( + counterText: "", + isDense: true, + hintText: hintText, + hintStyle: TextStyle( + fontFamily: isArabic ? 'CairoArabic' : 'Poppins', + fontSize: 14.f, + height: 21 / 16, + fontWeight: FontWeight.w600, + 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, isBold: true), + contentPadding: EdgeInsets.zero, + border: InputBorder.none, + focusedBorder: InputBorder.none, + enabledBorder: InputBorder.none, + ), ), ), ); @@ -371,7 +388,7 @@ class TextInputWidget extends StatelessWidget { // style: TextStyle( // fontSize: fontS, // height: isMultiline ? 1.2 : (isWalletAmountInput! ? 1 / 4 : 0), - // fontWeight: FontWeight.w500, + // isBold: true, // color: AppColors.textColor, // letterSpacing: -1, // ), @@ -381,11 +398,11 @@ class TextInputWidget extends StatelessWidget { // hintStyle: TextStyle( // fontSize: 14.f, // height: 21 / 16, - // fontWeight: FontWeight.w500, + // isBold: true, // 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), + // prefixIcon: prefix == null ? null : "+${prefix!}".toText14(letterSpacing: -1, color: AppColors.textColor, isBold: true), // contentPadding: EdgeInsets.zero, // border: InputBorder.none, // focusedBorder: InputBorder.none, @@ -405,3 +422,63 @@ class TextInputWidget extends StatelessWidget { ); } } + +class _ThousandSeparatorInputFormatter extends TextInputFormatter { + @override + TextEditingValue formatEditUpdate( + TextEditingValue oldValue, + TextEditingValue newValue, + ) { + // Remove all commas to get the raw number + String newText = newValue.text.replaceAll(',', ''); + + // Allow only digits and one decimal point + if (newText.isNotEmpty && !RegExp(r'^\d*\.?\d{0,2}$').hasMatch(newText)) { + return oldValue; + } + + // Split into integer and decimal parts + String integerPart; + String decimalPart = ''; + + if (newText.contains('.')) { + final parts = newText.split('.'); + integerPart = parts[0]; + decimalPart = '.${parts[1]}'; + } else { + integerPart = newText; + } + + // Add thousand separators to the integer part + if (integerPart.isNotEmpty) { + final buffer = StringBuffer(); + int count = 0; + for (int i = integerPart.length - 1; i >= 0; i--) { + buffer.write(integerPart[i]); + count++; + if (count == 3 && i > 0) { + buffer.write(','); + count = 0; + } + } + integerPart = buffer.toString().split('').reversed.join(); + } + + final formatted = '$integerPart$decimalPart'; + + // Calculate new cursor position + int cursorOffset = newValue.selection.baseOffset; + // Count commas before cursor in the new formatted string + int commasInNew = ','.allMatches(formatted.substring(0, cursorOffset.clamp(0, formatted.length))).length; + // Count commas before cursor in the old value + int commasInOld = ','.allMatches(oldValue.text.substring(0, oldValue.selection.baseOffset.clamp(0, oldValue.text.length))).length; + int newCursorPos = cursorOffset + (commasInNew - commasInOld); + newCursorPos = newCursorPos.clamp(0, formatted.length); + + return TextEditingValue( + text: formatted, + selection: TextSelection.collapsed(offset: newCursorPos), + ); + } +} + diff --git a/lib/widgets/language_switcher.dart b/lib/widgets/language_switcher.dart index 033eaaff..3ee10df3 100644 --- a/lib/widgets/language_switcher.dart +++ b/lib/widgets/language_switcher.dart @@ -62,12 +62,12 @@ class _LanguageSelectorState extends State { // currentLangData['name']?.toUpperCase() ?? 'EN', widget.currentLanguage == 'ar' ? "ENGLISH" : "العربية", style: context.dynamicTextStyle( - fontWeight: FontWeight.w500, + fontWeight: FontWeight.w600, fontSize: 14.f, color: AppColors.primaryRedColor, letterSpacing: 0.1, isLanguageSwitcher: true, - fontFamily: widget.currentLanguage == 'ar' ? "Poppins" : "GESSTwo"), + fontFamily: widget.currentLanguage == 'ar' ? "Poppins" : "CairoArabic"), ), ], ), diff --git a/lib/widgets/map/HMSMap.dart b/lib/widgets/map/HMSMap.dart index 96b2facd..50da5f3a 100644 --- a/lib/widgets/map/HMSMap.dart +++ b/lib/widgets/map/HMSMap.dart @@ -1,63 +1,263 @@ import 'dart:async'; +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/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:huawei_map/huawei_map.dart' ; +import 'package:huawei_map/huawei_map.dart'; -class HMSMap extends StatefulWidget{ +class HMSMap extends StatefulWidget { final CameraPosition currentLocation; final Function(CameraPosition) onCameraMoved; final VoidCallback onCameraIdle; - final MapType mapType; + final MapType initialMapType; final bool compassEnabled; final bool myLocationEnabled; final bool showCenterMarker; final Completer? inputController; - HMSMap({super.key, required this.currentLocation ,required this.onCameraMoved,required this.onCameraIdle, - this.mapType = MapType.normal,this.compassEnabled = false,this.showCenterMarker = false, - this.myLocationEnabled = true, this.inputController}); + const HMSMap({ + super.key, + required this.currentLocation, + required this.onCameraMoved, + required this.onCameraIdle, + this.initialMapType = MapType.normal, + this.compassEnabled = false, + this.showCenterMarker = false, + this.myLocationEnabled = true, + this.inputController, + }); @override State createState() => _HMSMapState(); } class _HMSMapState extends State { - Completer? controller; + Completer? controller; + late MapType _selectedMapType; + bool _showMapTypeSelector = false; + + final List<_MapTypeOption> _mapTypeOptions = [ + _MapTypeOption(type: MapType.normal, label: LocaleKeys.normalMapType.tr(), icon: Icons.map_outlined), + _MapTypeOption(type: MapType.terrain, label: LocaleKeys.terrainMapType.tr(), icon: Icons.terrain_outlined), + ]; @override void initState() { + super.initState(); HuaweiMapInitializer.initializeMap(); controller = widget.inputController ?? Completer(); - super.initState(); + _selectedMapType = widget.initialMapType; + } + + void _onMapTypeSelected(MapType type) { + setState(() { + _selectedMapType = type; + _showMapTypeSelector = false; + }); } - // @override + + void _toggleSelector() => + setState(() => _showMapTypeSelector = !_showMapTypeSelector); + @override - Widget build(BuildContext context) => - Stack( - children: [ - HuaweiMap( - mapType: widget.mapType, - zoomControlsEnabled: false, - myLocationEnabled: widget.myLocationEnabled, - myLocationButtonEnabled: false, - compassEnabled: widget.compassEnabled, - onCameraIdle:()=> widget.onCameraIdle(), - initialCameraPosition: widget.currentLocation, - onCameraMove: (value) => widget.onCameraMoved(value), - onMapCreated: (HuaweiMapController controller) { - this.controller?.complete(controller); - }, + Widget build(BuildContext context) { + return Stack( + children: [ + // ── Huawei Map ──────────────────────────────────────────────── + HuaweiMap( + mapType: _selectedMapType, + zoomControlsEnabled: false, + myLocationEnabled: widget.myLocationEnabled, + myLocationButtonEnabled: false, + compassEnabled: widget.compassEnabled, + onCameraIdle: widget.onCameraIdle, + initialCameraPosition: widget.currentLocation, + onCameraMove: widget.onCameraMoved, + onMapCreated: (HuaweiMapController ctrl) { + controller?.complete(ctrl); + }, + ), + + // ── Centre pin marker ───────────────────────────────────────── + if (widget.showCenterMarker) + Align( + alignment: Alignment.center, + child: Icon( + Icons.location_pin, + size: 36.h, + color: AppColors.primaryRedColor, + ).paddingOnly(bottom: 24.h), + ), + + // ── Map-type toggle + panel ─────────────────────────────────── + PositionedDirectional( + top: 48.h, + end: 16.w, + child: Column( + crossAxisAlignment: CrossAxisAlignment.end, + children: [ + _MapTypeToggleButton( + isActive: _showMapTypeSelector, + onTap: _toggleSelector, + ), + AnimatedSwitcher( + duration: const Duration(milliseconds: 220), + transitionBuilder: (child, animation) => FadeTransition( + opacity: animation, + child: SizeTransition( + sizeFactor: animation, + axisAlignment: -1, + child: child, + ), + ), + child: _showMapTypeSelector + ? _MapTypeSelectorPanel( + options: _mapTypeOptions, + selected: _selectedMapType, + onSelected: _onMapTypeSelected, + ) + : const SizedBox.shrink(), ), - Visibility( - visible: widget.showCenterMarker, - child: Align( - alignment: Alignment.center, - child: Icon(Icons.location_pin, size: 36.h, color: AppColors.primaryRedColor).paddingOnly(bottom: 24.h), + ], + ), + ), + ], + ); + } +} + +// ── Toggle button ───────────────────────────────────────────────────────────── + +class _MapTypeToggleButton extends StatelessWidget { + final bool isActive; + final VoidCallback onTap; + + const _MapTypeToggleButton({required this.isActive, required this.onTap}); + + @override + Widget build(BuildContext context) { + return GestureDetector( + onTap: onTap, + child: AnimatedContainer( + duration: const Duration(milliseconds: 200), + width: 42.w, + height: 42.h, + decoration: BoxDecoration( + color: isActive ? AppColors.primaryRedColor : AppColors.whiteColor, + borderRadius: BorderRadius.circular(8.r), + ), + child: Icon( + Icons.layers, + size: 22.h, + color: isActive ? AppColors.whiteColor : AppColors.blackBgColor, + ), + ), + ); + } +} + +// ── Selector panel ──────────────────────────────────────────────────────────── + +class _MapTypeSelectorPanel extends StatelessWidget { + final List<_MapTypeOption> options; + final MapType selected; + final ValueChanged onSelected; + + const _MapTypeSelectorPanel({ + required this.options, + required this.selected, + required this.onSelected, + }); + + @override + Widget build(BuildContext context) { + return Container( + key: const ValueKey('map_type_panel'), + margin: EdgeInsets.only(top: 8.h), + padding: EdgeInsets.symmetric(vertical: 8.h, horizontal: 6.w), + decoration: BoxDecoration( + color: AppColors.whiteColor, + borderRadius: BorderRadius.circular(12.r), + + ), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: options + .map((opt) => _MapTypeItem( + option: opt, + isSelected: selected == opt.type, + onTap: () => onSelected(opt.type), + )) + .toList(), + ), + ); + } +} + +// ── Single row item ─────────────────────────────────────────────────────────── + +class _MapTypeItem extends StatelessWidget { + final _MapTypeOption option; + final bool isSelected; + final VoidCallback onTap; + + const _MapTypeItem({ + required this.option, + required this.isSelected, + required this.onTap, + }); + + @override + Widget build(BuildContext context) { + return GestureDetector( + onTap: onTap, + child: AnimatedContainer( + duration: const Duration(milliseconds: 180), + margin: EdgeInsets.symmetric(vertical: 3.h), + padding: EdgeInsets.symmetric(horizontal: 10.w, vertical: 8.h), + decoration: BoxDecoration( + color: isSelected + ? AppColors.primaryRedColor.withOpacity(0.1) + : Colors.transparent, + borderRadius: BorderRadius.circular(8.r), + ), + child: Row( + mainAxisSize: MainAxisSize.min, + children: [ + Icon( + option.icon, + size: 18.h, + color: isSelected ? AppColors.primaryRedColor : AppColors.blackBgColor, ), - ) - ], - ); + SizedBox(width: 8.w), + Text( + option.label, + style: TextStyle( + fontSize: 14.f, + fontWeight: isSelected ? FontWeight.w600 : FontWeight.w400, + color: isSelected ? AppColors.primaryRedColor :AppColors.blackBgColor, + ), + ), + ], + ), + ), + ); + } +} + +// ── Data class ──────────────────────────────────────────────────────────────── + +class _MapTypeOption { + final MapType type; + final String label; + final IconData icon; + + const _MapTypeOption({ + required this.type, + required this.label, + required this.icon, + }); } \ No newline at end of file diff --git a/lib/widgets/map/gms_map.dart b/lib/widgets/map/gms_map.dart index f4c3ad54..33e0befb 100644 --- a/lib/widgets/map/gms_map.dart +++ b/lib/widgets/map/gms_map.dart @@ -1,54 +1,417 @@ +// import 'dart:async'; +// +// import 'package:flutter/material.dart'; +// import 'package:google_maps_flutter/google_maps_flutter.dart'; +// import 'package:hmg_patient_app_new/core/utils/size_utils.dart'; +// import 'package:hmg_patient_app_new/extensions/widget_extensions.dart'; +// import 'package:hmg_patient_app_new/theme/colors.dart'; +// +// class GMSMap extends StatelessWidget{ +// Completer? controller; +// final CameraPosition currentLocation; +// final Function(CameraPosition) onCameraMoved; +// final VoidCallback onCameraIdle; +// final MapType mapType; +// final bool compassEnabled; +// final bool myLocationEnabled; +// final bool showCenterMarker; +// +// +// GMSMap({super.key, required this.currentLocation ,required this.onCameraMoved,required this.onCameraIdle, +// this.mapType = MapType.normal,this.compassEnabled = false,this.showCenterMarker = false, +// this.myLocationEnabled = true,Completer? inputController}){ +// controller = inputController ?? Completer(); +// } +// +// @override +// Widget build(BuildContext context) { +// return Stack( +// children: [ +// GoogleMap( +// mapType: mapType, +// zoomControlsEnabled: true, +// myLocationEnabled: myLocationEnabled, +// myLocationButtonEnabled: false, +// compassEnabled: compassEnabled, +// initialCameraPosition: currentLocation, +// onCameraMove: (value) => onCameraMoved(value), +// onCameraIdle: ()=>onCameraIdle(), +// // padding: EdgeInsets.only(bottom: 300.h), +// onMapCreated: (GoogleMapController controller) { +// this.controller?.complete(controller); +// }, +// ), +// Visibility( +// visible: showCenterMarker, +// child: Align( +// alignment: Alignment.center, +// child: Icon(Icons.location_pin, size: 36.h, color: AppColors.primaryRedColor).paddingOnly(bottom: 24.h), +// ), +// ) +// ], +// ); +// } +// } + import 'dart:async'; +import 'dart:io'; +import 'package:easy_localization/easy_localization.dart'; import 'package:flutter/material.dart'; import 'package:google_maps_flutter/google_maps_flutter.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/location_util.dart'; +import 'package:hmg_patient_app_new/core/utils/utils.dart'; import 'package:hmg_patient_app_new/core/utils/size_utils.dart'; import 'package:hmg_patient_app_new/extensions/widget_extensions.dart'; +import 'package:hmg_patient_app_new/generated/locale_keys.g.dart'; import 'package:hmg_patient_app_new/theme/colors.dart'; -class GMSMap extends StatelessWidget{ - Completer? controller; +class GMSMap extends StatefulWidget { final CameraPosition currentLocation; final Function(CameraPosition) onCameraMoved; final VoidCallback onCameraIdle; - final MapType mapType; + final MapType initialMapType; final bool compassEnabled; final bool myLocationEnabled; final bool showCenterMarker; + final Completer? inputController; + final double bottomPaddingHeight; + + const GMSMap({ + super.key, + required this.currentLocation, + required this.onCameraMoved, + required this.onCameraIdle, + this.initialMapType = MapType.normal, + this.compassEnabled = false, + this.showCenterMarker = false, + this.myLocationEnabled = true, + this.inputController, + required this.bottomPaddingHeight, + }); + + @override + State createState() => _GMSMapState(); +} + +class _GMSMapState extends State { + late Completer _controller; + GoogleMapController? _activeController; + late MapType _selectedMapType; + bool _showMapTypeSelector = false; + num bottomPaddingHeight = 0; + + /// All supported map types with display labels and icons + final List<_MapTypeOption> _mapTypeOptions = [ + _MapTypeOption( + type: MapType.normal, + label: LocaleKeys.normalMapType.tr(), + icon: Icons.map_outlined, + ), + _MapTypeOption( + type: MapType.satellite, + label: LocaleKeys.satelliteMapType.tr(), + icon: Icons.satellite_alt_outlined, + ), + _MapTypeOption( + type: MapType.terrain, + label: LocaleKeys.terrainMapType.tr(), + icon: Icons.terrain_outlined, + ), + _MapTypeOption( + type: MapType.hybrid, + label: LocaleKeys.hybridMapType.tr(), + icon: Icons.layers_outlined, + ), + ]; + + @override + void initState() { + super.initState(); + _controller = widget.inputController ?? Completer(); + _selectedMapType = widget.initialMapType; + } + + void _onMapTypeSelected(MapType type) { + setState(() { + _selectedMapType = type; + _showMapTypeSelector = false; + }); + } + + void _toggleMapTypeSelector() { + setState(() => _showMapTypeSelector = !_showMapTypeSelector); + } + Future _animateToMyLocation() async { + final appState = getIt.get(); + final locationUtils = getIt.get(); - GMSMap({super.key, required this.currentLocation ,required this.onCameraMoved,required this.onCameraIdle, - this.mapType = MapType.normal,this.compassEnabled = false,this.showCenterMarker = false, - this.myLocationEnabled = true,Completer? inputController}){ - controller = inputController ?? Completer(); + void _moveToLocation() { + if (appState.userLat != 0.0 && appState.userLong != 0.0 && _activeController != null) { + _activeController!.animateCamera( + CameraUpdate.newCameraPosition( + CameraPosition( + target: LatLng(appState.userLat, appState.userLong), + zoom: 16, + ), + ), + ); + } + } + + if (appState.userLat != 0.0 && appState.userLong != 0.0) { + _moveToLocation(); + } else { + locationUtils.getLocation( + isShowConfirmDialog: true, + onSuccess: (latLng) { + _moveToLocation(); + }, + onFailure: () {}, + onLocationDeniedForever: () {}, + ); + } } @override Widget build(BuildContext context) { - return Stack( - children: [ - GoogleMap( - mapType: mapType, - zoomControlsEnabled: true, - myLocationEnabled: myLocationEnabled, + return Stack( + children: [ + // ── Google Map ────────────────────────────────────────────────── + GoogleMap( + mapType: _selectedMapType, + zoomControlsEnabled: false, + // myLocationEnabled: widget.myLocationEnabled, + myLocationEnabled: true, myLocationButtonEnabled: false, - compassEnabled: compassEnabled, - initialCameraPosition: currentLocation, - onCameraMove: (value) => onCameraMoved(value), - onCameraIdle: ()=>onCameraIdle(), - // padding: EdgeInsets.only(bottom: 300.h), + // padding: EdgeInsets.only(bottom: double.parse(widget.bottomPaddingHeight.toString())), + // padding: EdgeInsets.only(top: Platform.isAndroid ? double.parse(widget.bottomPaddingHeight.toString()) : 0, bottom: Platform.isIOS ? double.parse(widget.bottomPaddingHeight.toString()) : 0), + compassEnabled: widget.compassEnabled, + initialCameraPosition: widget.currentLocation, + onCameraMove: widget.onCameraMoved, + onCameraIdle: widget.onCameraIdle, onMapCreated: (GoogleMapController controller) { - this.controller?.complete(controller); + _activeController = controller; + if (!_controller.isCompleted) { + _controller.complete(controller); + } }, - ), - Visibility( - visible: showCenterMarker, - child: Align( - alignment: Alignment.center, - child: Icon(Icons.location_pin, size: 36.h, color: AppColors.primaryRedColor).paddingOnly(bottom: 24.h), + ), + + // ── Centre pin marker ─────────────────────────────────────────── + if (widget.showCenterMarker) + Align( + alignment: Alignment.center, + child: Icon( + Icons.location_pin, + size: 36.h, + color: AppColors.primaryRedColor, + ).paddingOnly(bottom: Platform.isAndroid ? 30.h : 60.h), ), - ) + + // ── My Location button ──────────────────────────────────────────── + PositionedDirectional( + // bottom: 300.h, + // bottom: 400.h, + bottom: widget.bottomPaddingHeight, + start: 16.w, + child: GestureDetector( + onTap: _animateToMyLocation, + child: Container( + width: 42.w, + height: 42.h, + decoration: BoxDecoration( + color: AppColors.whiteColor, + borderRadius: BorderRadius.circular(8.r), + ), + child: Icon( + Icons.my_location, + size: 22.h, + color: AppColors.blackBgColor, + ), + ), + ), + ), + + // ── Map-type toggle button ────────────────────────────────────── + PositionedDirectional( + top: 48.h, + end: 16.w, + child: Column( + crossAxisAlignment: CrossAxisAlignment.end, + children: [ + // Toggle button + _MapTypeToggleButton( + isActive: _showMapTypeSelector, + onTap: _toggleMapTypeSelector, + ), + + // Expandable selector panel + AnimatedSwitcher( + duration: const Duration(milliseconds: 220), + transitionBuilder: (child, animation) => FadeTransition( + opacity: animation, + child: SizeTransition( + sizeFactor: animation, + axisAlignment: -1, + child: child, + ), + ), + child: _showMapTypeSelector + ? _MapTypeSelectorPanel( + options: _mapTypeOptions, + selected: _selectedMapType, + onSelected: _onMapTypeSelected, + ) + : const SizedBox.shrink(), + ), + ], + ), + ), ], ); } +} + +// ── Toggle button ───────────────────────────────────────────────────────────── + +class _MapTypeToggleButton extends StatelessWidget { + final bool isActive; + final VoidCallback onTap; + + const _MapTypeToggleButton({ + required this.isActive, + required this.onTap, + }); + + @override + Widget build(BuildContext context) { + return GestureDetector( + onTap: onTap, + child: AnimatedContainer( + duration: const Duration(milliseconds: 200), + width: 42.w, + height: 42.h, + decoration: BoxDecoration( + color: isActive ? AppColors.primaryRedColor : AppColors.whiteColor, + borderRadius: BorderRadius.circular(8.r), + ), + child: Icon( + Icons.layers, + size: 22.h, + color: isActive ? AppColors.whiteColor : AppColors.blackBgColor, + ), + ), + ); + } +} + +// ── Selector panel ──────────────────────────────────────────────────────────── + +class _MapTypeSelectorPanel extends StatelessWidget { + final List<_MapTypeOption> options; + final MapType selected; + final ValueChanged onSelected; + + const _MapTypeSelectorPanel({ + required this.options, + required this.selected, + required this.onSelected, + }); + + @override + Widget build(BuildContext context) { + return Container( + key: const ValueKey('map_type_panel'), + margin: EdgeInsets.only(top: 8.h), + padding: EdgeInsets.symmetric(vertical: 8.h, horizontal: 6.w), + decoration: BoxDecoration( + color: AppColors.whiteColor, + borderRadius: BorderRadius.circular(12.r), + ), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: options + .map( + (opt) => _MapTypeItem( + option: opt, + isSelected: selected == opt.type, + onTap: () => onSelected(opt.type), + ), + ) + .toList(), + ), + ); + } +} + +// ── Single row item ─────────────────────────────────────────────────────────── + +class _MapTypeItem extends StatelessWidget { + final _MapTypeOption option; + final bool isSelected; + final VoidCallback onTap; + + const _MapTypeItem({ + required this.option, + required this.isSelected, + required this.onTap, + }); + + @override + Widget build(BuildContext context) { + return GestureDetector( + onTap: onTap, + child: AnimatedContainer( + duration: const Duration(milliseconds: 180), + margin: EdgeInsets.symmetric(vertical: 3.h), + padding: EdgeInsets.symmetric(horizontal: 10.w, vertical: 8.h), + decoration: BoxDecoration( + color: isSelected + ? AppColors.primaryRedColor.withOpacity(0.1) + : Colors.transparent, + borderRadius: BorderRadius.circular(8.r), + ), + child: Row( + mainAxisSize: MainAxisSize.min, + children: [ + Icon( + option.icon, + size: 18.h, + color: isSelected ? AppColors.primaryRedColor : Colors.black54, + ), + SizedBox(width: 8.w), + Text( + option.label, + style: TextStyle( + fontSize: 13.f, + fontWeight: + isSelected ? FontWeight.w600 : FontWeight.w400, + color: + isSelected ? AppColors.primaryRedColor : AppColors.blackBgColor, + ), + ), + ], + ), + ), + ); + } +} + +// ── Data class ──────────────────────────────────────────────────────────────── + +class _MapTypeOption { + final MapType type; + final String label; + final IconData icon; + + const _MapTypeOption({ + required this.type, + required this.label, + required this.icon, + }); } \ No newline at end of file diff --git a/lib/widgets/map/location_map_widget.dart b/lib/widgets/map/location_map_widget.dart index e3139a93..da17b486 100644 --- a/lib/widgets/map/location_map_widget.dart +++ b/lib/widgets/map/location_map_widget.dart @@ -108,7 +108,7 @@ class LocationMapWidget extends StatelessWidget { address, style: TextStyle( fontSize: 14.f, - fontWeight: FontWeight.w500, + fontWeight: FontWeight.w600, color: AppColors.blackColor, letterSpacing: -0.4, ), @@ -174,7 +174,7 @@ class LocationMapWidget extends StatelessWidget { borderColor: AppColors.textColor.withValues(alpha: 0.01), textColor: AppColors.whiteColor, fontSize: 14.f, - fontWeight: FontWeight.w500, + fontWeight: FontWeight.w600, borderRadius: 12.r, padding: EdgeInsets.symmetric(horizontal: 10.w), height: 40.h, diff --git a/lib/widgets/map/map_utility_screen.dart b/lib/widgets/map/map_utility_screen.dart index 422809cf..a24317e6 100644 --- a/lib/widgets/map/map_utility_screen.dart +++ b/lib/widgets/map/map_utility_screen.dart @@ -48,15 +48,15 @@ class MapUtilityScreen extends StatelessWidget { @override Widget build(BuildContext context) { return Scaffold( - floatingActionButton: Padding( - padding: EdgeInsetsDirectional.only(end: 8.h, bottom: 68.h), - child: DecoratedBox( - decoration: RoundedRectangleBorder().toSmoothCornerDecoration(color: AppColors.whiteColor, borderRadius: 12.h), - child: Utils.buildSvgWithAssets(icon: AppAssets.locate_me, width: 24.h, height: 24.h).paddingAll(12.h).onPress(() { - context.read().moveToCurrentLocation(); - }), - ), - ), + // floatingActionButton: Padding( + // padding: EdgeInsetsDirectional.only(end: 8.h, bottom: 68.h), + // child: DecoratedBox( + // decoration: RoundedRectangleBorder().toSmoothCornerDecoration(color: AppColors.whiteColor, borderRadius: 12.h), + // child: Utils.buildSvgWithAssets(icon: AppAssets.locate_me, width: 24.h, height: 24.h).paddingAll(12.h).onPress(() { + // context.read().moveToCurrentLocation(); + // }), + // ), + // ), bottomSheet: FixedBottomSheet(context), body: Stack( children: [ @@ -68,6 +68,7 @@ class MapUtilityScreen extends StatelessWidget { myLocationEnabled: true, inputController: context.read().gmsController, showCenterMarker: true, + bottomPaddingHeight: 300.h, ) else HMSMap( @@ -129,7 +130,7 @@ class MapUtilityScreen extends StatelessWidget { color: AppColors.textColor, ), subTitleString.toText12( - fontWeight: FontWeight.w500, + isBold: true, color: AppColors.greyTextColor, ) ], diff --git a/lib/widgets/medication_reminder/reminder_timer_dialog.dart b/lib/widgets/medication_reminder/reminder_timer_dialog.dart index 62dbd963..221e2b70 100644 --- a/lib/widgets/medication_reminder/reminder_timer_dialog.dart +++ b/lib/widgets/medication_reminder/reminder_timer_dialog.dart @@ -48,7 +48,7 @@ // LocaleKeys.cancel.tr(), // style: TextStyle( // color: AppColors.errorColor, -// fontWeight: FontWeight.w500, +// isBold: true, // fontSize: 14 // ), // ), @@ -72,7 +72,7 @@ // label: Text( // LocaleKeys.setReminder.tr(), // style: TextStyle( -// fontWeight: FontWeight.w500, +// isBold: true, // fontSize: 14 // ), // ), diff --git a/lib/widgets/nfc/nfc_reader_sheet.dart b/lib/widgets/nfc/nfc_reader_sheet.dart index 956f758b..0d6c2866 100644 --- a/lib/widgets/nfc/nfc_reader_sheet.dart +++ b/lib/widgets/nfc/nfc_reader_sheet.dart @@ -126,7 +126,7 @@ class _NfcLayoutState extends State { borderColor: AppColors.primaryRedColor, textColor: AppColors.whiteColor, fontSize: 14.f, - fontWeight: FontWeight.w500, + isBold: true, borderRadius: 12.h, height: 40.h, icon: AppAssets.cancel, @@ -194,7 +194,7 @@ class _NfcLayoutState extends State { borderColor: AppColors.primaryRedColor, textColor: AppColors.whiteColor, fontSize: 14, - fontWeight: FontWeight.w500, + isBold: true, borderRadius: 12.h, height: 40.h, icon: AppAssets.cancel, diff --git a/lib/widgets/radio/custom_radio_button.dart b/lib/widgets/radio/custom_radio_button.dart index a5301020..540aab46 100644 --- a/lib/widgets/radio/custom_radio_button.dart +++ b/lib/widgets/radio/custom_radio_button.dart @@ -38,7 +38,7 @@ class CustomRadioOption extends StatelessWidget { border: Border.all(color: isSelected ? AppColors.primaryRedColor : AppColors.bottomNAVBorder, width: 2.h)), ), SizedBox(width: 8.h), - text.toText16(weight: FontWeight.w500), // The provided content + text.toText16(isBold: true), // The provided content ], ), ), diff --git a/lib/widgets/radio_list_tile_widget.dart b/lib/widgets/radio_list_tile_widget.dart index 9025e190..afcebdf9 100644 --- a/lib/widgets/radio_list_tile_widget.dart +++ b/lib/widgets/radio_list_tile_widget.dart @@ -68,7 +68,7 @@ class RadioListTileWidget extends StatelessWidget { child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ - title.toText15(weight: FontWeight.w500, letterSpacing: -0.64), + title.toText15(isBold: true, letterSpacing: -0.64), if (subtitleWidget != null) ...[subtitleWidget!], if (subtitle != null) ...[ SizedBox(height: 4.h), diff --git a/lib/widgets/time_picker_widget.dart b/lib/widgets/time_picker_widget.dart index bc996de6..fd5cb409 100644 --- a/lib/widgets/time_picker_widget.dart +++ b/lib/widgets/time_picker_widget.dart @@ -201,7 +201,7 @@ class _TimePickerBottomSheetState extends State<_TimePickerBottomSheet> { hour.toString().padLeft(2, '0'), style: TextStyle( fontSize: 24.f, - fontWeight: FontWeight.w500, + fontWeight: FontWeight.w600, color: AppColors.textColor, ), ), @@ -216,7 +216,7 @@ class _TimePickerBottomSheetState extends State<_TimePickerBottomSheet> { ':', style: TextStyle( fontSize: 24.f, - fontWeight: FontWeight.w500, + fontWeight: FontWeight.w600, color: AppColors.textColor, ), ), @@ -241,7 +241,7 @@ class _TimePickerBottomSheetState extends State<_TimePickerBottomSheet> { index.toString().padLeft(2, '0'), style: TextStyle( fontSize: 24.f, - fontWeight: FontWeight.w500, + fontWeight: FontWeight.w600, color: AppColors.textColor, ), ), @@ -270,7 +270,7 @@ class _TimePickerBottomSheetState extends State<_TimePickerBottomSheet> { 'AM', style: TextStyle( fontSize: 24.f, - fontWeight: FontWeight.w500, + fontWeight: FontWeight.w600, color: AppColors.textColor, ), ), @@ -280,7 +280,7 @@ class _TimePickerBottomSheetState extends State<_TimePickerBottomSheet> { 'PM', style: TextStyle( fontSize: 24.f, - fontWeight: FontWeight.w500, + fontWeight: FontWeight.w600, color: AppColors.textColor, ), ), diff --git a/lib/widgets/user_avatar_widget.dart b/lib/widgets/user_avatar_widget.dart new file mode 100644 index 00000000..6132401c --- /dev/null +++ b/lib/widgets/user_avatar_widget.dart @@ -0,0 +1,94 @@ +import 'dart:convert'; +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'; + +class UserAvatarWidget extends StatelessWidget { + final double width; + final double height; + final int? gender; + final int? age; + final BoxFit? fit; + final bool isCircular; + final double? borderRadius; + final String? customProfileImageData; // For family members or other users + + const UserAvatarWidget({ + Key? key, + required this.width, + required this.height, + this.gender, + this.age, + this.fit, + this.isCircular = false, + this.borderRadius, + this.customProfileImageData, + }) : super(key: key); + + @override + Widget build(BuildContext context) { + final appState = getIt.get(); + final userGender = gender ?? appState.getAuthenticatedUser()?.gender ?? 1; + final userAge = age ?? appState.getAuthenticatedUser()?.age ?? 0; + + // Use custom profile image data if provided, otherwise use AppState data + final profileImageData = customProfileImageData ?? appState.getProfileImageData; + + + // Determine the default image based on gender and age + final String defaultImage; + if (userGender == 1) { + // Male + defaultImage = userAge < 7 ? AppAssets.babyBoyImg : AppAssets.maleImg; + } else { + // Female + defaultImage = userAge < 7 ? AppAssets.babyGirlImg : AppAssets.femaleImg; + } + + // Calculate border radius - default to 12.r if not specified and not circular + final effectiveBorderRadius = isCircular ? width / 2 : (borderRadius ?? 12.r); + + // Show uploaded profile image if available + if (profileImageData != null && profileImageData.isNotEmpty) { + try { + final bytes = base64Decode(profileImageData); + final imageWidget = Image.memory( + bytes, + width: width, + height: height, + fit: fit ?? BoxFit.cover, + ); + + return ClipRRect( + borderRadius: BorderRadius.circular(effectiveBorderRadius), + child: imageWidget, + ); + } catch (e) { + // If decoding fails, show default image + return ClipRRect( + borderRadius: BorderRadius.circular(effectiveBorderRadius), + child: Image.asset( + defaultImage, + width: width, + height: height, + fit: fit, + ), + ); + } + } + + // Show default image with rounded corners + return ClipRRect( + borderRadius: BorderRadius.circular(effectiveBorderRadius), + child: Image.asset( + defaultImage, + width: width, + height: height, + fit: fit, + ), + ); + } +} + diff --git a/lib/widgets/vital_sing_chart_and_detials.dart b/lib/widgets/vital_sing_chart_and_detials.dart index decca3f7..10805e58 100644 --- a/lib/widgets/vital_sing_chart_and_detials.dart +++ b/lib/widgets/vital_sing_chart_and_detials.dart @@ -77,7 +77,7 @@ // style: TextStyle( // fontSize: 16, // letterSpacing: -0.64, -// fontWeight: FontWeight.w600, +// isBold: true, // ), // ), // Table( diff --git a/lib/widgets/vital_sing_chart_blood_pressure.dart b/lib/widgets/vital_sing_chart_blood_pressure.dart index 8bff71b0..6730414b 100644 --- a/lib/widgets/vital_sing_chart_blood_pressure.dart +++ b/lib/widgets/vital_sing_chart_blood_pressure.dart @@ -72,7 +72,7 @@ // style: TextStyle( // fontSize: 16, // letterSpacing: -0.64, -// fontWeight: FontWeight.w600, +// isBold: true, // ), // ), // Table( diff --git a/package/device_calendar_plus/example/lib/main.dart b/package/device_calendar_plus/example/lib/main.dart index dbe66532..60011b2f 100644 --- a/package/device_calendar_plus/example/lib/main.dart +++ b/package/device_calendar_plus/example/lib/main.dart @@ -519,7 +519,7 @@ class _MyHomePageState extends State { style: const TextStyle( fontSize: 12, color: Colors.grey, - fontWeight: FontWeight.w500, + isBold: true, ), ), const SizedBox(height: 2), @@ -804,7 +804,7 @@ class _MyHomePageState extends State { title: Text( event.title, style: const TextStyle( - fontWeight: FontWeight.w500), + isBold: true), ), subtitle: Column( crossAxisAlignment: CrossAxisAlignment.start, diff --git a/package/flutter_swiper_view/CHANGELOG-ZH.md b/package/flutter_swiper_view/CHANGELOG-ZH.md new file mode 100644 index 00000000..8591f0e2 --- /dev/null +++ b/package/flutter_swiper_view/CHANGELOG-ZH.md @@ -0,0 +1,85 @@ +## [1.1.8] - [2022/05/27] + + * 修复 example 无法运行的问题 + * 修复 SwiperPagination.rect 不显示指示器的问题 + * 支持空安全 + * 支持 Flutter 3 + +## [1.1.7] - [2022/05/27] + + * 测试发布 + + +## [1.1.1] - [2018/09/20] + * 修复自动集成测试错误 + +## [1.1.0] - [2018/09/20] + * 修复index的bug ,见 #11 + * 增加 `autoplayDisableOnInteraction` 选项, 如果设置为true,那么在用户滑动的时候停止自动播放,滑动之后重新自动播放 + + +## [1.0.7] - [2018/09/02] + * 在Swiper dispose的时候不调用Controller的dispose. + +## [1.0.6] - [2018/08/28] + * `TINDER` 和 `STACK` 这两种布局方式实现垂直滚动, #9 + +## [1.0.5] - [2018/08/09] + * viewportFraction<1.0增加fade参数 + +## [1.0.4] - [2018/07/18] + * 修复一些错别字,感谢[csharad](https://github.com/csharad) + +## [1.0.3] - [2018/07/18] + * 根据#5 ,用new来创建对象 + +## [1.0.2] - [2018/07/16] + * 修复 #4 + +## [1.0.1] - [2018/07/11] + * 支持布局方式(CUSTOM),你可以定制你自己的布局 + +## [1.0.0] - [2018/07/08] + * 增加布局方式( DEFAULT,STACK,TINDER ) + * 可以将分页指示器放在容器外面 + +## [0.0.9] - [2018/06/10] + * 增加CI + * 增加测试用例 + +## [0.0.8] - [2018/06/07] + * 新增中文文档 + * 更新依赖包:infinity_page_view 版本到 1.0.0 + +## [0.0.7] - [2018/05/24] + * The default color of pagination is ThemeData.scaffoldBackgroundColor and ThemeData.primaryColor + * The default color of control buttons is ThemeData.primaryColor and ThemeData.disabledColor + * The alignment of pagination is Alignment.bottomCenter by default when scrollDirection== Axis.horizontal, Alignment.centerRight by default when scrollDirection== Axis.vertical + * Change default value of `autoplay` to false + +## [0.0.6] - [2018/05/24] + * Fix index bug + +## [0.0.5] - [2018/05/24] + * Fix zero itemCount bug + +## [0.0.4] - [2018/05/20] + * Update README + +## [0.0.3] - [2018/05/20] + * Update README + * Support none loop mode + * Add more examples + +## [0.0.2] - [2018/05/20] + * Autoplay + * Plugins support + * Examples + +## [0.0.1] - [2018/05/20] + * Infinite loop + * Control buttons + * Pagination + * Custom control buttons + * Custom pagination + * Controler \ No newline at end of file diff --git a/package/flutter_swiper_view/CHANGELOG.md b/package/flutter_swiper_view/CHANGELOG.md new file mode 100644 index 00000000..77c5eb2f --- /dev/null +++ b/package/flutter_swiper_view/CHANGELOG.md @@ -0,0 +1,95 @@ +## [1.1.8] - [2022/05/27] + + * fix example error + * fix SwiperPagination.rect indicator does not display + * support null safety + * support Flutter 3 + +## [1.1.7] - [2022/05/27] + + * test publish to pub.dev + + +## [1.1.5] - [2019/03/10] + + * Fix findRenderObject is null + + +## [1.1.4] - [2018/10/18] + +## [1.1.2] - [2018/10/10] + * Fix #7 + * Support layout for pagination. + +## [1.1.1] - [2018/09/20] + * Fix `test` failure. + +## [1.1.0] - [2018/09/20] + * Fix index bug ,See #11 + * Enable `autoplayDisableOnInteraction` property, if set `true`,disable autoplay when user swipes. + +## [1.0.7] - [2018/09/02] + * Don't call SwiperController's dispose when `Swiper` dispose. + +## [1.0.6] - [2018/08/28] + * Implement vertical scroll direction for `TINDER` and `STACK` layout, see #9 + +## [1.0.5] - [2018/08/09] + * Add feature : support fade for `viewportFraction` + +## [1.0.4] - [2018/07/18] + * Fix some typo,thanks to [csharad](https://github.com/csharad) + +## [1.0.3] - [2018/07/18] + * Use new to create everything. See #5 + +## [1.0.2] - [2018/07/16] + * Fix #4 + +## [1.0.1] - [2018/07/11] + * Add layout (CUSTOM) so that you can create your own layout + +## [1.0.0] - [2018/07/08] + * Add layouts ( DEFAULT,STACK,TINDER ) + * Allow to put pagination outer of the swiper container. + +## [0.0.9] - [2018/06/10] + * Add ci + * Add testcase + +## [0.0.8] - [2018/06/07] + * And chinese document + * Update infinity_page_view version to 1.0.0 + +## [0.0.7] - [2018/05/24] + * The default color of pagination is ThemeData.scaffoldBackgroundColor and ThemeData.primaryColor + * The default color of control buttons is ThemeData.primaryColor and ThemeData.disabledColor + * The alignment of pagination is Alignment.bottomCenter by default when scrollDirection== Axis.horizontal, Alignment.centerRight by default when scrollDirection== Axis.vertical + * Change default value of `autoplay` to false + +## [0.0.6] - [2018/05/24] + * Fix index bug + +## [0.0.5] - [2018/05/24] + * Fix zero itemCount bug + +## [0.0.4] - [2018/05/20] + * Update README + +## [0.0.3] - [2018/05/20] + * Update README + * Support none loop mode + * Add more examples + +## [0.0.2] - [2018/05/20] + * Autoplay + * Plugins support + * Examples + +## [0.0.1] - [2018/05/20] + * Infinite loop + * Control buttons + * Pagination + * Custom control buttons + * Custom pagination + * Controler \ No newline at end of file diff --git a/package/flutter_swiper_view/LICENSE b/package/flutter_swiper_view/LICENSE new file mode 100644 index 00000000..ac160e8a --- /dev/null +++ b/package/flutter_swiper_view/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2018 Xueliang Ren + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/package/flutter_swiper_view/README-ZH.md b/package/flutter_swiper_view/README-ZH.md new file mode 100644 index 00000000..cb2ff3c0 --- /dev/null +++ b/package/flutter_swiper_view/README-ZH.md @@ -0,0 +1,409 @@ +![Logo](banner.jpg) + +

+ + Build Status + + + Coverage Status + + + PRs Welcome + + + pub package + +

+

+ + 英文说明 + +

+ + +# flutter_swiper_view + +flutter 最强大的 swiper, 多种布局方式,无限轮播,Android 和 iOS 双端适配. + + +# :sparkles::sparkles: New Features: 视差 + +我们在 Swiper 中也像 Android 一样支持了 `PageTransformer`, 只要给 Swiper 设置一下 `transformer` 属性就行, +这里返回一个被转换的组件给 Swiper. 目前仅仅支持 `DEFAULT` 布局. +感谢 @FlutterRocks ,棒棒哒 👏. + + + + +# :sparkles::sparkles: 新功能 + + +![](https://github.com/jzoom/images/raw/master/layout1.gif) + +![](https://github.com/jzoom/images/raw/master/layout2.gif) + +![](https://github.com/jzoom/images/raw/master/layout3.gif) + +[更多](#内建的布局) + + +# 截图 + +![Horizontal](https://github.com/feicien/flutter_swiper_view/raw/master/example/res/1.gif) + +![Vertical](https://github.com/feicien/flutter_swiper_view/raw/master/example/res/2.gif) + +![Custom Pagination](https://github.com/feicien/flutter_swiper_view/raw/master/example/res/3.gif) + +![Custom Pagination](https://github.com/feicien/flutter_swiper_view/raw/master/example/res/4.gif) + +![Phone](https://github.com/feicien/flutter_swiper_view/raw/master/example/res/5.gif) + +![Example](https://github.com/jzoom/images/raw/master/swiper-example.gif) + +[更多](#代码) + +## 功能路线 + +1.x.x 功能实现: + +- [x] 无限循环轮播 +- [x] 控制按钮 +- [x] 分页指示器 +- [x] 非无限循环模式 +- [x] 单元测试 +- [x] 例子 +- [x] 滚动方向 +- [x] 可定制控制按钮 +- [x] 可定制分页 +- [x] 自动播放 +- [x] 控制器 +- [x] 外部分页指示器 +- [ ] 更多布局方式 + + +## 更新日志 + +>参考:[CHANGELOG.md](https://github.com/feicien/flutter_swiper_view/blob/master/CHANGELOG-ZH.md) + +## 目录 + +- [安装](#安装) +- [基本使用](#基本使用) +- [构建](#构建) + + [基本构造函数](#基本构造函数) + + [分页指示器](#分页指示器) + + [控制按钮](#控制按钮) + + [控制器](#控制器) + + [自动播放](#自动播放) ++ [内建的布局](#内建的布局) ++ [一些常用代码示例](#代码) + +### 安装 + +增加 + +```dart +dependencies: + flutter_swiper_view: ^1.1.8 +``` +到项目根目录下的 pubspec.yaml ,并且根目录运行命令行 + +```dart +flutter pub get +``` + + +### 基本使用 + +使用命令行创建一个新项目: + +```dart +flutter create myapp +``` + +编辑 lib/main.dart: + +```dart +import 'package:flutter/material.dart'; + +import 'package:flutter_swiper_view/flutter_swiper_view.dart'; + +void main() => runApp(const MyApp()); + +class MyApp extends StatelessWidget { + const MyApp({Key? key}) : super(key: key); + + @override + Widget build(BuildContext context) { + return MaterialApp( + title: 'Flutter Demo', + theme: ThemeData( + primarySwatch: Colors.blue, + ), + home: const MyHomePage(title: 'Flutter Demo Home Page'), + ); + } +} + +class MyHomePage extends StatefulWidget { + const MyHomePage({Key? key, required this.title}) : super(key: key); + + final String title; + + @override + State createState() => _MyHomePageState(); +} + +class _MyHomePageState extends State { + + @override + Widget build(BuildContext context) { + return Scaffold( + appBar: AppBar( + title: Text(widget.title), + ), + body: Swiper( + itemBuilder: (context, index){ + return Image.network("https://via.placeholder.com/350x150",fit: BoxFit.fill,); + }, + itemCount: 3, + pagination: const SwiperPagination(), + control: const SwiperControl(), + ), + ); + } +} +``` + + + +### 构建 + + +#### 基本参数 + +| 参数 | 默认值 | 描述 | +| :-------------- |:-----------------:| :------------------------| +| scrollDirection | Axis.horizontal |滚动方向,设置为 Axis.vertical 如果需要垂直滚动 | +| loop | true |无限轮播模式开关 | +| index | 0 |初始的时候下标位置 | +| autoplay | false |自动播放开关. | +| onIndexChanged | void onIndexChanged(int index) | 当用户手动拖拽或者自动播放引起下标改变的时候调用 | +| onTap | void onTap(int index) | 当用户点击某个轮播的时候调用 | +| duration | 300.0 | 动画时间,单位是毫秒 | +| pagination | null | 设置 `SwiperPagination()` 展示默认分页指示器 +| control | null | 设置 `SwiperControl()` 展示默认分页按钮 + + +#### 分页指示器 + +分页指示器继承自 `SwiperPlugin`,`SwiperPlugin` 为 `Swiper` 提供额外的界面.设置为`SwiperPagination()` 展示默认分页. + + +| 参数 | 默认值 | 描述 | +| :------------ |:---------------:| :-----| +| alignment | Alignment.bottomCenter | 如果要将分页指示器放到其他位置,那么可以修改这个参数 | +| margin | const EdgeInsets.all(10.0) | 分页指示器与容器边框的距离 | +| builder | SwiperPagination.dots | 目前已经定义了三个默认的分页指示器样式: `SwiperPagination.dots` 、 `SwiperPagination.fraction`、 `SwiperPagination.rect`,都可以做进一步的自定义. | + +如果需要定制自己的分页指示器,那么可以这样写: + +```dart +Swiper( + ..., + pagination: SwiperCustomPagination( + builder: (context, config){ + return YourOwnPaginatipon(); + }, + ) +); +``` + + + +#### 控制按钮 + +控制按钮组件也是继承自 `SwiperPlugin`,设置 `SwiperControl()` 展示默认控制按钮. + + +| 参数 | 默认值 | 描述 | +| :------------ |:---------------:| :-----| +| iconPrevious | Icons.arrow_back_ios | 上一页的IconData | +| iconNext | Icons.arrow_forward_ios | 下一页的IconData | +| color | Theme.of(context).primaryColor | 控制按钮颜色 | +| size | 30.0 | 控制按钮的大小 | +| padding | const EdgeInsets.all(5.0) | 控制按钮与容器的距离 | + + +#### 控制器(SwiperController) + +`SwiperController` 用于控制 Swiper的`index`属性, 停止和开始自动播放. 通过 `SwiperController()` 创建一个 SwiperController 实例,并保存,以便将来能使用。 + + +| 方法 | 描述 | +| :------------ |:-----| +| void move(int index, {bool animation: true}) | 移动到指定下标,设置是否播放动画| +| void next({bool animation: true}) | 下一页 | +| void previous({bool animation: true}) | 上一页 | +| void startAutoplay() | 开始自动播放 | +| void stopAutoplay() | 停止自动播放 | + + + +#### 自动播放 + +| 参数 | 默认值 | 描述 | +| :------------ |:---------------:| :-----| +| autoplayDely | 3000 | 自动播放延迟毫秒数. | +| autoplayDisableOnInteraction | true | 当用户拖拽的时候,是否停止自动播放. | + + + +## 内建的布局 +![](https://github.com/jzoom/images/raw/master/layout1.gif) + +```dart +Swiper( + itemBuilder: (context, index) { + return Image.network( + "https://via.placeholder.com/288x188", + fit: BoxFit.fill, + ); + }, + itemCount: 10, + viewportFraction: 0.8, + scale: 0.9, +) + +``` + + + +![](https://github.com/jzoom/images/raw/master/layout2.gif) + +```dart +Swiper( + itemBuilder: (context, index) { + return Image.network( + "https://via.placeholder.com/288x188", + fit: BoxFit.fill, + ); + }, + itemCount: 10, + itemWidth: 300.0, + layout: SwiperLayout.STACK, +) +``` + +![](https://github.com/jzoom/images/raw/master/layout3.gif) + +```dart +Swiper( + itemBuilder: (context, index) { + return Image.network( + "https://via.placeholder.com/288x188", + fit: BoxFit.fill, + ); + }, + itemCount: 10, + itemWidth: 300.0, + itemHeight: 400.0, + layout: SwiperLayout.TINDER, +) +``` + + + +![](https://github.com/jzoom/images/raw/master/layout4.gif) + +构建你自己的动画十分简单: + +```dart +Swiper( + layout: SwiperLayout.CUSTOM, + customLayoutOption: CustomLayoutOption(startIndex: -1, stateCount: 3) + ..addRotate([-45.0 / 180, 0.0, 45.0 / 180]) + ..addTranslate([ + const Offset(-370.0, -40.0), + const Offset(0.0, 0.0), + const Offset(370.0, -40.0) + ]), + itemWidth: 300.0, + itemHeight: 200.0, + itemBuilder: (context, index) { + return Container( + color: Colors.grey, + child: Center( + child: Text("$index"), + ), + ); + }, + itemCount: 10, +), +``` + + +`CustomLayoutOption` 被设计用来描述布局和动画,很简单的可以指定每一个元素的状态. + +```dart +CustomLayoutOption( + startIndex: -1, /// 开始下标 + stateCount: 3 /// 下面的数组长度 + )..addRotate([ // 每个元素的角度 + -45.0/180, + 0.0, + 45.0/180 + ])..addTranslate([ /// 每个元素的偏移 + const Offset(-370.0, -40.0), + const Offset(0.0, 0.0), + const Offset(370.0, -40.0) + ]) +``` + +## 代码 + + +![Example](https://github.com/jzoom/images/raw/master/swiper-example.gif) + + +```dart +ConstrainedBox( + constraints: BoxConstraints.loose(Size(screenWidth, 170.0)) + child: Swiper( + outer: false, + itemBuilder: (c, i) { + return Wrap( + runSpacing: 6.0, + children: [0,1,2,3,4,5,6,7,8,9].map((i){ + return SizedBox( + width: MediaQuery.of(context).size.width/5, + child: Column( + mainAxisSize: MainAxisSize.min, + children: [ + SizedBox( + height: MediaQuery.of(context).size.width * 0.12, + width: MediaQuery.of(context).size.width * 0.12, + child: Image.network("https://fuss10.elemecdn.com/c/db/d20d49e5029281b9b73db1c5ec6f9jpeg.jpeg%3FimageMogr/format/webp/thumbnail/!90x90r/gravity/Center/crop/90x90"), + ), + Padding( + padding: const EdgeInsets.only(top: 6.0), + child: Text("$i"), + ) + ], + ), + ); + }).toList(), + ); + }, + pagination: const SwiperPagination(margin: EdgeInsets.all(5.0)), + itemCount: 10, + ), +), +``` + + + +这里可以找到所有的定制选项 + +>https://github.com/feicien/flutter_swiper_view/blob/master/example/lib/src/example_custom.dart \ No newline at end of file diff --git a/package/flutter_swiper_view/README.md b/package/flutter_swiper_view/README.md new file mode 100644 index 00000000..3365bd8c --- /dev/null +++ b/package/flutter_swiper_view/README.md @@ -0,0 +1,393 @@ +![Logo](banner.jpg) + +

+ + Build Status + + + Coverage Status + + + PRs Welcome + + + pub package + +

+

+ + 中文说明 + +

+ + + +# flutter_swiper_view + +The best swiper for flutter , with multiple layouts, infinite loop. Compatible with Android & iOS. + + + +# :sparkles::sparkles: New Features:PageTransformer + +Finally, we have `PageTransformer` like android, just set a `transformer` to `Swiper`, +it returns a widget that has been transformed. For now, only support for layout `DEFAULT`. +Thanks to @FlutterRocks ,you've done great job 👏. + + + +# :sparkles::sparkles: New Features:Layout + + +![](https://github.com/jzoom/images/raw/master/layout1.gif) + +![](https://github.com/jzoom/images/raw/master/layout2.gif) + +![](https://github.com/jzoom/images/raw/master/layout3.gif) + +[See More](#build-in-layouts) + + +# Showcases + +![Horizontal](https://github.com/feicien/flutter_swiper_view/raw/master/example/res/1.gif) + +![Vertical](https://github.com/feicien/flutter_swiper_view/raw/master/example/res/2.gif) + +![Custom Pagination](https://github.com/feicien/flutter_swiper_view/raw/master/example/res/3.gif) + +![Custom Pagination](https://github.com/feicien/flutter_swiper_view/raw/master/example/res/4.gif) + +![Phone](https://github.com/feicien/flutter_swiper_view/raw/master/example/res/5.gif) + +![Example](https://github.com/jzoom/images/raw/master/swiper-example.gif) + +[See More](#codes) + + +## Changelogs + +>see:[CHANGELOG.md](https://github.com/feicien/flutter_swiper_view/blob/master/CHANGELOG.md) + +## Getting Started + +- [Installation](#installation) +- [Basic Usage](#basic-usage) +- [Constructor](#constructor) + + [Basic](#basic) + + [Pagination](#pagination) + + [Control buttons](#control-buttons) + + [Controller](#controller) + + [Autoplay](#autoplay) +- [Build in layouts](#build-in-layouts) +- [Codes](#codes) + +### Installation + +Add + +```dart +dependencies: + flutter_swiper_view: ^1.1.8 +``` +to your pubspec.yaml ,and run + +```dart +flutter pub get +``` +in your project's root directory. + +### Basic Usage + +Create a new project with command + +```dart +flutter create myapp +``` + +Edit lib/main.dart like this: + +```dart +import 'package:flutter/material.dart'; + +import 'package:flutter_swiper_view/flutter_swiper_view.dart'; + +void main() => runApp(const MyApp()); + +class MyApp extends StatelessWidget { + const MyApp({Key? key}) : super(key: key); + + @override + Widget build(BuildContext context) { + return MaterialApp( + title: 'Flutter Demo', + theme: ThemeData( + primarySwatch: Colors.blue, + ), + home: const MyHomePage(title: 'Flutter Demo Home Page'), + ); + } +} + +class MyHomePage extends StatefulWidget { + const MyHomePage({Key? key, required this.title}) : super(key: key); + + final String title; + + @override + State createState() => _MyHomePageState(); +} + +class _MyHomePageState extends State { + + @override + Widget build(BuildContext context) { + return Scaffold( + appBar: AppBar( + title: Text(widget.title), + ), + body: Swiper( + itemBuilder: (context, index){ + return Image.network("https://via.placeholder.com/350x150",fit: BoxFit.fill,); + }, + itemCount: 3, + pagination: const SwiperPagination(), + control: const SwiperControl(), + ), + ); + } +} +``` + + + +### Constructor + + +#### Basic + +| Parameter | Default | Description | +| :------------ |:---------------:| :-----| +| scrollDirection | Axis.horizontal | If `Axis.horizontal`, the scroll view's children are arranged horizontally in a row instead of vertically in a column. | +| loop | true |Set to `false` to disable continuous loop mode. | +| index | 0 | Index number of initial slide. | +| autoplay | false |Set to `true` enable auto play mode. | +| onIndexChanged | void onIndexChanged(int index) | Called with the new index when the user swiped or autoplay | +| onTap | void onTap(int index) | Called when user tap ui. | +| duration | 300.0 | The milliscends of every transaction animation costs | +| pagination | null | set `SwiperPagination()` to show default pagination +| control | null | set `SwiperControl()` to show default control buttons + + +#### Pagination + +The pagination extends from `SwiperPlugin`,the `SwiperPlugin` provides extra ui for `Swiper`.Set `SwiperPagination()` to show default pagination. + + +| Parameter | Default | Description | +| :------------ |:---------------:| :-----| +| alignment | Alignment.bottomCenter | Change this value if you what to put pagination in other place | +| margin | const EdgeInsets.all(10.0) | The distance between inner side of the parent container. | +| builder | SwiperPagination.dots | There are three default styles `SwiperPagination.dots` and `SwiperPagination.fraction`、 `SwiperPagination.rect`,both can be customized. | + +If you'd like to customize your own pagination, you can do like this: + +```dart +Swiper( + ..., + pagination: SwiperCustomPagination( + builder: (context, config){ + return YourOwnPaginatipon(); + }, + ) +); +``` + + + +#### Control buttons + +The control also extends from `SwiperPlugin`,set `SwiperControl()` to show default control buttons. + + +| Parameter | Default | Description | +| :------------ |:---------------:| :-----| +| iconPrevious | Icons.arrow_back_ios | The icon data to display `previous` control button | +| iconNext | Icons.arrow_forward_ios | The icon data to display `next`. | +| color | Theme.of(context).primaryColor | Control button color | +| size | 30.0 | Control button size | +| padding | const EdgeInsets.all(5.0) | Control button padding | + + +#### Controller + +The `Controller` is used to control the `index` of the Swiper, start or stop autoplay.You can create a controller by `SwiperController()` and save the instance by futher usage. + + +| Method | Description | +| :------------ |:-----| +| void move(int index, {bool animation: true}) | Move to the spicified `index`,with animation or not | +| void next({bool animation: true}) | Move to next | +| void previous({bool animation: true}) | Move to previous | +| void startAutoplay() | Start autoplay | +| void stopAutoplay() | Stop autoplay | + + + +#### Autoplay + +| Parameter | Default | Description | +| :------------ |:---------------:| :-----| +| autoplayDelay | 3000 | Autoplay delay milliseconds. | +| autoplayDisableOnInteraction | true | If set true, `autoplay` is disabled when use swipes. | + +## Build in layouts +![](https://github.com/jzoom/images/raw/master/layout1.gif) + +```dart +Swiper( + itemBuilder: (context, index) { + return Image.network( + "https://via.placeholder.com/288x188", + fit: BoxFit.fill, + ); + }, + itemCount: 10, + viewportFraction: 0.8, + scale: 0.9, +) +``` + + + +![](https://github.com/jzoom/images/raw/master/layout2.gif) + +```dart +Swiper( + itemBuilder: (context, index) { + return Image.network( + "https://via.placeholder.com/288x188", + fit: BoxFit.fill, + ); + }, + itemCount: 10, + itemWidth: 300.0, + layout: SwiperLayout.STACK, +) +``` + +![](https://github.com/jzoom/images/raw/master/layout3.gif) + +```dart +Swiper( + itemBuilder: (context, index) { + return Image.network( + "https://via.placeholder.com/288x188", + fit: BoxFit.fill, + ); + }, + itemCount: 10, + itemWidth: 300.0, + itemHeight: 400.0, + layout: SwiperLayout.TINDER, +) +``` + + +![](https://github.com/jzoom/images/raw/master/layout4.gif) + +Very easy to create you own custom animation: + +```dart +Swiper( + layout: SwiperLayout.CUSTOM, + customLayoutOption: CustomLayoutOption(startIndex: -1, stateCount: 3) + ..addRotate([-45.0 / 180, 0.0, 45.0 / 180]) + ..addTranslate([ + const Offset(-370.0, -40.0), + const Offset(0.0, 0.0), + const Offset(370.0, -40.0) + ]), + itemWidth: 300.0, + itemHeight: 200.0, + itemBuilder: (context, index) { + return Container( + color: Colors.grey, + child: Center( + child: Text("$index"), + ), + ); + }, + itemCount: 10, +), +``` + +The `CustomLayoutOption` is designed to describe animations. +It is very easy to specify every state of items in Swiper. + +```dart +CustomLayoutOption( + startIndex: -1, /// Which index is the first item of array below + stateCount: 3 /// array length + )..addRotate([ // rotation of every item + -45.0/180, + 0.0, + 45.0/180 + ])..addTranslate([ /// offset of every item + const Offset(-370.0, -40.0), + const Offset(0.0, 0.0), + const Offset(370.0, -40.0) + ]) +``` + +## Codes + +![Example](https://github.com/jzoom/images/raw/master/swiper-example.gif) + +```dart +ConstrainedBox( + constraints: BoxConstraints.loose(Size(screenWidth, 170.0)) + child: Swiper( + outer: false, + itemBuilder: (c, i) { + return Wrap( + runSpacing: 6.0, + children: [0,1,2,3,4,5,6,7,8,9].map((i){ + return SizedBox( + width: MediaQuery.of(context).size.width/5, + child: Column( + mainAxisSize: MainAxisSize.min, + children: [ + SizedBox( + height: MediaQuery.of(context).size.width * 0.12, + width: MediaQuery.of(context).size.width * 0.12, + child: Image.network("https://fuss10.elemecdn.com/c/db/d20d49e5029281b9b73db1c5ec6f9jpeg.jpeg%3FimageMogr/format/webp/thumbnail/!90x90r/gravity/Center/crop/90x90"), + ), + Padding( + padding: const EdgeInsets.only(top: 6.0), + child: Text("$i"), + ) + ], + ), + ); + }).toList(), + ); + }, + pagination: const SwiperPagination(margin: EdgeInsets.all(5.0)), + itemCount: 10, + ), +), +``` + + + + + +You can find all custom options here: + +>https://github.com/feicien/flutter_swiper_view/blob/master/example/lib/src/example_custom.dart + + + + + diff --git a/package/flutter_swiper_view/analysis_options.yaml b/package/flutter_swiper_view/analysis_options.yaml new file mode 100644 index 00000000..61b6c4de --- /dev/null +++ b/package/flutter_swiper_view/analysis_options.yaml @@ -0,0 +1,29 @@ +# This file configures the analyzer, which statically analyzes Dart code to +# check for errors, warnings, and lints. +# +# The issues identified by the analyzer are surfaced in the UI of Dart-enabled +# IDEs (https://dart.dev/tools#ides-and-editors). The analyzer can also be +# invoked from the command line by running `flutter analyze`. + +# The following line activates a set of recommended lints for Flutter apps, +# packages, and plugins designed to encourage good coding practices. +include: package:flutter_lints/flutter.yaml + +linter: + # The lint rules applied to this project can be customized in the + # section below to disable rules from the `package:flutter_lints/flutter.yaml` + # included above or to enable additional rules. A list of all available lints + # and their documentation is published at + # https://dart-lang.github.io/linter/lints/index.html. + # + # Instead of disabling a lint rule for the entire project in the + # section below, it can also be suppressed for a single line of code + # or a specific dart file by using the `// ignore: name_of_lint` and + # `// ignore_for_file: name_of_lint` syntax on the line or in the file + # producing the lint. + rules: + # avoid_print: false # Uncomment to disable the `avoid_print` rule + # prefer_single_quotes: true # Uncomment to enable the `prefer_single_quotes` rule + +# Additional information about this file can be found at +# https://dart.dev/guides/language/analysis-options diff --git a/package/flutter_swiper_view/banner.jpg b/package/flutter_swiper_view/banner.jpg new file mode 100644 index 00000000..73c4eb89 Binary files /dev/null and b/package/flutter_swiper_view/banner.jpg differ diff --git a/package/flutter_swiper_view/dev/bots/travis_install.sh b/package/flutter_swiper_view/dev/bots/travis_install.sh new file mode 100755 index 00000000..a834cb95 --- /dev/null +++ b/package/flutter_swiper_view/dev/bots/travis_install.sh @@ -0,0 +1,18 @@ +#!/bin/bash + +echo "$PWD" +export ROOT="$PWD" + +mkdir ~/development + +cd ~/development +wget https://storage.googleapis.com/flutter_infra/releases/beta/linux/flutter_linux_v0.4.4-beta.tar.xz +tar xf ~/development/flutter_linux_v0.4.4-beta.tar.xz + +export PATH=~/development/flutter/bin:$PATH + + +cd $ROOT +flutter packages get + +gem install coveralls-lcov diff --git a/package/flutter_swiper_view/dev/bots/travis_script.sh b/package/flutter_swiper_view/dev/bots/travis_script.sh new file mode 100755 index 00000000..8eea42df --- /dev/null +++ b/package/flutter_swiper_view/dev/bots/travis_script.sh @@ -0,0 +1,17 @@ +#!/bin/bash +set -ex + +export PATH=~/development/flutter/bin:$PATH +export ROOT="$PWD" + +if [[ "$SHARD" == "dartfmt" ]]; then + echo 'Formating code' + cd $ROOT + flutter format . || exit $? +else + # tests shard + cd $ROOT + + flutter test --coverage test/* || exit $? + +fi diff --git a/package/flutter_swiper_view/example/README.md b/package/flutter_swiper_view/example/README.md new file mode 100644 index 00000000..64a12f6b --- /dev/null +++ b/package/flutter_swiper_view/example/README.md @@ -0,0 +1,8 @@ +# example + +A new Flutter project. + +## Getting Started + +For help getting started with Flutter, view our online +[documentation](https://flutter.io/). diff --git a/package/flutter_swiper_view/example/images/1.png b/package/flutter_swiper_view/example/images/1.png new file mode 100755 index 00000000..9b87ff2a Binary files /dev/null and b/package/flutter_swiper_view/example/images/1.png differ diff --git a/package/flutter_swiper_view/example/images/2.png b/package/flutter_swiper_view/example/images/2.png new file mode 100755 index 00000000..d08e334d Binary files /dev/null and b/package/flutter_swiper_view/example/images/2.png differ diff --git a/package/flutter_swiper_view/example/images/3.png b/package/flutter_swiper_view/example/images/3.png new file mode 100755 index 00000000..2ad5a781 Binary files /dev/null and b/package/flutter_swiper_view/example/images/3.png differ diff --git a/package/flutter_swiper_view/example/images/bg.jpeg b/package/flutter_swiper_view/example/images/bg.jpeg new file mode 100644 index 00000000..76ee2df0 Binary files /dev/null and b/package/flutter_swiper_view/example/images/bg.jpeg differ diff --git a/package/flutter_swiper_view/example/images/bg0.jpeg b/package/flutter_swiper_view/example/images/bg0.jpeg new file mode 100644 index 00000000..bf3b7436 Binary files /dev/null and b/package/flutter_swiper_view/example/images/bg0.jpeg differ diff --git a/package/flutter_swiper_view/example/images/bg1.jpeg b/package/flutter_swiper_view/example/images/bg1.jpeg new file mode 100644 index 00000000..2c4f2d64 Binary files /dev/null and b/package/flutter_swiper_view/example/images/bg1.jpeg differ diff --git a/package/flutter_swiper_view/example/images/bg2.jpeg b/package/flutter_swiper_view/example/images/bg2.jpeg new file mode 100644 index 00000000..b560cc57 Binary files /dev/null and b/package/flutter_swiper_view/example/images/bg2.jpeg differ diff --git a/package/flutter_swiper_view/example/lib/inner_swiper.dart b/package/flutter_swiper_view/example/lib/inner_swiper.dart new file mode 100644 index 00000000..c7087387 --- /dev/null +++ b/package/flutter_swiper_view/example/lib/inner_swiper.dart @@ -0,0 +1,90 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_swiper_view/flutter_swiper_view.dart'; + +void main() => runApp(const MyApp()); + +class MyApp extends StatelessWidget { + const MyApp({Key? key}) : super(key: key); + + @override + Widget build(BuildContext context) { + return const MaterialApp( + home: InnerSwiper(), + ); + } +} + +class InnerSwiper extends StatefulWidget { + const InnerSwiper({Key? key}) : super(key: key); + + @override + State createState() { + return _InnerSwiperState(); + } +} + +class _InnerSwiperState extends State { + late SwiperController controller; + + late List autoPlayer; + + late List controllers; + + @override + void initState() { + controller = SwiperController(); + autoPlayer = List.generate(10, (index) => false); + controllers = List.generate(10, (index) => SwiperController()); + super.initState(); + } + + @override + Widget build(BuildContext context) { + return Scaffold( + body: Swiper( + loop: false, + itemCount: 10, + controller: controller, + pagination: const SwiperPagination(), + itemBuilder: (context, index) { + return Column( + children: [ + SizedBox( + height: 300.0, + child: Swiper( + controller: controllers[index], + pagination: const SwiperPagination(), + itemCount: 4, + itemBuilder: (context, index) { + return Container( + color: Colors.greenAccent, + child: const Text("jkfjkldsfjd"), + ); + }, + autoplay: autoPlayer[index], + ), + ), + ElevatedButton( + onPressed: () { + setState(() { + autoPlayer[index] = true; + }); + }, + child: const Text("Start autoplay"), + ), + ElevatedButton( + onPressed: () { + setState(() { + autoPlayer[index] = false; + }); + }, + child: const Text("End autoplay"), + ), + Text("is autoplay: ${autoPlayer[index]}") + ], + ); + }, + ), + ); + } +} diff --git a/package/flutter_swiper_view/example/lib/listener_test.dart b/package/flutter_swiper_view/example/lib/listener_test.dart new file mode 100644 index 00000000..f2dc2136 --- /dev/null +++ b/package/flutter_swiper_view/example/lib/listener_test.dart @@ -0,0 +1,45 @@ +import 'package:flutter/material.dart'; + +import 'package:flutter_swiper_view/flutter_swiper_view.dart'; + +void main() => runApp(const MyApp()); + +class MyApp extends StatelessWidget { + const MyApp({Key? key}) : super(key: key); + + // This widget is the root of your application. + @override + Widget build(BuildContext context) { + return MaterialApp( + title: 'Flutter Demo', + theme: ThemeData( + primarySwatch: Colors.blue, + ), + home: const MyHomePage(title: 'Flutter Swiper'), + ); + } +} + +class MyHomePage extends StatefulWidget { + const MyHomePage({Key? key, required this.title}) : super(key: key); + + final String title; + + @override + State createState() => _MyHomePageState(); +} + +class _MyHomePageState extends State { + @override + Widget build(BuildContext context) { + return Scaffold( + body: Swiper( + itemCount: 10, + itemBuilder: (c, i) { + return Text("$i"); + }, + plugins: const [], + ), + ); + } +} diff --git a/package/flutter_swiper_view/example/lib/main.dart b/package/flutter_swiper_view/example/lib/main.dart new file mode 100644 index 00000000..1b088745 --- /dev/null +++ b/package/flutter_swiper_view/example/lib/main.dart @@ -0,0 +1,409 @@ +import 'package:flutter/gestures.dart'; +import 'package:flutter/material.dart'; + +import 'package:flutter_swiper_view/flutter_swiper_view.dart'; +import 'src/example_custom.dart'; +import 'src/config.dart'; +import 'src/example_swiper_in_scrollview.dart'; + +void main() => runApp(const MyApp()); + +class MyScrollBehavior extends MaterialScrollBehavior { + @override + Set get dragDevices => { + PointerDeviceKind.touch, + PointerDeviceKind.mouse, + }; +} + +class MyApp extends StatelessWidget { + const MyApp({Key? key}) : super(key: key); + + // This widget is the root of your application. + @override + Widget build(BuildContext context) { + return MaterialApp( + scrollBehavior: MyScrollBehavior(), + title: 'Flutter Demo', + theme: ThemeData.light(), + home: const MyHomePage(title: 'Flutter Swiper'), + //home: buildHome(), + routes: { + '/example01': (context) => const ExampleHorizontal(), + '/example02': (context) => const ExampleVertical(), + '/example03': (context) => const ExampleFraction(), + '/example04': (context) => const ExampleCustomPagination(), + '/example05': (context) => const ExamplePhone(), + '/example06': (context) => const ScaffoldWidget( + title: "ScrollView", child: ExampleSwiperInScrollView()), + '/example07': (context) => const ScaffoldWidget( + title: "Custom All", + child: ExampleCustom(), + ), + '/example08': (context) => const ExampleRect(), + }, + ); + } +} + +class MyHomePage extends StatefulWidget { + const MyHomePage({Key? key, required this.title}) : super(key: key); + + final String title; + + @override + State createState() => _MyHomePageState(); +} + +class _MyHomePageState extends State { + List render(BuildContext context, List> children) { + return ListTile.divideTiles( + context: context, + tiles: children.map((data) { + return buildListTile(context, data[0], data[1], data[2]); + }), + ).toList(); + } + + Widget buildListTile( + BuildContext context, String title, String subtitle, String url) { + return ListTile( + onTap: () { + Navigator.of(context).pushNamed(url); + }, + isThreeLine: true, + dense: false, + leading: null, + title: Text(title), + subtitle: Text(subtitle), + trailing: const Icon( + Icons.arrow_right, + color: Colors.blueAccent, + ), + ); + } + + @override + Widget build(BuildContext context) { + // DateTime moonLanding = DateTime.parse("1969-07-20"); + + return Scaffold( + appBar: AppBar( + title: Text(widget.title), + ), + body: ListView( + children: render(context, [ + ["Horizontal", "Scroll Horizontal", "/example01"], + ["Vertical", "Scroll Vertical", "/example02"], + ["Fraction", "Fraction style", "/example03"], + ["Rect", "Rect style", "/example08"], + ["Custom Pagination", "Custom Pagination", "/example04"], + ["Phone", "Phone view", "/example05"], + ["ScrollView ", "In a ScrollView", "/example06"], + ["Custom", "Custom all properties", "/example07"], + ]), + ), + ); + } +} + +const List titles = [ + "Flutter Swiper is awosome", + "Really nice", + "Yeap" +]; + +class ExampleHorizontal extends StatelessWidget { + const ExampleHorizontal({Key? key}) : super(key: key); + + @override + Widget build(BuildContext context) { + return Scaffold( + appBar: AppBar( + title: const Text("ExampleHorizontal"), + ), + body: Swiper( + itemBuilder: (context, index) { + return Image.asset( + images[index], + fit: BoxFit.fill, + ); + }, + indicatorLayout: PageIndicatorLayout.COLOR, + autoplay: true, + itemCount: images.length, + pagination: const SwiperPagination(), + control: const SwiperControl(), + )); + } +} + +class ExampleVertical extends StatelessWidget { + const ExampleVertical({Key? key}) : super(key: key); + + @override + Widget build(BuildContext context) { + return Scaffold( + appBar: AppBar( + title: const Text("ExampleVertical"), + ), + body: Swiper( + itemBuilder: (context, index) { + return Image.asset( + images[index], + fit: BoxFit.fill, + ); + }, + autoplay: true, + itemCount: images.length, + scrollDirection: Axis.vertical, + pagination: const SwiperPagination(alignment: Alignment.centerRight), + control: const SwiperControl(), + )); + } +} + +class ExampleFraction extends StatelessWidget { + const ExampleFraction({Key? key}) : super(key: key); + + @override + Widget build(BuildContext context) { + return Scaffold( + appBar: AppBar( + title: const Text("ExampleFraction"), + ), + body: Column( + children: [ + Expanded( + child: Swiper( + itemBuilder: (context, index) { + return Image.asset( + images[index], + fit: BoxFit.fill, + ); + }, + autoplay: true, + itemCount: images.length, + pagination: + const SwiperPagination(builder: SwiperPagination.fraction), + control: const SwiperControl(), + )), + Expanded( + child: Swiper( + itemBuilder: (context, index) { + return Image.asset( + images[index], + fit: BoxFit.fill, + ); + }, + autoplay: true, + itemCount: images.length, + scrollDirection: Axis.vertical, + pagination: const SwiperPagination( + alignment: Alignment.centerRight, + builder: SwiperPagination.fraction), + )) + ], + )); + } +} + +class ExampleRect extends StatelessWidget { + const ExampleRect({Key? key}) : super(key: key); + + @override + Widget build(BuildContext context) { + return Scaffold( + appBar: AppBar( + title: const Text("ExampleFraction"), + ), + body: Column( + children: [ + Expanded( + child: Swiper( + itemBuilder: (context, index) { + return Image.asset( + images[index], + fit: BoxFit.fill, + ); + }, + autoplay: true, + itemCount: images.length, + pagination: + const SwiperPagination(builder: SwiperPagination.rect), + control: const SwiperControl(), + ), + ), + Expanded( + child: Swiper( + itemBuilder: (context, index) { + return Image.asset( + images[index], + fit: BoxFit.fill, + ); + }, + autoplay: true, + itemCount: images.length, + scrollDirection: Axis.vertical, + pagination: const SwiperPagination( + alignment: Alignment.centerRight, + builder: SwiperPagination.rect, + ), + ), + ) + ], + )); + } +} + +class ExampleCustomPagination extends StatelessWidget { + const ExampleCustomPagination({Key? key}) : super(key: key); + + @override + Widget build(BuildContext context) { + return Scaffold( + appBar: AppBar( + title: const Text("Custom Pagination"), + ), + body: Column( + children: [ + Expanded( + child: Swiper( + itemBuilder: (context, index) { + return Image.asset( + images[index], + fit: BoxFit.fill, + ); + }, + autoplay: true, + itemCount: images.length, + pagination: SwiperPagination( + margin: const EdgeInsets.all(0.0), + builder: SwiperCustomPagination(builder: + (BuildContext context, SwiperPluginConfig config) { + return ConstrainedBox( + constraints: const BoxConstraints.expand(height: 50.0), + child: Container( + color: Colors.white, + child: Text( + "${titles[config.activeIndex]} ${config.activeIndex + 1}/${config.itemCount}", + style: const TextStyle(fontSize: 20.0), + )), + ); + })), + control: const SwiperControl(), + ), + ), + Expanded( + child: Swiper( + itemBuilder: (context, index) { + return Image.asset( + images[index], + fit: BoxFit.fill, + ); + }, + autoplay: true, + itemCount: images.length, + pagination: SwiperPagination( + margin: const EdgeInsets.all(0.0), + builder: SwiperCustomPagination(builder: + (BuildContext context, SwiperPluginConfig config) { + return ConstrainedBox( + constraints: const BoxConstraints.expand(height: 50.0), + child: Row( + children: [ + Text( + "${titles[config.activeIndex]} ${config.activeIndex + 1}/${config.itemCount}", + style: const TextStyle(fontSize: 20.0), + ), + Expanded( + child: Align( + alignment: Alignment.centerRight, + child: const DotSwiperPaginationBuilder( + color: Colors.black12, + activeColor: Colors.black, + size: 10.0, + activeSize: 20.0) + .build(context, config), + ), + ) + ], + ), + ); + })), + control: const SwiperControl(color: Colors.redAccent), + ), + ) + ], + )); + } +} + +class ExamplePhone extends StatelessWidget { + const ExamplePhone({Key? key}) : super(key: key); + + @override + Widget build(BuildContext context) { + return Scaffold( + appBar: AppBar( + title: const Text("Phone"), + ), + body: Stack( + children: [ + ConstrainedBox( + constraints: const BoxConstraints.expand(), + child: Image.asset( + "images/bg.jpeg", + fit: BoxFit.fill, + ), + ), + Swiper.children( + autoplay: false, + pagination: const SwiperPagination( + margin: EdgeInsets.fromLTRB(0.0, 0.0, 0.0, 30.0), + builder: DotSwiperPaginationBuilder( + color: Colors.white30, + activeColor: Colors.white, + size: 20.0, + activeSize: 20.0)), + children: [ + Image.asset( + "images/1.png", + fit: BoxFit.contain, + ), + Image.asset( + "images/2.png", + fit: BoxFit.contain, + ), + Image.asset("images/3.png", fit: BoxFit.contain) + ], + ) + ], + ), + ); + } +} + +class ScaffoldWidget extends StatelessWidget { + final Widget child; + final String title; + final List? actions; + + const ScaffoldWidget({ + Key? key, + required this.title, + required this.child, + this.actions, + }) : super(key: key); + + @override + Widget build(BuildContext context) { + return Scaffold( + appBar: AppBar( + title: Text(title), + actions: actions, + ), + body: child, + ); + } +} diff --git a/package/flutter_swiper_view/example/lib/src/config.dart b/package/flutter_swiper_view/example/lib/src/config.dart new file mode 100644 index 00000000..fcea1725 --- /dev/null +++ b/package/flutter_swiper_view/example/lib/src/config.dart @@ -0,0 +1,5 @@ +const List images = [ + "images/bg0.jpeg", + "images/bg1.jpeg", + "images/bg2.jpeg", +]; diff --git a/package/flutter_swiper_view/example/lib/src/example_custom.dart b/package/flutter_swiper_view/example/lib/src/example_custom.dart new file mode 100644 index 00000000..5c32d2e4 --- /dev/null +++ b/package/flutter_swiper_view/example/lib/src/example_custom.dart @@ -0,0 +1,365 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_swiper_view/flutter_swiper_view.dart'; + +import 'config.dart'; +import 'forms/form_widget.dart'; + +class ExampleCustom extends StatefulWidget { + const ExampleCustom({Key? key}) : super(key: key); + + @override + State createState() { + return _ExampleCustomState(); + } +} + +class _ExampleCustomState extends State { + //properties want to custom + late int _itemCount; + + late bool _loop; + + late bool _autoplay; + + late int _autoplayDelay; + + late double _padding; + + late bool _outer; + + late double _radius; + + late double _viewportFraction; + + late SwiperLayout _layout; + + late int _currentIndex; + + late double _scale; + + late Axis _scrollDirection; + + late AxisDirection _axisDirection; + + late Curve _curve; + + late double _fade; + + late bool _autoplayDisableOnInteraction; + + late CustomLayoutOption customLayoutOption; + + late SwiperController _controller; + + TextEditingController numberController = TextEditingController(); + + Widget _buildItem(BuildContext context, int index) { + return ClipRRect( + borderRadius: BorderRadius.all(Radius.circular(_radius)), + child: Image.asset( + images[index % images.length], + fit: BoxFit.fill, + ), + ); + } + + @override + void didUpdateWidget(ExampleCustom oldWidget) { + customLayoutOption = CustomLayoutOption(startIndex: -1, stateCount: 3) + ..addRotate([-45.0 / 180, 0.0, 45.0 / 180]) + ..addTranslate([ + const Offset(-370.0, -40.0), + const Offset(0.0, 0.0), + const Offset(370.0, -40.0) + ]); + super.didUpdateWidget(oldWidget); + } + + @override + void initState() { + customLayoutOption = CustomLayoutOption(startIndex: -1, stateCount: 3) + ..addRotate([-25.0 / 180, 0.0, 25.0 / 180]) + ..addTranslate([ + const Offset(-350.0, 0.0), + const Offset(0.0, 0.0), + const Offset(350.0, 0.0) + ]); + _fade = 1.0; + _currentIndex = 0; + _curve = Curves.ease; + _scale = 0.8; + _autoplay = false; + _controller = SwiperController(); + _layout = SwiperLayout.TINDER; + _radius = 10.0; + _padding = 0.0; + _loop = true; + _itemCount = 3; + _autoplayDelay = 3000; + _viewportFraction = 0.8; + _outer = false; + _scrollDirection = Axis.horizontal; + _axisDirection = AxisDirection.left; + _autoplayDisableOnInteraction = false; + super.initState(); + } + +// maintain the index + + Widget buildSwiper() { + return Swiper( + onTap: (index) { + Navigator.of(context).push(MaterialPageRoute( + builder: (context) { + return Scaffold( + appBar: AppBar( + title: const Text('New page'), + ), + body: Container(), + ); + }, + )); + }, + customLayoutOption: customLayoutOption, + fade: _fade, + index: _currentIndex, + onIndexChanged: (index) { + setState(() { + _currentIndex = index; + }); + }, + curve: _curve, + scale: _scale, + itemWidth: 300.0, + controller: _controller, + layout: _layout, + outer: _outer, + itemHeight: 200.0, + viewportFraction: _viewportFraction, + autoplayDelay: _autoplayDelay, + loop: _loop, + autoplay: _autoplay, + itemBuilder: _buildItem, + itemCount: _itemCount, + scrollDirection: _scrollDirection, + axisDirection: _axisDirection, + indicatorLayout: PageIndicatorLayout.COLOR, + autoplayDisableOnInteraction: _autoplayDisableOnInteraction, + pagination: const SwiperPagination( + builder: DotSwiperPaginationBuilder( + size: 20.0, activeSize: 20.0, space: 10.0)), + ); + } + + @override + Widget build(BuildContext context) { + return Column( + children: [ + Container( + color: Colors.black87, + child: SizedBox( + height: 300.0, + width: double.infinity, + child: buildSwiper(), + ), + ), + Expanded( + child: ListView( + children: [ + Text('Index:$_currentIndex'), + Row( + children: [ + ElevatedButton( + onPressed: () { + _controller.previous(animation: true); + }, + child: const Text('Prev'), + ), + ElevatedButton( + onPressed: () { + _controller.next(animation: true); + }, + child: const Text('Next'), + ), + Expanded( + child: TextField( + controller: numberController, + )), + ElevatedButton( + onPressed: () { + final text = numberController.text; + setState(() { + _currentIndex = int.tryParse(text) ?? 0; + }); + }, + child: const Text('Update'), + ), + ], + ), + FormWidget( + label: 'layout', + child: FormSelect( + placeholder: 'Select layout', + value: _layout, + values: const [ + SwiperLayout.DEFAULT, + SwiperLayout.STACK, + SwiperLayout.TINDER, + SwiperLayout.CUSTOM + ], + valueChanged: (value) { + _layout = value; + setState(() {}); + }, + ), + ), + FormWidget( + label: 'scrollDirection', + child: Switch( + value: _scrollDirection == Axis.horizontal, + onChanged: (value) => setState(() => _scrollDirection = + value ? Axis.horizontal : Axis.vertical)), + ), + if (_layout == SwiperLayout.STACK) + FormWidget( + label: 'axisDirection (left <-> right)', + child: Switch( + value: _axisDirection == AxisDirection.right, + onChanged: (value) => setState(() => _axisDirection = + value ? AxisDirection.right : AxisDirection.left)), + ), + FormWidget( + label: 'autoplayDisableOnInteraction', + child: Switch( + value: _autoplayDisableOnInteraction, + onChanged: (value) => + setState(() => _autoplayDisableOnInteraction = value)), + ), + //Pannel Begin + FormWidget( + label: 'loop', + child: Switch( + value: _loop, + onChanged: (value) => setState(() => _loop = value)), + ), + FormWidget( + label: 'outer', + child: Switch( + value: _outer, + onChanged: (value) => setState(() => _outer = value)), + ), + //Pannel Begin + FormWidget( + label: 'autoplay', + child: Switch( + value: _autoplay, + onChanged: (value) => setState(() => _autoplay = value)), + ), + + FormWidget( + label: 'padding', + child: NumberPad( + number: _padding, + step: 5.0, + min: 0.0, + max: 30.0, + onChangeValue: (value) { + _padding = value.toDouble(); + setState(() {}); + }, + ), + ), + FormWidget( + label: 'scale', + child: NumberPad( + number: _scale, + step: 0.1, + min: 0.0, + max: 1.0, + onChangeValue: (value) { + _scale = value.toDouble(); + setState(() {}); + }, + ), + ), + FormWidget( + label: 'fade', + child: NumberPad( + number: _fade, + step: 0.1, + min: 0.0, + max: 1.0, + onChangeValue: (value) { + _fade = value.toDouble(); + setState(() {}); + }, + ), + ), + FormWidget( + label: 'itemCount', + child: NumberPad( + number: _itemCount, + step: 1, + min: 0, + max: 100, + onChangeValue: (value) { + _itemCount = value.toInt(); + setState(() {}); + }, + ), + ), + + FormWidget( + label: 'radius', + child: NumberPad( + number: _radius, + step: 1.0, + min: 0.0, + max: 30.0, + onChangeValue: (value) { + _radius = value.toDouble(); + setState(() {}); + }, + ), + ), + + FormWidget( + label: 'viewportFraction', + child: NumberPad( + number: _viewportFraction, + step: 0.1, + max: 1.0, + min: 0.5, + onChangeValue: (value) { + _viewportFraction = value.toDouble(); + setState(() {}); + }, + ), + ), + + FormWidget( + label: 'curve', + child: FormSelect( + placeholder: 'Select curve', + value: _curve, + values: const [ + Curves.easeInOut, + Curves.ease, + Curves.bounceInOut, + Curves.bounceOut, + Curves.bounceIn, + Curves.fastOutSlowIn + ], + valueChanged: (value) { + _curve = value; + setState(() {}); + }, + ), + ), + ], + ), + ) + ], + ); + } +} diff --git a/package/flutter_swiper_view/example/lib/src/example_swiper_in_scrollview.dart b/package/flutter_swiper_view/example/lib/src/example_swiper_in_scrollview.dart new file mode 100644 index 00000000..fbf531b3 --- /dev/null +++ b/package/flutter_swiper_view/example/lib/src/example_swiper_in_scrollview.dart @@ -0,0 +1,276 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_swiper_view/flutter_swiper_view.dart'; + +class ExampleSwiperInScrollView extends StatefulWidget { + const ExampleSwiperInScrollView({Key? key}) : super(key: key); + + @override + State createState() { + return _ExampleState(); + } +} + +class _ExampleState extends State + with TickerProviderStateMixin { + late AnimationController controller; + late Animation _animation10; + late Animation _animation11; + late Animation _animation12; + late Animation _animation13; + + _ExampleState(); + + @override + void dispose() { + controller.dispose(); + + super.dispose(); + } + + @override + void initState() { + controller = AnimationController(vsync: this); + _animation10 = Tween(begin: 0.0, end: 1.0).animate(controller); + _animation11 = Tween(begin: 0.0, end: 1.0).animate(controller); + _animation12 = Tween(begin: 0.0, end: 1.0).animate(controller); + _animation13 = Tween(begin: 0.0, end: 1.0).animate(controller); + controller.animateTo(1.0, duration: const Duration(seconds: 3)); + super.initState(); + } + + Widget _buildDynamicCard() { + return Card( + elevation: 2.0, + color: Colors.white, + child: Stack( + children: [ + Column( + children: [ + Container( + padding: const EdgeInsets.all(10.0), + ), + ], + ), + Column( + children: [ + Row( + mainAxisAlignment: MainAxisAlignment.spaceEvenly, + children: [ + Column( + children: [ + Container( + padding: const EdgeInsets.only(top: 40.0), + ), + ScaleTransition( + scale: _animation10, + alignment: FractionalOffset.center, + ), + ], + ), + Column( + children: [ + Container( + padding: const EdgeInsets.only(top: 160.0), + ), + ScaleTransition( + scale: _animation11, + alignment: FractionalOffset.center, + ), + ], + ), + Container( + padding: const EdgeInsets.all(1.0), + ), + Column( + children: [ + Container( + padding: const EdgeInsets.only(top: 160.0), + ), + ScaleTransition( + scale: _animation12, + alignment: FractionalOffset.center, + ), + ], + ), + Column( + children: [ + Container( + padding: const EdgeInsets.only(top: 40.0), + ), + ScaleTransition( + scale: _animation13, + alignment: FractionalOffset.center, + ), + ], + ), + ]), + Container( + padding: const EdgeInsets.all(10.0), + ), + ], + ) + ], + ), + ); + } + + @override + Widget build(BuildContext context) { + return Container( + color: Theme.of(context).primaryColorLight, + child: CustomScrollView( + slivers: [ + SliverList( + delegate: SliverChildBuilderDelegate((c, i) { + return Column( + mainAxisSize: MainAxisSize.min, + children: [ + SizedBox( + height: 100.0, + child: Swiper( + scale: 0.8, + fade: 0.8, + itemBuilder: (c, i) { + return Container( + color: Colors.grey, + child: Text("$i"), + ); + }, + itemCount: 10, + pagination: const SwiperPagination(), + ), + ), + SizedBox( + height: 100.0, + child: Swiper( + scale: 0.8, + fade: 0.8, + itemBuilder: (c, i) { + return Container( + color: Colors.grey, + child: Text("$i"), + ); + }, + pagination: const SwiperPagination( + builder: SwiperPagination.fraction), + itemCount: 0), + ), + SizedBox( + height: 100.0, + child: Swiper( + scale: 0.8, + fade: 0.8, + itemBuilder: (c, i) { + return Container( + color: Colors.grey, + child: Text("$i"), + ); + }, + pagination: const SwiperPagination( + builder: SwiperPagination.fraction), + itemCount: 10000), + ), + SizedBox( + height: 100.0, + child: Swiper( + outer: true, + scale: 0.8, + fade: 0.8, + itemBuilder: (c, i) { + return Container( + color: Colors.grey, + child: Text("$i"), + ); + }, + pagination: const SwiperPagination(), + itemCount: 10), + ), + const Text("Image from network"), + SizedBox( + height: 300.0, + child: Swiper( + itemCount: 10, + itemBuilder: (BuildContext context, int index) { + return Image.network( + "https://ss3.baidu.com/9fo3dSag_xI4khGko9WTAnF6hhy/image/h%3D300/sign=87d6daed02f41bd5c553eef461d881a0/f9198618367adab4b025268587d4b31c8601e47b.jpg"); + }, + ), + ), + SizedBox( + height: 100.0, + child: Swiper( + outer: true, + scale: 0.8, + fade: 0.8, + itemBuilder: (c, i) { + return Card( + elevation: 2.0, + child: Stack( + alignment: AlignmentDirectional.center, + children: [ + Image.network( + "https://ss3.baidu.com/9fo3dSag_xI4khGko9WTAnF6hhy/image/h%3D300/sign=87d6daed02f41bd5c553eef461d881a0/f9198618367adab4b025268587d4b31c8601e47b.jpg"), + FractionalTranslation( + translation: const Offset(0.0, 0.0), + child: Container( + alignment: const FractionalOffset(0.0, 0.0), + decoration: BoxDecoration( + border: Border.all( + color: Colors.lightBlue.withOpacity(0.5), + width: 100.0, + ), + shape: BoxShape.circle, + ), + ), + ), + Container( + //padding: const EdgeInsets.only(bottom:10.0), + margin: const EdgeInsets.all(140.0), + + child: const Icon(Icons.location_on, + color: Colors.white, size: 25.0), + ), + ], + ), + ); + }, + pagination: const SwiperPagination( + alignment: Alignment.topCenter), + itemCount: 10), + ), + SizedBox( + height: 400.0, + child: Swiper( + outer: true, + itemBuilder: (c, i) { + return _buildDynamicCard(); + }, + pagination: const SwiperPagination( + alignment: Alignment.topCenter), + itemCount: 10), + ), + SizedBox( + height: 100.0, + child: Swiper( + outer: true, + fade: 0.8, + viewportFraction: 0.8, + scale: 0.8, + itemBuilder: (c, i) { + return Container( + color: Colors.grey, + child: Text("$i"), + ); + }, + pagination: const SwiperPagination( + alignment: Alignment.topCenter), + itemCount: 10), + ), + ], + ); + }, childCount: 1)) + ], + ), + ); + } +} diff --git a/package/flutter_swiper_view/example/lib/src/forms/form_widget.dart b/package/flutter_swiper_view/example/lib/src/forms/form_widget.dart new file mode 100644 index 00000000..d5e91eb8 --- /dev/null +++ b/package/flutter_swiper_view/example/lib/src/forms/form_widget.dart @@ -0,0 +1,162 @@ +import 'package:flutter/material.dart'; +import 'package:flutter/cupertino.dart'; + +class FormWidget extends StatelessWidget { + final String label; + + final Widget child; + + const FormWidget({ + Key? key, + required this.label, + required this.child, + }) : super(key: key); + + @override + Widget build(BuildContext context) { + return Padding( + padding: const EdgeInsets.all(5.0), + child: Row( + children: [ + Text(label, style: const TextStyle(fontSize: 14.0)), + Expanded( + child: Align( + alignment: Alignment.centerRight, + child: child, + ), + ) + ], + ), + ); + } +} + +class FormSelect extends StatefulWidget { + final String placeholder; + final ValueChanged valueChanged; + final List values; + final T value; + + const FormSelect({ + Key? key, + required this.placeholder, + required this.valueChanged, + required this.value, + required this.values, + }) : super(key: key); + + @override + State createState() { + return _FormSelectState(); + } +} + +class _FormSelectState extends State> { + int _selectedIndex = 0; + + @override + void initState() { + for (int i = 0; i < widget.values.length; ++i) { + if (widget.values[i] == widget.value) { + _selectedIndex = i; + break; + } + } + + super.initState(); + } + + @override + Widget build(BuildContext context) { + final placeholder = widget.placeholder; + final values = widget.values; + + return InkWell( + child: Text( + _selectedIndex < 0 ? placeholder : values[_selectedIndex].toString()), + onTap: () { + _selectedIndex = 0; + showBottomSheet( + context: context, + builder: (context) { + return SizedBox( + height: values.length * 30.0 + 200.0, + child: Column( + mainAxisSize: MainAxisSize.min, + children: [ + SizedBox( + height: values.length * 30.0 + 70.0, + child: CupertinoPicker( + itemExtent: 30.0, + children: values.map((value) { + return Text(value.toString()); + }).toList(), + onSelectedItemChanged: (index) { + _selectedIndex = index; + }, + ), + ), + Center( + child: ElevatedButton( + onPressed: () { + if (_selectedIndex >= 0) { + widget.valueChanged( + widget.values[_selectedIndex], + ); + } + + setState(() {}); + + Navigator.of(context).pop(); + }, + child: const Text('ok'), + ), + ) + ], + ), + ); + }); + }, + ); + } +} + +class NumberPad extends StatelessWidget { + final num number; + final num step; + final num max; + final num min; + final ValueChanged onChangeValue; + + const NumberPad({ + Key? key, + required this.number, + required this.step, + required this.onChangeValue, + required this.max, + required this.min, + }) : super(key: key); + + void onAdd() { + onChangeValue(number + step > max ? max : number + step); + } + + void onSub() { + onChangeValue(number - step < min ? min : number - step); + } + + @override + Widget build(BuildContext context) { + return Row( + mainAxisSize: MainAxisSize.min, + children: [ + IconButton(icon: const Icon(Icons.exposure_neg_1), onPressed: onSub), + Text( + number is int ? number.toString() : number.toStringAsFixed(1), + style: const TextStyle(fontSize: 14.0), + ), + IconButton(icon: const Icon(Icons.exposure_plus_1), onPressed: onAdd) + ], + ); + } +} diff --git a/package/flutter_swiper_view/example/pubspec.yaml b/package/flutter_swiper_view/example/pubspec.yaml new file mode 100644 index 00000000..3504f4bd --- /dev/null +++ b/package/flutter_swiper_view/example/pubspec.yaml @@ -0,0 +1,35 @@ +name: example +description: A new Flutter project. + +environment: + sdk: '>=2.12.0 <3.0.0' + +dependencies: + flutter: + sdk: flutter + + # The following adds the Cupertino Icons font to your application. + # Use with the CupertinoIcons class for iOS style icons. + cupertino_icons: ^1.0.2 + + flutter_swiper_view: + path: ../ + + +dev_dependencies: + flutter_lints: ^2.0.1 + flutter_test: + sdk: flutter + +flutter: + uses-material-design: true + + + assets: + - images/bg0.jpeg + - images/bg1.jpeg + - images/bg2.jpeg + - images/bg.jpeg + - images/1.png + - images/2.png + - images/3.png diff --git a/package/flutter_swiper_view/example/res/1.gif b/package/flutter_swiper_view/example/res/1.gif new file mode 100644 index 00000000..51de191b Binary files /dev/null and b/package/flutter_swiper_view/example/res/1.gif differ diff --git a/package/flutter_swiper_view/example/res/2.gif b/package/flutter_swiper_view/example/res/2.gif new file mode 100644 index 00000000..821df059 Binary files /dev/null and b/package/flutter_swiper_view/example/res/2.gif differ diff --git a/package/flutter_swiper_view/example/res/3.gif b/package/flutter_swiper_view/example/res/3.gif new file mode 100644 index 00000000..a8f50a61 Binary files /dev/null and b/package/flutter_swiper_view/example/res/3.gif differ diff --git a/package/flutter_swiper_view/example/res/4.gif b/package/flutter_swiper_view/example/res/4.gif new file mode 100644 index 00000000..43bde554 Binary files /dev/null and b/package/flutter_swiper_view/example/res/4.gif differ diff --git a/package/flutter_swiper_view/example/res/5.gif b/package/flutter_swiper_view/example/res/5.gif new file mode 100644 index 00000000..a9b87b28 Binary files /dev/null and b/package/flutter_swiper_view/example/res/5.gif differ diff --git a/package/flutter_swiper_view/example/test/widget_test.dart b/package/flutter_swiper_view/example/test/widget_test.dart new file mode 100644 index 00000000..43218bbf --- /dev/null +++ b/package/flutter_swiper_view/example/test/widget_test.dart @@ -0,0 +1,29 @@ +// This is a basic Flutter widget test. +// To perform an interaction with a widget in your test, use the WidgetTester utility that Flutter +// provides. For example, you can send tap and scroll gestures. You can also use WidgetTester to +// find child widgets in the widget tree, read text, and verify that the values of widget properties +// are correct. + +import 'package:flutter/material.dart'; +import 'package:flutter_test/flutter_test.dart'; + +import 'package:example/main.dart'; + +void main() { + testWidgets('Counter increments smoke test', (WidgetTester tester) async { + // Build our app and trigger a frame. + await tester.pumpWidget(const MyApp()); + + // Verify that our counter starts at 0. + expect(find.text('0'), findsOneWidget); + expect(find.text('1'), findsNothing); + + // Tap the '+' icon and trigger a frame. + await tester.tap(find.byIcon(Icons.add)); + await tester.pump(); + + // Verify that our counter has incremented. + expect(find.text('0'), findsNothing); + expect(find.text('1'), findsOneWidget); + }); +} diff --git a/package/flutter_swiper_view/lib/flutter_swiper_view.dart b/package/flutter_swiper_view/lib/flutter_swiper_view.dart new file mode 100644 index 00000000..8dc90daa --- /dev/null +++ b/package/flutter_swiper_view/lib/flutter_swiper_view.dart @@ -0,0 +1,9 @@ +library flutter_swiper_view; + +export 'src/flutter_page_indicator.dart'; +export 'src/index_controller.dart'; +export 'src/swiper.dart'; +export 'src/swiper_pagination.dart'; +export 'src/swiper_control.dart'; +export 'src/swiper_controller.dart'; +export 'src/swiper_plugin.dart'; diff --git a/package/flutter_swiper_view/lib/src/custom_layout.dart b/package/flutter_swiper_view/lib/src/custom_layout.dart new file mode 100644 index 00000000..8270a490 --- /dev/null +++ b/package/flutter_swiper_view/lib/src/custom_layout.dart @@ -0,0 +1,449 @@ +part of 'swiper.dart'; + +abstract class _CustomLayoutStateBase extends State + with SingleTickerProviderStateMixin { + late double _swiperWidth; + late double _swiperHeight; + late Animation _animation; + late AnimationController _animationController; + SwiperController get _controller => widget.controller; + late int _startIndex; + int? _animationCount; + int _currentIndex = 0; + + @override + void initState() { + _currentIndex = widget.index ?? 0; + if (widget.itemWidth == null) { + throw Exception( + '==============\n\nwidget.itemWidth must not be null when use stack layout.\n========\n', + ); + } + + _createAnimationController(); + _controller.addListener(_onController); + super.initState(); + } + + void _createAnimationController() { + _animationController = AnimationController(vsync: this, value: 0.5); + Tween tween = Tween(begin: 0.0, end: 1.0); + _animation = tween.animate(_animationController); + } + + @override + void didChangeDependencies() { + WidgetsBinding.instance.addPostFrameCallback(_getSize); + super.didChangeDependencies(); + } + + void _getSize(Duration _) { + if (!mounted) return; + afterRender(); + } + + @mustCallSuper + void afterRender() { + RenderObject renderObject = context.findRenderObject()!; + Size size = renderObject.paintBounds.size; + _swiperWidth = size.width; + _swiperHeight = size.height; + setState(() {}); + } + + @override + void didUpdateWidget(T oldWidget) { + if (widget.controller != oldWidget.controller) { + oldWidget.controller.removeListener(_onController); + widget.controller.addListener(_onController); + } + + if (widget.loop != oldWidget.loop) { + if (!widget.loop) { + _currentIndex = _ensureIndex(_currentIndex); + } + } + + if (widget.axisDirection != oldWidget.axisDirection) { + afterRender(); + } + + super.didUpdateWidget(oldWidget); + } + + int _ensureIndex(int index) { + var res = index; + res = index % widget.itemCount; + if (res < 0) { + res += widget.itemCount; + } + return res; + } + + @override + void dispose() { + widget.controller.removeListener(_onController); + _animationController.dispose(); + super.dispose(); + } + + Widget _buildItem(int i, int realIndex, double animationValue); + + Widget _buildContainer(List list) { + return Stack( + children: list, + ); + } + + Widget _buildAnimation(BuildContext context, Widget? w) { + List list = []; + + double animationValue = _animation.value; + + for (int i = 0; i < _animationCount! && widget.itemCount > 0; ++i) { + int realIndex = _currentIndex + i + _startIndex; + realIndex = realIndex % widget.itemCount; + if (realIndex < 0) { + realIndex += widget.itemCount; + } + + if (widget.axisDirection == AxisDirection.right) { + list.insert(0, _buildItem(i, realIndex, animationValue)); + } else { + list.add(_buildItem(i, realIndex, animationValue)); + } + } + + return GestureDetector( + behavior: HitTestBehavior.opaque, + onPanStart: _onPanStart, + onPanEnd: _onPanEnd, + onPanUpdate: _onPanUpdate, + child: ClipRect( + child: Center( + child: _buildContainer(list), + ), + ), + ); + } + + @override + Widget build(BuildContext context) { + if (_animationCount == null) { + return Container(); + } + return AnimatedBuilder( + animation: _animationController, + builder: _buildAnimation, + ); + } + + late double _currentValue; + late double _currentPos; + + bool _lockScroll = false; + + Future _move(double position, {int? nextIndex}) async { + if (_lockScroll) return; + try { + _lockScroll = true; + await _animationController.animateTo( + position, + duration: Duration(milliseconds: widget.duration!), + curve: widget.curve, + ); + if (nextIndex != null) { + widget.onIndexChanged!(widget.getCorrectIndex(nextIndex)); + } + } catch (e) { + debugPrint('error animating _animationController ${e.toString()}'); + } finally { + if (nextIndex != null) { + try { + _animationController.value = 0.5; + } catch (e) { + debugPrint( + 'error setting _animationController.value ${e.toString()}'); + } + + _currentIndex = nextIndex; + } + _lockScroll = false; + } + } + + int _getProperNewIndex(int newIndex) { + var res = newIndex; + if (!widget.loop && newIndex >= widget.itemCount - 1) { + res = widget.itemCount - 1; + } else if (!widget.loop && newIndex < 0) { + res = 0; + } + return res; + } + + void _onController() { + SwiperController controller = widget.controller; + final event = controller.event; + if (event is StepBasedIndexControllerEvent) { + final newIndex = event.calcNextIndex( + currentIndex: _currentIndex, + itemCount: widget.itemCount, + loop: widget.loop, + reverse: false, + ); + _move(event.targetPosition, nextIndex: newIndex); + } else if (event is MoveIndexControllerEvent) { + _move( + event.targetPosition, + nextIndex: _getProperNewIndex(event.newIndex), + ); + } + } + + void _onPanEnd(DragEndDetails details) { + if (_lockScroll) return; + + double velocity = widget.scrollDirection == Axis.horizontal + ? details.velocity.pixelsPerSecond.dx + : details.velocity.pixelsPerSecond.dy; + + if (_animationController.value >= 0.75 || velocity > 500.0) { + if (_currentIndex <= 0 && !widget.loop) { + return; + } + _move(1.0, nextIndex: _currentIndex - 1); + } else if (_animationController.value < 0.25 || velocity < -500.0) { + if (_currentIndex >= widget.itemCount - 1 && !widget.loop) { + return; + } + _move(0.0, nextIndex: _currentIndex + 1); + } else { + _move(0.5); + } + } + + void _onPanStart(DragStartDetails details) { + if (_lockScroll) return; + _currentValue = _animationController.value; + _currentPos = widget.scrollDirection == Axis.horizontal + ? details.globalPosition.dx + : details.globalPosition.dy; + } + + void _onPanUpdate(DragUpdateDetails details) { + if (_lockScroll) return; + double value = _currentValue + + ((widget.scrollDirection == Axis.horizontal + ? details.globalPosition.dx + : details.globalPosition.dy) - + _currentPos) / + _swiperWidth / + 2; + // no loop ? + if (!widget.loop) { + if (_currentIndex >= widget.itemCount - 1) { + if (value < 0.5) { + value = 0.5; + } + } else if (_currentIndex <= 0) { + if (value > 0.5) { + value = 0.5; + } + } + } + + _animationController.value = value; + } +} + +double _getValue(List values, double animationValue, int index) { + double s = values[index]; + if (animationValue >= 0.5) { + if (index < values.length - 1) { + s = s + (values[index + 1] - s) * (animationValue - 0.5) * 2.0; + } + } else { + if (index != 0) { + s = s - (s - values[index - 1]) * (0.5 - animationValue) * 2.0; + } + } + return s; +} + +Offset _getOffsetValue(List values, double animationValue, int index) { + Offset s = values[index]; + double dx = s.dx; + double dy = s.dy; + if (animationValue >= 0.5) { + if (index < values.length - 1) { + dx = dx + (values[index + 1].dx - dx) * (animationValue - 0.5) * 2.0; + dy = dy + (values[index + 1].dy - dy) * (animationValue - 0.5) * 2.0; + } + } else { + if (index != 0) { + dx = dx - (dx - values[index - 1].dx) * (0.5 - animationValue) * 2.0; + dy = dy - (dy - values[index - 1].dy) * (0.5 - animationValue) * 2.0; + } + } + return Offset(dx, dy); +} + +abstract class TransformBuilder { + final List values; + + TransformBuilder({required this.values}); + + Widget build(int i, double animationValue, Widget widget); +} + +class ScaleTransformBuilder extends TransformBuilder { + final Alignment alignment; + + ScaleTransformBuilder({ + required List values, + this.alignment = Alignment.center, + }) : super(values: values); + + @override + Widget build(int i, double animationValue, Widget widget) { + double s = _getValue(values, animationValue, i); + return Transform.scale(scale: s, child: widget); + } +} + +class OpacityTransformBuilder extends TransformBuilder { + OpacityTransformBuilder({required List values}) + : super(values: values); + + @override + Widget build(int i, double animationValue, Widget widget) { + double v = _getValue(values, animationValue, i); + return Opacity( + opacity: v, + child: widget, + ); + } +} + +class RotateTransformBuilder extends TransformBuilder { + RotateTransformBuilder({required List values}) + : super(values: values); + + @override + Widget build(int i, double animationValue, Widget widget) { + double v = _getValue(values, animationValue, i); + return Transform.rotate( + angle: v, + child: widget, + ); + } +} + +class TranslateTransformBuilder extends TransformBuilder { + TranslateTransformBuilder({required List values}) + : super(values: values); + + @override + Widget build(int i, double animationValue, Widget widget) { + Offset s = _getOffsetValue(values, animationValue, i); + return Transform.translate( + offset: s, + child: widget, + ); + } +} + +class CustomLayoutOption { + final List builders = []; + final int startIndex; + final int? stateCount; + + CustomLayoutOption({this.stateCount, required this.startIndex}); + + void addOpacity(List values) { + builders.add(OpacityTransformBuilder(values: values)); + } + + void addTranslate(List values) { + builders.add(TranslateTransformBuilder(values: values)); + } + + void addScale(List values, Alignment alignment) { + builders.add(ScaleTransformBuilder(values: values, alignment: alignment)); + } + + void addRotate(List values) { + builders.add(RotateTransformBuilder(values: values)); + } +} + +class _CustomLayoutSwiper extends _SubSwiper { + final CustomLayoutOption option; + + const _CustomLayoutSwiper({ + required this.option, + double? itemWidth, + required bool loop, + double? itemHeight, + ValueChanged? onIndexChanged, + Key? key, + IndexedWidgetBuilder? itemBuilder, + required Curve curve, + int? duration, + int? index, + required int itemCount, + Axis? scrollDirection, + required SwiperController controller, + }) : super( + loop: loop, + onIndexChanged: onIndexChanged, + itemWidth: itemWidth, + itemHeight: itemHeight, + key: key, + itemBuilder: itemBuilder, + curve: curve, + duration: duration, + index: index, + itemCount: itemCount, + controller: controller, + scrollDirection: scrollDirection); + + @override + State createState() { + return _CustomLayoutState(); + } +} + +class _CustomLayoutState extends _CustomLayoutStateBase<_CustomLayoutSwiper> { + @override + void didChangeDependencies() { + super.didChangeDependencies(); + _startIndex = widget.option.startIndex; + _animationCount = widget.option.stateCount; + } + + @override + void didUpdateWidget(_CustomLayoutSwiper oldWidget) { + _startIndex = widget.option.startIndex; + _animationCount = widget.option.stateCount; + super.didUpdateWidget(oldWidget); + } + + @override + Widget _buildItem(int index, int realIndex, double animationValue) { + List builders = widget.option.builders; + + Widget child = SizedBox( + width: widget.itemWidth ?? double.infinity, + height: widget.itemHeight ?? double.infinity, + child: widget.itemBuilder!(context, realIndex)); + + for (int i = builders.length - 1; i >= 0; --i) { + TransformBuilder builder = builders[i]; + child = builder.build(index, animationValue, child); + } + + return child; + } +} diff --git a/package/flutter_swiper_view/lib/src/flutter_page_indicator.dart b/package/flutter_swiper_view/lib/src/flutter_page_indicator.dart new file mode 100644 index 00000000..3384a35e --- /dev/null +++ b/package/flutter_swiper_view/lib/src/flutter_page_indicator.dart @@ -0,0 +1,355 @@ +// ignore_for_file: constant_identifier_names + +import 'package:flutter/material.dart'; +import 'package:flutter_swiper_view/flutter_swiper_view.dart'; + +class WarmPainter extends BasePainter { + WarmPainter(PageIndicator widget, double page, int index, Paint paint) + : super(widget, page, index, paint); + + @override + void draw(Canvas canvas, double space, double size, double radius) { + double progress = page - index; + double distance = size + space; + double start = index * (size + space); + + if (progress > 0.5) { + double right = start + size + distance; + //progress=>0.5-1.0 + //left:0.0=>distance + + double left = index * distance + distance * (progress - 0.5) * 2; + canvas.drawRRect( + RRect.fromLTRBR(left, 0.0, right, size, Radius.circular(radius)), + _paint); + } else { + double right = start + size + distance * progress * 2; + + canvas.drawRRect( + RRect.fromLTRBR(start, 0.0, right, size, Radius.circular(radius)), + _paint); + } + } +} + +class DropPainter extends BasePainter { + DropPainter(PageIndicator widget, double page, int index, Paint paint) + : super(widget, page, index, paint); + + @override + void draw(Canvas canvas, double space, double size, double radius) { + double progress = page - index; + double dropHeight = widget.dropHeight; + double rate = (0.5 - progress).abs() * 2; + double scale = widget.scale; + + //lerp(begin, end, progress) + + canvas.drawCircle( + Offset(radius + ((page) * (size + space)), + radius - dropHeight * (1 - rate)), + radius * (scale + rate * (1.0 - scale)), + _paint); + } +} + +class NonePainter extends BasePainter { + NonePainter(PageIndicator widget, double page, int index, Paint paint) + : super(widget, page, index, paint); + + @override + void draw(Canvas canvas, double space, double size, double radius) { + double progress = page - index; + double secondOffset = index == widget.count - 1 + ? radius + : radius + ((index + 1) * (size + space)); + + if (progress > 0.5) { + canvas.drawCircle(Offset(secondOffset, radius), radius, _paint); + } else { + canvas.drawCircle( + Offset(radius + (index * (size + space)), radius), radius, _paint); + } + } +} + +class SlidePainter extends BasePainter { + SlidePainter(PageIndicator widget, double page, int index, Paint paint) + : super(widget, page, index, paint); + + @override + void draw(Canvas canvas, double space, double size, double radius) { + canvas.drawCircle( + Offset(radius + (page * (size + space)), radius), radius, _paint); + } +} + +class ScalePainter extends BasePainter { + ScalePainter(PageIndicator widget, double page, int index, Paint paint) + : super(widget, page, index, paint); + + // 连续的两个点,含有最后一个和第一个 + @override + bool _shouldSkip(int i) { + if (index == widget.count - 1) { + return i == 0 || i == index; + } + return (i == index || i == index + 1); + } + + @override + void paint(Canvas canvas, Size size) { + _paint.color = widget.color; + double space = widget.space; + double size = widget.size; + double radius = size / 2; + int c = widget.count; + for (int i = 0; i < c; ++i) { + if (_shouldSkip(i)) { + continue; + } + canvas.drawCircle(Offset(i * (size + space) + radius, radius), + radius * widget.scale, _paint); + } + + _paint.color = widget.activeColor; + draw(canvas, space, size, radius); + } + + @override + void draw(Canvas canvas, double space, double size, double radius) { + double secondOffset = index == widget.count - 1 + ? radius + : radius + ((index + 1) * (size + space)); + + double progress = page - index; + _paint.color = Color.lerp(widget.activeColor, widget.color, progress)!; + //last + canvas.drawCircle(Offset(radius + (index * (size + space)), radius), + lerp(radius, radius * widget.scale, progress), _paint); + //first + _paint.color = Color.lerp(widget.color, widget.activeColor, progress)!; + canvas.drawCircle(Offset(secondOffset, radius), + lerp(radius * widget.scale, radius, progress), _paint); + } +} + +class ColorPainter extends BasePainter { + ColorPainter(PageIndicator widget, double page, int index, Paint paint) + : super(widget, page, index, paint); + + // 连续的两个点,含有最后一个和第一个 + @override + bool _shouldSkip(int i) { + if (index == widget.count - 1) { + return i == 0 || i == index; + } + return (i == index || i == index + 1); + } + + @override + void draw(Canvas canvas, double space, double size, double radius) { + double progress = page - index; + double secondOffset = index == widget.count - 1 + ? radius + : radius + ((index + 1) * (size + space)); + + _paint.color = Color.lerp(widget.activeColor, widget.color, progress)!; + //left + canvas.drawCircle( + Offset(radius + (index * (size + space)), radius), radius, _paint); + //right + _paint.color = Color.lerp(widget.color, widget.activeColor, progress)!; + canvas.drawCircle(Offset(secondOffset, radius), radius, _paint); + } +} + +abstract class BasePainter extends CustomPainter { + final PageIndicator widget; + final double page; + final int index; + final Paint _paint; + + double lerp(double begin, double end, double progress) { + return begin + (end - begin) * progress; + } + + BasePainter(this.widget, this.page, this.index, this._paint); + + void draw(Canvas canvas, double space, double size, double radius); + + bool _shouldSkip(int i) { + return false; + } + //double secondOffset = index == widget.count-1 ? radius : radius + ((index + 1) * (size + space)); + + @override + void paint(Canvas canvas, Size size) { + _paint.color = widget.color; + double space = widget.space; + double size = widget.size; + double radius = size / 2; + int c = widget.count; + for (int i = 0; i < c; ++i) { + if (_shouldSkip(i)) { + continue; + } + canvas.drawCircle( + Offset(i * (size + space) + radius, radius), radius, _paint); + } + + double page = this.page; + if (page < index) { + page = 0.0; + } + _paint.color = widget.activeColor; + draw(canvas, space, size, radius); + } + + @override + bool shouldRepaint(BasePainter oldDelegate) { + return oldDelegate.page != page; + } +} + +class _PageIndicatorState extends State { + int index = 0; + double page = 0; + final Paint _paint = Paint(); + + BasePainter _createPainter() { + switch (widget.layout) { + case PageIndicatorLayout.NONE: + return NonePainter(widget, page, index, _paint); + case PageIndicatorLayout.SLIDE: + return SlidePainter(widget, page, index, _paint); + case PageIndicatorLayout.WARM: + return WarmPainter(widget, page, index, _paint); + case PageIndicatorLayout.COLOR: + return ColorPainter(widget, page, index, _paint); + case PageIndicatorLayout.SCALE: + return ScalePainter(widget, page, index, _paint); + case PageIndicatorLayout.DROP: + return DropPainter(widget, page, index, _paint); + default: + throw Exception("Not a valid layout"); + } + } + + @override + Widget build(BuildContext context) { + Widget child = SizedBox( + width: widget.count * widget.size + (widget.count - 1) * widget.space, + height: widget.size, + child: CustomPaint( + painter: _createPainter(), + ), + ); + + if (widget.layout == PageIndicatorLayout.SCALE || + widget.layout == PageIndicatorLayout.COLOR) { + child = ClipRect( + child: child, + ); + } + + return IgnorePointer( + child: child, + ); + } + + void _setInitialPage() { + // use the initial page index but cut off + // the offset specified when looping (kMiddleValue) + index = widget.controller.initialPage % kMiddleValue; + page = index.toDouble(); + } + + void _onController() { + if (!widget.controller.hasClients) return; + page = widget.controller.page ?? 0.0; + index = page.floor(); + + setState(() {}); + } + + @override + void initState() { + super.initState(); + widget.controller.addListener(_onController); + _setInitialPage(); + } + + @override + void didUpdateWidget(PageIndicator oldWidget) { + super.didUpdateWidget(oldWidget); + if (widget.controller != oldWidget.controller) { + oldWidget.controller.removeListener(_onController); + widget.controller.addListener(_onController); + } + } + + @override + void dispose() { + widget.controller.removeListener(_onController); + super.dispose(); + } +} + +enum PageIndicatorLayout { + NONE, + SLIDE, + WARM, + COLOR, + SCALE, + DROP, +} + +class PageIndicator extends StatefulWidget { + /// size of the dots + final double size; + + /// space between dots. + final double space; + + /// count of dots + final int count; + + /// active color + final Color activeColor; + + /// normal color + final Color color; + + /// layout of the dots,default is [PageIndicatorLayout.SLIDE] + final PageIndicatorLayout? layout; + + // Only valid when layout==PageIndicatorLayout.scale + final double scale; + + // Only valid when layout==PageIndicatorLayout.drop + final double dropHeight; + + final PageController controller; + + final double activeSize; + + const PageIndicator({ + Key? key, + this.size = 20.0, + this.space = 5.0, + required this.count, + this.activeSize = 20.0, + required this.controller, + this.color = Colors.white30, + this.layout = PageIndicatorLayout.SLIDE, + this.activeColor = Colors.white, + this.scale = 0.6, + this.dropHeight = 20.0, + }) : super(key: key); + + @override + State createState() { + return _PageIndicatorState(); + } +} diff --git a/package/flutter_swiper_view/lib/src/index_controller.dart b/package/flutter_swiper_view/lib/src/index_controller.dart new file mode 100644 index 00000000..f8ea30aa --- /dev/null +++ b/package/flutter_swiper_view/lib/src/index_controller.dart @@ -0,0 +1,119 @@ +import 'dart:async'; + +import 'package:flutter/foundation.dart'; +import 'package:flutter/widgets.dart'; + +abstract class IndexControllerEventBase { + final bool animation; + + final completer = Completer(); + Future get future => completer.future; + void complete() { + if (!completer.isCompleted) { + completer.complete(); + } + } + + IndexControllerEventBase({ + required this.animation, + }); +} + +mixin TargetedPositionControllerEvent on IndexControllerEventBase { + double get targetPosition; +} +mixin StepBasedIndexControllerEvent on TargetedPositionControllerEvent { + int get step; + int calcNextIndex({ + required int currentIndex, + required int itemCount, + required bool loop, + required bool reverse, + }) { + var cIndex = currentIndex; + if (reverse) { + cIndex -= step; + } else { + cIndex += step; + } + + if (!loop) { + if (cIndex >= itemCount) { + cIndex = 0; + } else if (cIndex < 0) { + cIndex = itemCount - 1; + } + } + return cIndex; + } +} + +class NextIndexControllerEvent extends IndexControllerEventBase + with TargetedPositionControllerEvent, StepBasedIndexControllerEvent { + NextIndexControllerEvent({ + required bool animation, + }) : super( + animation: animation, + ); + + @override + int get step => 1; + + @override + double get targetPosition => 1; +} + +class PrevIndexControllerEvent extends IndexControllerEventBase + with TargetedPositionControllerEvent, StepBasedIndexControllerEvent { + PrevIndexControllerEvent({ + required bool animation, + }) : super( + animation: animation, + ); + @override + int get step => -1; + + @override + double get targetPosition => 0; +} + +class MoveIndexControllerEvent extends IndexControllerEventBase + with TargetedPositionControllerEvent { + final int newIndex; + final int oldIndex; + MoveIndexControllerEvent({ + required this.newIndex, + required this.oldIndex, + required bool animation, + }) : super( + animation: animation, + ); + @override + double get targetPosition => newIndex > oldIndex ? 1 : 0; +} + +class IndexController extends ChangeNotifier { + IndexControllerEventBase? event; + int index = 0; + Future move(int index, {bool animation = true}) { + final e = event = MoveIndexControllerEvent( + animation: animation, + newIndex: index, + oldIndex: this.index, + ); + notifyListeners(); + return e.future; + } + + Future next({bool animation = true}) { + final e = event = NextIndexControllerEvent(animation: animation); + notifyListeners(); + return e.future; + } + + Future previous({bool animation = true}) { + final e = event = PrevIndexControllerEvent(animation: animation); + notifyListeners(); + return e.future; + } +} diff --git a/package/flutter_swiper_view/lib/src/parallax.dart b/package/flutter_swiper_view/lib/src/parallax.dart new file mode 100644 index 00000000..905920f4 --- /dev/null +++ b/package/flutter_swiper_view/lib/src/parallax.dart @@ -0,0 +1,152 @@ +import 'package:flutter/widgets.dart'; + +import 'transformer_page_view.dart'; + +typedef PaintCallback = void Function(Canvas canvas, Size size); + +class ColorPainter extends CustomPainter { + final Paint _paint; + final TransformInfo info; + final List colors; + + ColorPainter(this._paint, this.info, this.colors); + + @override + void paint(Canvas canvas, Size size) { + int index = info.fromIndex; + _paint.color = colors[index]; + canvas.drawRect(Rect.fromLTWH(0.0, 0.0, size.width, size.height), _paint); + if (info.done!) { + return; + } + int alpha; + int color; + double opacity; + double? position = info.position; + if (info.forward!) { + if (index < colors.length - 1) { + color = colors[index + 1].value & 0x00ffffff; + opacity = (position! <= 0 + ? (-position / info.viewportFraction!) + : 1 - position / info.viewportFraction!); + if (opacity > 1) { + opacity -= 1.0; + } + if (opacity < 0) { + opacity += 1.0; + } + alpha = (0xff * opacity).toInt(); + + _paint.color = Color((alpha << 24) | color); + canvas.drawRect( + Rect.fromLTWH(0.0, 0.0, size.width, size.height), _paint); + } + } else { + if (index > 0) { + color = colors[index - 1].value & 0x00ffffff; + opacity = (position! > 0 + ? position / info.viewportFraction! + : (1 + position / info.viewportFraction!)); + if (opacity > 1) { + opacity -= 1.0; + } + if (opacity < 0) { + opacity += 1.0; + } + alpha = (0xff * opacity).toInt(); + + _paint.color = Color((alpha << 24) | color); + canvas.drawRect( + Rect.fromLTWH(0.0, 0.0, size.width, size.height), _paint); + } + } + } + + @override + bool shouldRepaint(ColorPainter oldDelegate) { + return oldDelegate.info != info; + } +} + +class _ParallaxColorState extends State { + Paint paint = Paint(); + + @override + Widget build(BuildContext context) { + return CustomPaint( + painter: ColorPainter(paint, widget.info, widget.colors), + child: widget.child, + ); + } +} + +class ParallaxColor extends StatefulWidget { + final Widget child; + + final List colors; + + final TransformInfo info; + + const ParallaxColor({ + Key? key, + required this.colors, + required this.info, + required this.child, + }) : super(key: key); + + @override + State createState() { + return _ParallaxColorState(); + } +} + +class ParallaxContainer extends StatelessWidget { + final Widget child; + final double position; + final double translationFactor; + final double opacityFactor; + + const ParallaxContainer({ + Key? key, + required this.child, + required this.position, + this.translationFactor = 100.0, + this.opacityFactor = 1.0, + }) : super(key: key); + + @override + Widget build(BuildContext context) { + return Opacity( + opacity: (1 - position.abs()).clamp(0.0, 1.0) * opacityFactor, + child: Transform.translate( + offset: Offset(position * translationFactor, 0.0), + child: child, + ), + ); + } +} + +class ParallaxImage extends StatelessWidget { + final Image image; + final double imageFactor; + + ParallaxImage.asset( + String name, { + Key? key, + required double position, + this.imageFactor = 0.3, + }) : image = Image.asset( + name, + fit: BoxFit.cover, + alignment: FractionalOffset( + 0.5 + position * imageFactor, + 0.5, + ), + ), + super(key: key); + + @override + Widget build(BuildContext context) { + return image; + } +} diff --git a/package/flutter_swiper_view/lib/src/swiper.dart b/package/flutter_swiper_view/lib/src/swiper.dart new file mode 100644 index 00000000..1b55ae73 --- /dev/null +++ b/package/flutter_swiper_view/lib/src/swiper.dart @@ -0,0 +1,1003 @@ +// ignore_for_file: constant_identifier_names + +import 'dart:async'; + +import 'package:flutter/material.dart'; +import 'package:flutter_swiper_view/flutter_swiper_view.dart'; + +import 'transformer_page_view.dart'; + +part 'custom_layout.dart'; + +typedef SwiperOnTap = void Function(int index); + +typedef SwiperDataBuilder = Widget Function( + BuildContext context, T data, int index); + +/// default auto play delay +const int kDefaultAutoplayDelayMs = 3000; + +/// Default auto play transition duration (in millisecond) +const int kDefaultAutoplayTransactionDuration = 300; + +const int kMaxValue = 2000000000; +const int kMiddleValue = 1000000000; + +enum SwiperLayout { + DEFAULT, + STACK, + TINDER, + CUSTOM, +} + +class Swiper extends StatefulWidget { + /// If set true , the pagination will display 'outer' of the 'content' container. + final bool outer; + + /// Inner item height, this property is valid if layout=STACK or layout=TINDER or LAYOUT=CUSTOM, + final double? itemHeight; + + /// Inner item width, this property is valid if layout=STACK or layout=TINDER or LAYOUT=CUSTOM, + final double? itemWidth; + + // height of the inside container,this property is valid when outer=true,otherwise the inside container size is controlled by parent widget + final double? containerHeight; + // width of the inside container,this property is valid when outer=true,otherwise the inside container size is controlled by parent widget + final double? containerWidth; + + /// Build item on index + final IndexedWidgetBuilder? itemBuilder; + + /// Support transform like Android PageView did + /// `itemBuilder` and `transformItemBuilder` must have one not null + final PageTransformer? transformer; + + /// count of the display items + final int itemCount; + + final ValueChanged? onIndexChanged; + + ///auto play config + final bool autoplay; + + ///Duration of the animation between transactions (in millisecond). + final int autoplayDelay; + + ///disable auto play when interaction + final bool autoplayDisableOnInteraction; + + ///auto play transition duration (in millisecond) + final int duration; + + ///horizontal/vertical + final Axis scrollDirection; + + ///left/right for Stack Layout + final AxisDirection axisDirection; + + ///transition curve + final Curve curve; + + /// Set to false to disable continuous loop mode. + final bool loop; + + ///Index number of initial slide. + ///If not set , the `Swiper` is 'uncontrolled', which means manage index by itself + ///If set , the `Swiper` is 'controlled', which means the index is fully managed by parent widget. + final int? index; + + ///Called when tap + final SwiperOnTap? onTap; + + ///The swiper pagination plugin + final SwiperPlugin? pagination; + + ///the swiper control button plugin + final SwiperPlugin? control; + + ///other plugins, you can custom your own plugin + final List? plugins; + + /// + final SwiperController? controller; + + final ScrollPhysics? physics; + + /// + final double viewportFraction; + + /// Build in layouts + final SwiperLayout layout; + + /// this value is valid when layout == SwiperLayout.CUSTOM + final CustomLayoutOption? customLayoutOption; + + // This value is valid when viewportFraction is set and < 1.0 + final double? scale; + + // This value is valid when viewportFraction is set and < 1.0 + final double? fade; + + final PageIndicatorLayout indicatorLayout; + + final bool allowImplicitScrolling; + + const Swiper({ + this.itemBuilder, + this.indicatorLayout = PageIndicatorLayout.NONE, + + /// + this.transformer, + required this.itemCount, + bool autoplay = false, + this.layout = SwiperLayout.DEFAULT, + this.autoplayDelay = kDefaultAutoplayDelayMs, + this.autoplayDisableOnInteraction = true, + this.duration = kDefaultAutoplayTransactionDuration, + this.onIndexChanged, + this.index, + this.onTap, + this.control, + bool loop = true, + this.curve = Curves.ease, + this.scrollDirection = Axis.horizontal, + this.axisDirection = AxisDirection.left, + this.pagination, + this.plugins, + this.physics, + Key? key, + this.controller, + this.customLayoutOption, + + /// since v1.0.0 + this.containerHeight, + this.containerWidth, + this.viewportFraction = 1.0, + this.itemHeight, + this.itemWidth, + this.outer = false, + this.scale, + this.fade, + this.allowImplicitScrolling = false, + }) : assert( + itemBuilder != null || transformer != null, + 'itemBuilder and transformItemBuilder must not be both null', + ), + assert( + !loop || + ((loop && + layout == SwiperLayout.DEFAULT && + (indicatorLayout == PageIndicatorLayout.SCALE || + indicatorLayout == PageIndicatorLayout.COLOR || + indicatorLayout == PageIndicatorLayout.NONE)) || + (loop && layout != SwiperLayout.DEFAULT)), + "Only support `PageIndicatorLayout.SCALE` and `PageIndicatorLayout.COLOR`when layout==SwiperLayout.DEFAULT in loop mode"), + autoplay = (autoplay && itemCount > 1), + loop = (loop && itemCount > 1), + super(key: key); + + factory Swiper.children({ + required List children, + bool autoplay = false, + PageTransformer? transformer, + int autoplayDelay = kDefaultAutoplayDelayMs, + bool autoplayDisableOnInteraction = true, + int duration = kDefaultAutoplayTransactionDuration, + ValueChanged? onIndexChanged, + int? index, + SwiperOnTap? onTap, + bool loop = true, + Curve curve = Curves.ease, + Axis scrollDirection = Axis.horizontal, + AxisDirection axisDirection = AxisDirection.left, + SwiperPlugin? pagination, + SwiperPlugin? control, + List? plugins, + SwiperController? controller, + Key? key, + CustomLayoutOption? customLayoutOption, + ScrollPhysics? physics, + double? containerHeight, + double? containerWidth, + double viewportFraction = 1.0, + double? itemHeight, + double? itemWidth, + bool outer = false, + double scale = 1.0, + double? fade, + PageIndicatorLayout indicatorLayout = PageIndicatorLayout.NONE, + SwiperLayout layout = SwiperLayout.DEFAULT, + }) => + Swiper( + fade: fade, + indicatorLayout: indicatorLayout, + layout: layout, + transformer: transformer, + customLayoutOption: customLayoutOption, + containerHeight: containerHeight, + containerWidth: containerWidth, + viewportFraction: viewportFraction, + itemHeight: itemHeight, + itemWidth: itemWidth, + outer: outer, + scale: scale, + autoplay: autoplay, + autoplayDelay: autoplayDelay, + autoplayDisableOnInteraction: autoplayDisableOnInteraction, + duration: duration, + onIndexChanged: onIndexChanged, + index: index, + onTap: onTap, + curve: curve, + scrollDirection: scrollDirection, + axisDirection: axisDirection, + pagination: pagination, + control: control, + controller: controller, + loop: loop, + plugins: plugins, + physics: physics, + key: key, + itemBuilder: (context, index) { + return children[index]; + }, + itemCount: children.length, + ); + + static Swiper list({ + PageTransformer? transformer, + required List list, + CustomLayoutOption? customLayoutOption, + required SwiperDataBuilder builder, + bool autoplay = false, + int autoplayDelay = kDefaultAutoplayDelayMs, + bool reverse = false, + bool autoplayDisableOnInteraction = true, + int duration = kDefaultAutoplayTransactionDuration, + ValueChanged? onIndexChanged, + int? index, + SwiperOnTap? onTap, + bool loop = true, + Curve curve = Curves.ease, + Axis scrollDirection = Axis.horizontal, + AxisDirection axisDirection = AxisDirection.left, + SwiperPlugin? pagination, + SwiperPlugin? control, + List? plugins, + SwiperController? controller, + Key? key, + ScrollPhysics? physics, + double? containerHeight, + double? containerWidth, + double viewportFraction = 1.0, + double? itemHeight, + double? itemWidth, + bool outer = false, + double scale = 1.0, + double? fade, + PageIndicatorLayout indicatorLayout = PageIndicatorLayout.NONE, + SwiperLayout layout = SwiperLayout.DEFAULT, + }) => + Swiper( + fade: fade, + indicatorLayout: indicatorLayout, + layout: layout, + transformer: transformer, + customLayoutOption: customLayoutOption, + containerHeight: containerHeight, + containerWidth: containerWidth, + viewportFraction: viewportFraction, + itemHeight: itemHeight, + itemWidth: itemWidth, + outer: outer, + scale: scale, + autoplay: autoplay, + autoplayDelay: autoplayDelay, + autoplayDisableOnInteraction: autoplayDisableOnInteraction, + duration: duration, + onIndexChanged: onIndexChanged, + index: index, + onTap: onTap, + curve: curve, + key: key, + scrollDirection: scrollDirection, + axisDirection: axisDirection, + pagination: pagination, + control: control, + controller: controller, + loop: loop, + plugins: plugins, + physics: physics, + itemBuilder: (context, index) { + return builder(context, list[index], index); + }, + itemCount: list.length, + ); + + @override + State createState() => _SwiperState(); +} + +abstract class _SwiperTimerMixin extends State { + Timer? _timer; + + late SwiperController _controller; + + @override + void initState() { + super.initState(); + _controller = widget.controller ?? SwiperController(); + _controller.addListener(_onController); + if (widget.autoplay) { + _controller.startAutoplay(); + } else { + _controller.stopAutoplay(); + } + } + + void _onController() { + final event = _controller.event; + if (event is AutoPlaySwiperControllerEvent) { + if (event.autoplay) { + if (_timer == null) { + _startAutoplay(); + } + } else { + _stopAutoplay(); + } + } + } + + @override + void didUpdateWidget(Swiper oldWidget) { + super.didUpdateWidget(oldWidget); + if (_controller != oldWidget.controller) { + final oldController = oldWidget.controller; + if (oldController != null) { + oldController.removeListener(_onController); + _controller = oldController; + _controller.addListener(_onController); + } + } + if (widget.autoplay != oldWidget.autoplay) { + if (widget.autoplay) { + _controller.startAutoplay(); + } else { + _controller.stopAutoplay(); + } + } + } + + @override + void dispose() { + _controller.removeListener(_onController); + _stopAutoplay(); + super.dispose(); + } + + void _startAutoplay() { + _stopAutoplay(); + _timer = Timer.periodic( + Duration( + milliseconds: widget.autoplayDelay, + ), + _onTimer, + ); + } + + void _onTimer(Timer timer) { + _controller.next(animation: true); + } + + void _stopAutoplay() { + _timer?.cancel(); + _timer = null; + } +} + +class _SwiperState extends _SwiperTimerMixin { + late int _activeIndex; + + TransformerPageController? _pageController; + + Widget _wrapTap(BuildContext context, int index) { + return GestureDetector( + behavior: HitTestBehavior.opaque, + onTap: () => widget.onTap!(index), + child: widget.itemBuilder!(context, index), + ); + } + + @override + void initState() { + super.initState(); + _activeIndex = widget.index ?? widget.controller?.index ?? 0; + if (_isPageViewLayout()) { + _pageController = TransformerPageController( + initialPage: widget.index ?? widget.controller?.index ?? 0, + loop: widget.loop, + itemCount: widget.itemCount, + reverse: widget.transformer?.reverse ?? false, + viewportFraction: widget.viewportFraction, + ); + } + } + + bool _isPageViewLayout() { + return widget.layout == SwiperLayout.DEFAULT; + } + + @override + void didChangeDependencies() { + super.didChangeDependencies(); + } + + bool _getReverse(Swiper widget) => widget.transformer?.reverse ?? false; + + @override + void didUpdateWidget(Swiper oldWidget) { + super.didUpdateWidget(oldWidget); + if (_isPageViewLayout()) { + if (_pageController == null || + (widget.index != oldWidget.index || + widget.loop != oldWidget.loop || + widget.itemCount != oldWidget.itemCount || + widget.viewportFraction != oldWidget.viewportFraction || + _getReverse(widget) != _getReverse(oldWidget))) { + _pageController = TransformerPageController( + initialPage: widget.index ?? widget.controller?.index ?? 0, + loop: widget.loop, + itemCount: widget.itemCount, + reverse: _getReverse(widget), + viewportFraction: widget.viewportFraction, + ); + } + } else { + scheduleMicrotask(() { + // So that we have a chance to do `removeListener` in child widgets. + if (_pageController != null) { + _pageController!.dispose(); + _pageController = null; + } + }); + } + if (widget.index != null && widget.index != _activeIndex) { + _activeIndex = widget.index!; + } + } + + void _onIndexChanged(int index) { + setState(() { + _activeIndex = index; + }); + widget.onIndexChanged?.call(index); + } + + Widget _buildSwiper() { + IndexedWidgetBuilder? itemBuilder; + if (widget.onTap != null) { + itemBuilder = _wrapTap; + } else { + itemBuilder = widget.itemBuilder; + } + + if (widget.layout == SwiperLayout.STACK) { + return _StackSwiper( + loop: widget.loop, + itemWidth: widget.itemWidth, + itemHeight: widget.itemHeight, + itemCount: widget.itemCount, + itemBuilder: itemBuilder, + index: _activeIndex, + curve: widget.curve, + duration: widget.duration, + onIndexChanged: _onIndexChanged, + controller: _controller, + scrollDirection: widget.scrollDirection, + axisDirection: widget.axisDirection, + ); + } else if (_isPageViewLayout()) { + PageTransformer? transformer = widget.transformer; + if (widget.scale != null || widget.fade != null) { + transformer = + ScaleAndFadeTransformer(scale: widget.scale, fade: widget.fade); + } + + Widget child = TransformerPageView( + pageController: _pageController, + loop: widget.loop, + itemCount: widget.itemCount, + itemBuilder: itemBuilder, + transformer: transformer, + viewportFraction: widget.viewportFraction, + index: _activeIndex, + duration: Duration(milliseconds: widget.duration), + scrollDirection: widget.scrollDirection, + onPageChanged: _onIndexChanged, + curve: widget.curve, + physics: widget.physics, + controller: _controller, + allowImplicitScrolling: widget.allowImplicitScrolling, + ); + if (widget.autoplayDisableOnInteraction && widget.autoplay) { + return NotificationListener( + onNotification: (notification) { + if (notification is ScrollStartNotification) { + if (notification.dragDetails != null) { + //by human + if (_timer != null) _stopAutoplay(); + } + } else if (notification is ScrollEndNotification) { + if (_timer == null) _startAutoplay(); + } + + return false; + }, + child: child, + ); + } + + return child; + } else if (widget.layout == SwiperLayout.TINDER) { + return _TinderSwiper( + loop: widget.loop, + itemWidth: widget.itemWidth, + itemHeight: widget.itemHeight, + itemCount: widget.itemCount, + itemBuilder: itemBuilder, + index: _activeIndex, + curve: widget.curve, + duration: widget.duration, + onIndexChanged: _onIndexChanged, + controller: _controller, + scrollDirection: widget.scrollDirection, + ); + } else if (widget.layout == SwiperLayout.CUSTOM) { + return _CustomLayoutSwiper( + loop: widget.loop, + option: widget.customLayoutOption!, + itemWidth: widget.itemWidth, + itemHeight: widget.itemHeight, + itemCount: widget.itemCount, + itemBuilder: itemBuilder, + index: _activeIndex, + curve: widget.curve, + duration: widget.duration, + onIndexChanged: _onIndexChanged, + controller: _controller, + scrollDirection: widget.scrollDirection, + ); + } else { + return const SizedBox.shrink(); + } + } + + SwiperPluginConfig _ensureConfig(SwiperPluginConfig? config) { + final con = config ?? + SwiperPluginConfig( + outer: widget.outer, + itemCount: widget.itemCount, + layout: widget.layout, + indicatorLayout: widget.indicatorLayout, + pageController: _pageController, + activeIndex: _activeIndex, + scrollDirection: widget.scrollDirection, + axisDirection: widget.axisDirection, + controller: _controller, + loop: widget.loop, + ); + return con; + } + + List? _ensureListForStack({ + required Widget swiper, + required List? listForStack, + required Widget widget, + }) { + final resList = []; + if (listForStack == null) { + resList.addAll([swiper, widget]); + } else { + resList.addAll([...listForStack, widget]); + } + return resList; + } + + @override + Widget build(BuildContext context) { + Widget swiper = _buildSwiper(); + List? listForStack; + SwiperPluginConfig? config; + if (widget.control != null) { + //Stack + config = _ensureConfig(config); + listForStack = _ensureListForStack( + swiper: swiper, + listForStack: listForStack, + widget: widget.control!.build(context, config), + ); + } + + if (widget.plugins != null) { + config = _ensureConfig(config); + for (SwiperPlugin plugin in widget.plugins!) { + listForStack = _ensureListForStack( + swiper: swiper, + listForStack: listForStack, + widget: plugin.build(context, config), + ); + } + } + if (widget.pagination != null) { + config = _ensureConfig(config); + if (widget.outer) { + return _buildOuterPagination( + widget.pagination! as SwiperPagination, + listForStack == null ? swiper : Stack(children: listForStack), + config); + } else { + listForStack = _ensureListForStack( + swiper: swiper, + listForStack: listForStack, + widget: widget.pagination!.build(context, config), + ); + } + } + + if (listForStack != null) { + return Stack( + children: listForStack, + ); + } + + return swiper; + } + + Widget _buildOuterPagination( + SwiperPagination pagination, + Widget swiper, + SwiperPluginConfig config, + ) { + final list = []; + //Only support bottom yet! + if (widget.containerHeight != null || widget.containerWidth != null) { + list.add(swiper); + } else { + list.add(Expanded(child: swiper)); + } + + list.add(Align( + alignment: Alignment.center, + child: pagination.build(context, config), + )); + + return Column( + crossAxisAlignment: CrossAxisAlignment.stretch, + mainAxisSize: MainAxisSize.min, + children: list, + ); + } +} + +abstract class _SubSwiper extends StatefulWidget { + final IndexedWidgetBuilder? itemBuilder; + final int itemCount; + final int? index; + final ValueChanged? onIndexChanged; + final SwiperController controller; + final int? duration; + final Curve curve; + final double? itemWidth; + final double? itemHeight; + final bool loop; + final Axis? scrollDirection; + final AxisDirection? axisDirection; + + const _SubSwiper({ + Key? key, + required this.loop, + this.itemHeight, + this.itemWidth, + this.duration, + required this.curve, + this.itemBuilder, + required this.controller, + this.index, + required this.itemCount, + this.scrollDirection = Axis.horizontal, + this.axisDirection = AxisDirection.left, + this.onIndexChanged, + }) : super(key: key); + + @override + State createState(); + + int getCorrectIndex(int indexNeedsFix) { + if (itemCount == 0) return 0; + var value = indexNeedsFix % itemCount; + if (value < 0) { + value += itemCount; + } + return value; + } +} + +class _TinderSwiper extends _SubSwiper { + const _TinderSwiper({ + Key? key, + required Curve curve, + int? duration, + required SwiperController controller, + ValueChanged? onIndexChanged, + double? itemHeight, + double? itemWidth, + IndexedWidgetBuilder? itemBuilder, + int? index, + required bool loop, + required int itemCount, + Axis? scrollDirection, + }) : assert(itemWidth != null && itemHeight != null), + super( + loop: loop, + key: key, + itemWidth: itemWidth, + itemHeight: itemHeight, + itemBuilder: itemBuilder, + curve: curve, + duration: duration, + controller: controller, + index: index, + onIndexChanged: onIndexChanged, + itemCount: itemCount, + scrollDirection: scrollDirection); + + @override + State createState() { + return _TinderState(); + } +} + +class _StackSwiper extends _SubSwiper { + const _StackSwiper({ + Key? key, + required Curve curve, + int? duration, + required SwiperController controller, + ValueChanged? onIndexChanged, + double? itemHeight, + double? itemWidth, + IndexedWidgetBuilder? itemBuilder, + int? index, + required bool loop, + required int itemCount, + Axis? scrollDirection, + AxisDirection? axisDirection, + }) : super( + loop: loop, + key: key, + itemWidth: itemWidth, + itemHeight: itemHeight, + itemBuilder: itemBuilder, + curve: curve, + duration: duration, + controller: controller, + index: index, + onIndexChanged: onIndexChanged, + itemCount: itemCount, + scrollDirection: scrollDirection, + axisDirection: axisDirection, + ); + + @override + State createState() => _StackViewState(); +} + +class _TinderState extends _CustomLayoutStateBase<_TinderSwiper> { + late List scales; + late List offsetsX; + late List offsetsY; + late List opacity; + late List rotates; + + double getOffsetY(double scale) { + return widget.itemHeight! - widget.itemHeight! * scale; + } + + @override + void didChangeDependencies() { + super.didChangeDependencies(); + } + + @override + void didUpdateWidget(_TinderSwiper oldWidget) { + _updateValues(); + super.didUpdateWidget(oldWidget); + } + + @override + void afterRender() { + super.afterRender(); + + _startIndex = -3; + _animationCount = 5; + opacity = [0.0, 0.9, 0.9, 1.0, 0.0, 0.0]; + scales = [0.80, 0.80, 0.85, 0.90, 1.0, 1.0, 1.0]; + rotates = [0.0, 0.0, 0.0, 0.0, 20.0, 25.0]; + _updateValues(); + } + + void _updateValues() { + if (widget.scrollDirection == Axis.horizontal) { + offsetsX = [0.0, 0.0, 0.0, 0.0, _swiperWidth, _swiperWidth]; + offsetsY = [ + 0.0, + 0.0, + -5.0, + -10.0, + -15.0, + -20.0, + ]; + } else { + offsetsX = [ + 0.0, + 0.0, + 5.0, + 10.0, + 15.0, + 20.0, + ]; + + offsetsY = [0.0, 0.0, 0.0, 0.0, _swiperHeight, _swiperHeight]; + } + } + + @override + Widget _buildItem(int i, int realIndex, double animationValue) { + double s = _getValue(scales, animationValue, i); + double f = _getValue(offsetsX, animationValue, i); + double fy = _getValue(offsetsY, animationValue, i); + double o = _getValue(opacity, animationValue, i); + double a = _getValue(rotates, animationValue, i); + + Alignment alignment = widget.scrollDirection == Axis.horizontal + ? Alignment.bottomCenter + : Alignment.centerLeft; + + return Opacity( + opacity: o, + child: Transform.rotate( + angle: a / 180.0, + child: Transform.translate( + key: ValueKey(_currentIndex + i), + offset: Offset(f, fy), + child: Transform.scale( + scale: s, + alignment: alignment, + child: SizedBox( + width: widget.itemWidth ?? double.infinity, + height: widget.itemHeight ?? double.infinity, + child: widget.itemBuilder!(context, realIndex), + ), + ), + ), + ), + ); + } +} + +class _StackViewState extends _CustomLayoutStateBase<_StackSwiper> { + late List scales; + late List offsets; + late List opacity; + @override + void didChangeDependencies() { + super.didChangeDependencies(); + } + + void _updateValues() { + if (widget.scrollDirection == Axis.horizontal) { + double space = (_swiperWidth - widget.itemWidth!) / 2; + offsets = widget.axisDirection == AxisDirection.left + ? [-space, -space / 3 * 2, -space / 3, 0.0, _swiperWidth] + : [_swiperWidth, 0.0, -space / 3, -space / 3 * 2, -space]; + } else { + double space = (_swiperHeight - widget.itemHeight!) / 2; + offsets = [-space, -space / 3 * 2, -space / 3, 0.0, _swiperHeight]; + } + } + + @override + void didUpdateWidget(_StackSwiper oldWidget) { + _updateValues(); + super.didUpdateWidget(oldWidget); + } + + @override + void afterRender() { + super.afterRender(); + final isRightSide = widget.axisDirection == AxisDirection.right; + + //length of the values array below + _animationCount = 5; + + //Array below this line, '0' index is 1.0, which is the first item show in swiper. + _startIndex = isRightSide ? -1 : -3; + scales = + isRightSide ? [1.0, 1.0, 0.9, 0.8, 0.7] : [0.7, 0.8, 0.9, 1.0, 1.0]; + opacity = + isRightSide ? [1.0, 1.0, 1.0, 0.5, 0.0] : [0.0, 0.5, 1.0, 1.0, 1.0]; + + _updateValues(); + } + + @override + Widget _buildItem(int i, int realIndex, double animationValue) { + double s = _getValue(scales, animationValue, i); + double f = _getValue(offsets, animationValue, i); + double o = _getValue(opacity, animationValue, i); + + Offset offset = widget.scrollDirection == Axis.horizontal + ? widget.axisDirection == AxisDirection.left + ? Offset(f, 0.0) + : Offset(-f, 0.0) + : Offset(0.0, f); + + Alignment alignment = widget.scrollDirection == Axis.horizontal + ? widget.axisDirection == AxisDirection.left + ? Alignment.centerLeft + : Alignment.centerRight + : Alignment.topCenter; + + return Opacity( + opacity: o, + child: Transform.translate( + key: ValueKey(_currentIndex + i), + offset: offset, + child: Transform.scale( + scale: s, + alignment: alignment, + child: SizedBox( + width: widget.itemWidth ?? double.infinity, + height: widget.itemHeight ?? double.infinity, + child: widget.itemBuilder!(context, realIndex), + ), + ), + ), + ); + } +} + +class ScaleAndFadeTransformer extends PageTransformer { + final double? _scale; + final double? _fade; + + ScaleAndFadeTransformer({double? fade = 0.3, double? scale = 0.8}) + : _fade = fade, + _scale = scale; + + @override + Widget transform(Widget child, TransformInfo info) { + double? position = info.position; + Widget newChild = child; + if (_scale != null) { + double scaleFactor = (1 - position!.abs()) * (1 - _scale!); + double scale = _scale! + scaleFactor; + + newChild = Transform.scale( + scale: scale, + child: child, + ); + } + + if (_fade != null) { + double fadeFactor = (1 - position!.abs()) * (1 - _fade!); + double opacity = _fade! + fadeFactor; + newChild = Opacity( + opacity: opacity, + child: newChild, + ); + } + + return newChild; + } +} diff --git a/package/flutter_swiper_view/lib/src/swiper_control.dart b/package/flutter_swiper_view/lib/src/swiper_control.dart new file mode 100644 index 00000000..b3b3b125 --- /dev/null +++ b/package/flutter_swiper_view/lib/src/swiper_control.dart @@ -0,0 +1,133 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_swiper_view/flutter_swiper_view.dart'; + +class SwiperControl extends SwiperPlugin { + ///IconData for previous + final IconData iconPrevious; + + ///iconData fopr next + final IconData iconNext; + + ///icon size + final double size; + + ///Icon normal color, The theme's [ThemeData.primaryColor] by default. + final Color? color; + + ///if set loop=false on Swiper, this color will be used when swiper goto the last slide. + ///The theme's [ThemeData.disabledColor] by default. + final Color? disableColor; + + final EdgeInsetsGeometry padding; + + final Key? key; + + const SwiperControl({ + this.iconPrevious = Icons.arrow_back_ios, + this.iconNext = Icons.arrow_forward_ios, + this.color, + this.disableColor, + this.key, + this.size = 30.0, + this.padding = const EdgeInsets.all(5.0), + }); + + Widget buildButton({ + required SwiperPluginConfig? config, + required Color color, + required IconData iconDaga, + required int quarterTurns, + required bool previous, + }) { + return GestureDetector( + behavior: HitTestBehavior.opaque, + onTap: () { + if (previous) { + config!.controller.previous(animation: true); + } else { + config!.controller.next(animation: true); + } + }, + child: Padding( + padding: padding, + child: RotatedBox( + quarterTurns: quarterTurns, + child: Icon( + iconDaga, + semanticLabel: previous ? "Previous" : "Next", + size: size, + color: color, + ))), + ); + } + + @override + Widget build(BuildContext context, SwiperPluginConfig config) { + ThemeData themeData = Theme.of(context); + + Color color = this.color ?? themeData.primaryColor; + Color disableColor = this.disableColor ?? themeData.disabledColor; + Color prevColor; + Color nextColor; + + if (config.loop) { + prevColor = nextColor = color; + } else { + bool next = config.activeIndex < config.itemCount - 1; + bool prev = config.activeIndex > 0; + prevColor = prev ? color : disableColor; + nextColor = next ? color : disableColor; + } + + Widget child; + if (config.scrollDirection == Axis.horizontal) { + child = Row( + key: key, + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + buildButton( + config: config, + color: prevColor, + iconDaga: iconPrevious, + quarterTurns: 0, + previous: true, + ), + buildButton( + config: config, + color: nextColor, + iconDaga: iconNext, + quarterTurns: 0, + previous: false, + ) + ], + ); + } else { + child = Column( + key: key, + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + buildButton( + config: config, + color: prevColor, + iconDaga: iconPrevious, + quarterTurns: -3, + previous: true, + ), + buildButton( + config: config, + color: nextColor, + iconDaga: iconNext, + quarterTurns: -3, + previous: false, + ) + ], + ); + } + + return SizedBox( + height: double.infinity, + width: double.infinity, + child: child, + ); + } +} diff --git a/package/flutter_swiper_view/lib/src/swiper_controller.dart b/package/flutter_swiper_view/lib/src/swiper_controller.dart new file mode 100644 index 00000000..13363edf --- /dev/null +++ b/package/flutter_swiper_view/lib/src/swiper_controller.dart @@ -0,0 +1,44 @@ +import 'package:flutter_swiper_view/flutter_swiper_view.dart'; + +class SwipeIndexControllerEvent extends IndexControllerEventBase { + SwipeIndexControllerEvent({ + required this.pos, + required bool animation, + }) : super(animation: animation); + final double pos; +} + +class BuildIndexControllerEvent extends IndexControllerEventBase { + BuildIndexControllerEvent({ + required bool animation, + required this.config, + }) : super(animation: animation); + final SwiperPluginConfig config; +} + +class AutoPlaySwiperControllerEvent extends IndexControllerEventBase { + AutoPlaySwiperControllerEvent({ + required bool animation, + required this.autoplay, + }) : super(animation: animation); + + AutoPlaySwiperControllerEvent.start({ + required bool animation, + }) : this(animation: animation, autoplay: true); + AutoPlaySwiperControllerEvent.stop({ + required bool animation, + }) : this(animation: animation, autoplay: false); + final bool autoplay; +} + +class SwiperController extends IndexController { + void startAutoplay({bool animation = true}) { + event = AutoPlaySwiperControllerEvent.start(animation: animation); + notifyListeners(); + } + + void stopAutoplay({bool animation = true}) { + event = AutoPlaySwiperControllerEvent.stop(animation: animation); + notifyListeners(); + } +} diff --git a/package/flutter_swiper_view/lib/src/swiper_pagination.dart b/package/flutter_swiper_view/lib/src/swiper_pagination.dart new file mode 100644 index 00000000..51a18e81 --- /dev/null +++ b/package/flutter_swiper_view/lib/src/swiper_pagination.dart @@ -0,0 +1,310 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_swiper_view/flutter_swiper_view.dart'; + +class FractionPaginationBuilder extends SwiperPlugin { + ///color ,if set null , will be Theme.of(context).scaffoldBackgroundColor + final Color? color; + + ///color when active,if set null , will be Theme.of(context).primaryColor + final Color? activeColor; + + ////font size + final double fontSize; + + ///font size when active + final double activeFontSize; + + final Key? key; + + const FractionPaginationBuilder({ + this.color, + this.fontSize = 20.0, + this.key, + this.activeColor, + this.activeFontSize = 35.0, + }); + + @override + Widget build(BuildContext context, SwiperPluginConfig config) { + int itemCount = config.itemCount; + if (itemCount <= 1) { + return Container(); + } + + ThemeData themeData = Theme.of(context); + Color activeColor = this.activeColor ?? themeData.primaryColor; + Color color = this.color ?? themeData.scaffoldBackgroundColor; + + if (Axis.vertical == config.scrollDirection) { + return Column( + key: key, + mainAxisSize: MainAxisSize.min, + children: [ + Text( + "${config.activeIndex + 1}", + style: TextStyle(color: activeColor, fontSize: activeFontSize), + ), + Text( + "/", + style: TextStyle(color: color, fontSize: fontSize), + ), + Text( + "$itemCount", + style: TextStyle(color: color, fontSize: fontSize), + ) + ], + ); + } else { + return Row( + key: key, + mainAxisSize: MainAxisSize.min, + children: [ + Text( + "${config.activeIndex + 1}", + style: TextStyle(color: activeColor, fontSize: activeFontSize), + ), + Text( + " / $itemCount", + style: TextStyle(color: color, fontSize: fontSize), + ) + ], + ); + } + } +} + +class RectSwiperPaginationBuilder extends SwiperPlugin { + ///color when current index,if set null , will be Theme.of(context).primaryColor + final Color? activeColor; + + ///,if set null , will be Theme.of(context).scaffoldBackgroundColor + final Color? color; + + ///Size of the rect when activate + final Size activeSize; + + ///Size of the rect + final Size size; + + /// Space between rects + final double space; + + final Key? key; + + const RectSwiperPaginationBuilder({ + this.activeColor, + this.color, + this.key, + this.size = const Size(10.0, 3.0), + this.activeSize = const Size(10.0, 3.0), + this.space = 2.0, + }); + + @override + Widget build(BuildContext context, SwiperPluginConfig config) { + int itemCount = config.itemCount; + if (itemCount <= 1) { + return Container(); + } + + ThemeData themeData = Theme.of(context); + Color activeColor = this.activeColor ?? themeData.primaryColor; + Color color = this.color ?? themeData.scaffoldBackgroundColor; + + List list = []; + + int activeIndex = config.activeIndex; + if (itemCount > 20) { + debugPrint( + "The itemCount is too big, we suggest use FractionPaginationBuilder instead of DotSwiperPaginationBuilder in this situation"); + } + + for (int i = 0; i < itemCount; ++i) { + bool active = i == activeIndex; + Size size = active ? activeSize : this.size; + list.add(Container( + width: size.width, + height: size.height, + key: Key("pagination_$i"), + margin: EdgeInsets.all(space), + decoration: BoxDecoration( + borderRadius: BorderRadius.circular(1.5), + color: active ? activeColor : color, + ), + )); + } + + if (config.scrollDirection == Axis.vertical) { + return Column( + key: key, + mainAxisSize: MainAxisSize.min, + children: list, + ); + } else { + return Row( + key: key, + mainAxisSize: MainAxisSize.min, + children: list, + ); + } + } +} + +class DotSwiperPaginationBuilder extends SwiperPlugin { + ///color when current index,if set null , will be Theme.of(context).primaryColor + final Color? activeColor; + + ///,if set null , will be Theme.of(context).scaffoldBackgroundColor + final Color? color; + + ///Size of the dot when activate + final double activeSize; + + ///Size of the dot + final double size; + + /// Space between dots + final double space; + + final Key? key; + + const DotSwiperPaginationBuilder({ + this.activeColor, + this.color, + this.key, + this.size = 10.0, + this.activeSize = 10.0, + this.space = 3.0, + }); + + @override + Widget build(BuildContext context, SwiperPluginConfig config) { + + int itemCount = config.itemCount; + if (itemCount <= 1) { + return Container(); + } + + if (config.itemCount > 20) { + debugPrint( + "The itemCount is too big, we suggest use FractionPaginationBuilder instead of DotSwiperPaginationBuilder in this sitituation"); + } + Color? activeColor = this.activeColor; + Color? color = this.color; + + if (activeColor == null || color == null) { + ThemeData themeData = Theme.of(context); + activeColor = this.activeColor ?? themeData.primaryColor; + color = this.color ?? themeData.scaffoldBackgroundColor; + } + + if (config.indicatorLayout != PageIndicatorLayout.NONE && + config.layout == SwiperLayout.DEFAULT) { + return PageIndicator( + count: config.itemCount, + controller: config.pageController!, + layout: config.indicatorLayout, + size: size, + activeColor: activeColor, + color: color, + space: space, + ); + } + + List list = []; + + int? activeIndex = config.activeIndex; + + for (int i = 0; i < itemCount; ++i) { + bool active = i == activeIndex; + list.add(Container( + key: Key("pagination_$i"), + margin: EdgeInsets.all(space), + child: ClipOval( + child: Container( + color: active ? activeColor : color, + width: active ? activeSize : size, + height: active ? activeSize : size, + ), + ), + )); + } + + if (config.scrollDirection == Axis.vertical) { + return Column( + key: key, + mainAxisSize: MainAxisSize.min, + children: list, + ); + } else { + return Row( + key: key, + mainAxisSize: MainAxisSize.min, + children: list, + ); + } + } +} + +typedef SwiperPaginationBuilder = Widget Function( + BuildContext context, SwiperPluginConfig config); + +class SwiperCustomPagination extends SwiperPlugin { + final SwiperPaginationBuilder builder; + + const SwiperCustomPagination({required this.builder}); + + @override + Widget build(BuildContext context, SwiperPluginConfig config) { + return builder(context, config); + } +} + +class SwiperPagination extends SwiperPlugin { + /// dot style pagination + static const SwiperPlugin dots = DotSwiperPaginationBuilder(); + + /// fraction style pagination + static const SwiperPlugin fraction = FractionPaginationBuilder(); + + /// round rect style pagination + static const SwiperPlugin rect = RectSwiperPaginationBuilder(); + + /// Alignment.bottomCenter by default when scrollDirection== Axis.horizontal + /// Alignment.centerRight by default when scrollDirection== Axis.vertical + final Alignment? alignment; + + /// Distance between pagination and the container + final EdgeInsetsGeometry margin; + + /// Build the widget + final SwiperPlugin builder; + + final Key? key; + + const SwiperPagination({ + this.alignment, + this.key, + this.margin = const EdgeInsets.all(10.0), + this.builder = SwiperPagination.dots, + }); + + @override + Widget build(BuildContext context, SwiperPluginConfig config) { + Alignment defaultAlignment = config.scrollDirection == Axis.horizontal + ? Alignment.bottomCenter + : Alignment.centerRight; + Widget child = Container( + margin: margin, + child: builder.build(context, config), + ); + if (!config.outer!) { + child = Align( + key: key, + alignment: alignment ?? defaultAlignment, + child: child, + ); + } + return child; + } +} diff --git a/package/flutter_swiper_view/lib/src/swiper_plugin.dart b/package/flutter_swiper_view/lib/src/swiper_plugin.dart new file mode 100644 index 00000000..1a6fe003 --- /dev/null +++ b/package/flutter_swiper_view/lib/src/swiper_plugin.dart @@ -0,0 +1,52 @@ +import 'package:flutter/widgets.dart'; +import 'package:flutter_swiper_view/flutter_swiper_view.dart'; + +/// plugin to display swiper components +/// +abstract class SwiperPlugin { + const SwiperPlugin(); + + Widget build(BuildContext context, SwiperPluginConfig config); +} + +class SwiperPluginConfig { + final Axis scrollDirection; + final AxisDirection? axisDirection; + final SwiperController controller; + final int activeIndex; + final int itemCount; + final PageIndicatorLayout? indicatorLayout; + final bool loop; + final bool? outer; + final PageController? pageController; + final SwiperLayout? layout; + + const SwiperPluginConfig({ + required this.scrollDirection, + required this.controller, + required this.activeIndex, + required this.itemCount, + this.axisDirection, + this.indicatorLayout, + this.outer, + this.pageController, + this.layout, + this.loop = false, + }); +} + +class SwiperPluginView extends StatelessWidget { + final SwiperPlugin plugin; + final SwiperPluginConfig config; + + const SwiperPluginView({ + Key? key, + required this.plugin, + required this.config, + }) : super(key: key); + + @override + Widget build(BuildContext context) { + return plugin.build(context, config); + } +} diff --git a/package/flutter_swiper_view/lib/src/transformer_page_view.dart b/package/flutter_swiper_view/lib/src/transformer_page_view.dart new file mode 100644 index 00000000..a970b3f2 --- /dev/null +++ b/package/flutter_swiper_view/lib/src/transformer_page_view.dart @@ -0,0 +1,595 @@ +import 'package:flutter/widgets.dart'; + +import 'index_controller.dart'; + +/// +/// NOTICE:: +/// +/// In order to make package smaller,currently we're not supporting any build-in page transformers +/// You can find build in transforms here: +/// +/// +/// + +const int kMaxValue = 2000000000; +const int kMiddleValue = 1000000000; + +/// Default auto play transition duration (in millisecond) +const int kDefaultTransactionDuration = 300; + +class TransformInfo { + /// The `width` of the `TransformerPageView` + final double? width; + + /// The `height` of the `TransformerPageView` + final double? height; + + /// The `position` of the widget pass to [PageTransformer.transform] + /// A `position` describes how visible the widget is. + /// The widget in the center of the screen' which is full visible, position is 0.0. + /// The widge in the left ,may be hidden, of the screen's position is less than 0.0, -1.0 when out of the screen. + /// The widge in the right ,may be hidden, of the screen's position is greater than 0.0, 1.0 when out of the screen + /// + /// + final double? position; + + /// The `index` of the widget pass to [PageTransformer.transform] + final int? index; + + /// The `activeIndex` of the PageView + final int? activeIndex; + + /// The `activeIndex` of the PageView, from user start to swipe + /// It will change when user end drag + final int fromIndex; + + /// Next `index` is greater than this `index` + final bool? forward; + + /// User drag is done. + final bool? done; + + /// Same as [TransformerPageView.viewportFraction] + final double? viewportFraction; + + /// Copy from [TransformerPageView.scrollDirection] + final Axis? scrollDirection; + + TransformInfo({ + this.index, + this.position, + this.width, + this.height, + this.activeIndex, + required this.fromIndex, + this.forward, + this.done, + this.viewportFraction, + this.scrollDirection, + }); +} + +abstract class PageTransformer { + /// + final bool reverse; + + PageTransformer({this.reverse = false}); + + /// Return a transformed widget, based on child and TransformInfo + Widget transform(Widget child, TransformInfo info); +} + +typedef PageTransformerBuilderCallback = Widget Function( + Widget child, TransformInfo info); + +class PageTransformerBuilder extends PageTransformer { + final PageTransformerBuilderCallback builder; + + PageTransformerBuilder({bool reverse = false, required this.builder}) + : super(reverse: reverse); + + @override + Widget transform(Widget child, TransformInfo info) { + return builder(child, info); + } +} + +class TransformerPageController extends PageController { + final bool loop; + final int itemCount; + final bool reverse; + + TransformerPageController({ + int initialPage = 0, + bool keepPage = true, + double viewportFraction = 1.0, + this.loop = false, + this.itemCount = 0, + this.reverse = false, + }) : super( + initialPage: TransformerPageController._getRealIndexFromRenderIndex( + initialPage, loop, itemCount, reverse), + keepPage: keepPage, + viewportFraction: viewportFraction, + ); + + int getRenderIndexFromRealIndex(num index) { + return _getRenderIndexFromRealIndex(index, loop, itemCount, reverse); + } + + int? getRealItemCount() { + if (itemCount == 0) return 0; + return loop ? itemCount + kMaxValue : itemCount; + } + + static int _getRenderIndexFromRealIndex( + num index, + bool loop, + int itemCount, + bool reverse, + ) { + if (itemCount == 0) return 0; + int renderIndex; + if (loop) { + renderIndex = (index - kMiddleValue).toInt(); + renderIndex = renderIndex % itemCount; + if (renderIndex < 0) { + renderIndex += itemCount; + } + } else { + renderIndex = index.toInt(); + } + if (reverse) { + renderIndex = itemCount - renderIndex - 1; + } + + return renderIndex; + } + + double get realPage => super.page ?? 0.0; + + static double? _getRenderPageFromRealPage( + double page, + bool loop, + int itemCount, + bool reverse, + ) { + double? renderPage; + if (loop) { + renderPage = page - kMiddleValue; + renderPage = renderPage % itemCount; + if (renderPage < 0) { + renderPage += itemCount; + } + } else { + renderPage = page; + } + if (reverse) { + renderPage = itemCount - renderPage - 1; + } + + return renderPage; + } + + @override + double? get page { + return loop + ? _getRenderPageFromRealPage(realPage, loop, itemCount, reverse) + : realPage; + } + + int getRealIndexFromRenderIndex(num index) { + return _getRealIndexFromRenderIndex(index, loop, itemCount, reverse); + } + + static int _getRealIndexFromRenderIndex( + num index, bool loop, int itemCount, bool reverse) { + int result = reverse ? itemCount - index - 1 as int : index as int; + if (loop) { + result += kMiddleValue; + } + return result; + } +} + +class TransformerPageView extends StatefulWidget { + /// Create a `transformed` widget base on the widget that has been passed to the [PageTransformer.transform]. + /// See [TransformInfo] + /// + final PageTransformer? transformer; + + /// Same as [PageView.scrollDirection] + /// + /// Defaults to [Axis.horizontal]. + final Axis scrollDirection; + + /// Same as [PageView.physics] + final ScrollPhysics? physics; + + /// Set to false to disable page snapping, useful for custom scroll behavior. + /// Same as [PageView.pageSnapping] + final bool pageSnapping; + + /// Called whenever the page in the center of the viewport changes. + /// Same as [PageView.onPageChanged] + final ValueChanged? onPageChanged; + + final IndexedWidgetBuilder? itemBuilder; + + // See [IndexController.mode],[IndexController.next],[IndexController.previous] + final IndexController? controller; + + /// Animation duration + final Duration duration; + + /// Animation curve + final Curve curve; + + final TransformerPageController? pageController; + + /// Set true to open infinity loop mode. + final bool loop; + + /// This value is only valid when `pageController` is not set, + final int itemCount; + + /// This value is only valid when `pageController` is not set, + final double viewportFraction; + + /// If not set, it is controlled by this widget. + final int? index; + + final bool allowImplicitScrolling; + + /// Creates a scrollable list that works page by page using widgets that are + /// created on demand. + /// + /// This constructor is appropriate for page views with a large (or infinite) + /// number of children because the builder is called only for those children + /// that are actually visible. + /// + /// Providing a non-null [itemCount] lets the [PageView] compute the maximum + /// scroll extent. + /// + /// [itemBuilder] will be called only with indices greater than or equal to + /// zero and less than [itemCount]. + const TransformerPageView({ + Key? key, + this.index, + Duration? duration, + this.curve = Curves.ease, + this.viewportFraction = 1.0, + required this.loop, + this.scrollDirection = Axis.horizontal, + this.physics, + this.pageSnapping = true, + this.onPageChanged, + this.controller, + this.transformer, + this.allowImplicitScrolling = false, + this.itemBuilder, + this.pageController, + required this.itemCount, + }) : assert(itemCount == 0 || itemBuilder != null || transformer != null), + duration = duration ?? + const Duration(milliseconds: kDefaultTransactionDuration), + super(key: key); + + factory TransformerPageView.children({ + Key? key, + int? index, + Duration? duration, + Curve curve = Curves.ease, + double viewportFraction = 1.0, + bool loop = false, + Axis scrollDirection = Axis.horizontal, + ScrollPhysics? physics, + bool pageSnapping = true, + ValueChanged? onPageChanged, + IndexController? controller, + PageTransformer? transformer, + bool allowImplicitScrolling = false, + required List children, + TransformerPageController? pageController, + }) { + return TransformerPageView( + itemCount: children.length, + itemBuilder: (context, index) { + return children[index]; + }, + pageController: pageController, + transformer: transformer, + pageSnapping: pageSnapping, + key: key, + index: index, + loop: loop, + duration: duration, + curve: curve, + viewportFraction: viewportFraction, + scrollDirection: scrollDirection, + physics: physics, + allowImplicitScrolling: allowImplicitScrolling, + onPageChanged: onPageChanged, + controller: controller, + ); + } + + @override + State createState() => _TransformerPageViewState(); + + static int getRealIndexFromRenderIndex({ + required bool reverse, + int index = 0, + int itemCount = 0, + required bool loop, + }) { + int initPage = reverse ? (itemCount - index - 1) : index; + if (loop) { + initPage += kMiddleValue; + } + return initPage; + } + + static PageController createPageController({ + required bool reverse, + int index = 0, + int itemCount = 0, + required bool loop, + required double viewportFraction, + }) { + return PageController( + initialPage: getRealIndexFromRenderIndex( + reverse: reverse, + index: index, + itemCount: itemCount, + loop: loop, + ), + viewportFraction: viewportFraction, + ); + } +} + +class _TransformerPageViewState extends State { + Size? _size; + int _activeIndex = 0; + late double _currentPixels; + bool _done = false; + + ///This value will not change until user end drag. + late int _fromIndex; + + PageTransformer? _transformer; + + late TransformerPageController _pageController; + + Widget _buildItemNormal(BuildContext context, int index) { + int renderIndex = _pageController.getRenderIndexFromRealIndex(index); + return widget.itemBuilder!(context, renderIndex); + } + + Widget _buildItem(BuildContext context, int index) { + return AnimatedBuilder( + animation: _pageController, + builder: (c, w) { + int renderIndex = _pageController.getRenderIndexFromRealIndex(index); + Widget child = widget.itemBuilder?.call(context, renderIndex) ?? + const SizedBox.shrink(); + if (_size == null) { + return child; + } + + double position; + + double page = _pageController.realPage; + + if (_transformer!.reverse) { + position = page - index; + } else { + position = index - page; + } + position *= widget.viewportFraction; + + TransformInfo info = TransformInfo( + index: renderIndex, + width: _size!.width, + height: _size!.height, + position: position.clamp(-1.0, 1.0), + activeIndex: + _pageController.getRenderIndexFromRealIndex(_activeIndex), + fromIndex: _fromIndex, + forward: _pageController.position.pixels - _currentPixels >= 0, + done: _done, + scrollDirection: widget.scrollDirection, + viewportFraction: widget.viewportFraction, + ); + return _transformer!.transform(child, info); + }); + } + + double? _calcCurrentPixels() { + _currentPixels = _pageController.getRenderIndexFromRealIndex(_activeIndex) * + _pageController.position.viewportDimension * + widget.viewportFraction; + + // print("activeIndex:$_activeIndex , pix:$_currentPixels"); + + return _currentPixels; + } + + @override + Widget build(BuildContext context) { + IndexedWidgetBuilder builder = + _transformer == null ? _buildItemNormal : _buildItem; + Widget child = PageView.builder( + allowImplicitScrolling: widget.allowImplicitScrolling, + itemBuilder: builder, + itemCount: _pageController.getRealItemCount(), + onPageChanged: _onIndexChanged, + controller: _pageController, + scrollDirection: widget.scrollDirection, + physics: widget.physics, + pageSnapping: widget.pageSnapping, + reverse: _pageController.reverse, + ); + if (_transformer == null) { + return child; + } + return NotificationListener( + onNotification: (notification) { + if (notification is ScrollStartNotification) { + _calcCurrentPixels(); + _done = false; + _fromIndex = _activeIndex; + } else if (notification is ScrollEndNotification) { + _calcCurrentPixels(); + _fromIndex = _activeIndex; + _done = true; + } + + return false; + }, + child: child, + ); + } + + void _onIndexChanged(int index) { + _activeIndex = index; + widget.onPageChanged + ?.call(_pageController.getRenderIndexFromRealIndex(index)); + } + + void _onGetSize(Duration _) { + if (!mounted) return; + Size? size; + RenderObject? renderObject = context.findRenderObject(); + if (renderObject != null) { + Rect bounds = renderObject.paintBounds; + size = bounds.size; + } + _calcCurrentPixels(); + onGetSize(size); + } + + void onGetSize(Size? size) { + if (mounted) { + setState(() { + _size = size; + }); + } + } + + IndexController? _controller; + + @override + void initState() { + _transformer = widget.transformer; + // int index = widget.index ?? 0; + _pageController = widget.pageController ?? + TransformerPageController( + initialPage: widget.index ?? 0, + itemCount: widget.itemCount, + loop: widget.loop, + reverse: widget.transformer?.reverse ?? false, + ); + // int initPage = _getRealIndexFromRenderIndex(index); + // _pageController = PageController(initialPage: initPage,viewportFraction: widget.viewportFraction); + _fromIndex = _activeIndex = _pageController.initialPage; + + _controller = widget.controller; + _controller?.addListener(onChangeNotifier); + super.initState(); + } + + @override + void didUpdateWidget(TransformerPageView oldWidget) { + _transformer = widget.transformer; + int index = widget.index ?? 0; + bool created = false; + if (_pageController != widget.pageController) { + if (widget.pageController != null) { + _pageController = widget.pageController!; + } else { + created = true; + _pageController = TransformerPageController( + initialPage: widget.index ?? 0, + itemCount: widget.itemCount, + loop: widget.loop, + reverse: widget.transformer?.reverse ?? false, + ); + } + } + + if (_pageController.getRenderIndexFromRealIndex(_activeIndex) != index) { + _fromIndex = _activeIndex = _pageController.initialPage; + if (!created) { + int initPage = _pageController.getRealIndexFromRenderIndex(index); + if (_pageController.hasClients) { + _pageController.animateToPage( + initPage, + duration: widget.duration, + curve: widget.curve, + ); + } + } + } + if (_transformer != null) { + WidgetsBinding.instance.addPostFrameCallback(_onGetSize); + } + + if (_controller != widget.controller) { + _controller?.removeListener(onChangeNotifier); + _controller = widget.controller; + _controller?.addListener(onChangeNotifier); + } + super.didUpdateWidget(oldWidget); + } + + @override + void didChangeDependencies() { + if (_transformer != null) { + WidgetsBinding.instance.addPostFrameCallback(_onGetSize); + } + super.didChangeDependencies(); + } + + void onChangeNotifier() { + final controller = widget.controller!; + final event = controller.event; + int index; + if (event == null) return; + if (event is MoveIndexControllerEvent) { + index = _pageController.getRealIndexFromRenderIndex(event.newIndex); + } else if (event is StepBasedIndexControllerEvent) { + index = event.calcNextIndex( + currentIndex: _activeIndex, + itemCount: _pageController.itemCount, + loop: _pageController.loop, + reverse: _pageController.reverse, + ); + } else { + //ignore other events + return; + } + if (_pageController.hasClients) { + if (event.animation) { + _pageController + .animateToPage( + index, + duration: widget.duration, + curve: widget.curve, + ) + .whenComplete(event.complete); + } else { + event.complete(); + } + } else { + event.complete(); + } + } + + @override + void dispose() { + _controller?.removeListener(onChangeNotifier); + super.dispose(); + } +} diff --git a/package/flutter_swiper_view/pubspec.yaml b/package/flutter_swiper_view/pubspec.yaml new file mode 100644 index 00000000..a9de80ee --- /dev/null +++ b/package/flutter_swiper_view/pubspec.yaml @@ -0,0 +1,20 @@ +name: flutter_swiper_view +description: The best swiper(carousel) for flutter, with multiple layouts, infinite loop. Compatible with Android & iOS. +version: 1.1.8 +homepage: https://github.com/feicien/flutter_swiper_view + +dependencies: + flutter: + sdk: flutter + + +environment: + sdk: '>=2.12.0 <3.0.0' + flutter: ">=1.17.0" + +dev_dependencies: + flutter_lints: ^2.0.1 + flutter_test: + sdk: flutter + +flutter: diff --git a/package/flutter_swiper_view/test/control_test.dart b/package/flutter_swiper_view/test/control_test.dart new file mode 100644 index 00000000..7766de47 --- /dev/null +++ b/package/flutter_swiper_view/test/control_test.dart @@ -0,0 +1,69 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_swiper_view/flutter_swiper_view.dart'; +import 'package:flutter_test/flutter_test.dart'; + +void main() { + testWidgets('Control horizontal', (WidgetTester tester) async { + SwiperController controller = SwiperController(); + + SwiperPluginConfig config = SwiperPluginConfig( + activeIndex: 0, + controller: controller, + itemCount: 10, + loop: true, + scrollDirection: Axis.horizontal); + + Key key = UniqueKey(); + await tester.pumpWidget(MaterialApp( + home: Scaffold(body: Builder(builder: (BuildContext context) { + return SwiperControl(key: key).build(context, config); + })), + )); + + expect(find.byKey(key), findsOneWidget); + + bool first = true; + + await tester.tap(find.byWidgetPredicate((Widget widget) { + if (widget is GestureDetector && first) { + first = false; + return true; + } + + return false; + })); + }); + + testWidgets('Control vertical', (WidgetTester tester) async { + SwiperController controller = SwiperController(); + + SwiperPluginConfig config = SwiperPluginConfig( + activeIndex: 0, + controller: controller, + itemCount: 10, + loop: true, + scrollDirection: Axis.vertical); + + Key key = UniqueKey(); + await tester.pumpWidget(MaterialApp( + home: Scaffold(body: Builder(builder: (BuildContext context) { + return SwiperControl( + key: key, color: Colors.white, disableColor: Colors.black87) + .build(context, config); + })), + )); + + expect(find.byKey(key), findsOneWidget); + + bool first = true; + + await tester.tap(find.byWidgetPredicate((Widget widget) { + if (widget is GestureDetector && first) { + first = false; + return true; + } + + return false; + })); + }); +} diff --git a/package/flutter_swiper_view/test/flutter_swiper_test.dart b/package/flutter_swiper_view/test/flutter_swiper_test.dart new file mode 100644 index 00000000..4e26daca --- /dev/null +++ b/package/flutter_swiper_view/test/flutter_swiper_test.dart @@ -0,0 +1,163 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_swiper_view/flutter_swiper_view.dart'; +import 'package:flutter_test/flutter_test.dart'; + +void main() { + testWidgets('Default Swiper', (WidgetTester tester) async { + // Build our app and trigger a frame. + await tester.pumpWidget(MaterialApp( + home: Swiper( + itemBuilder: (context, index) { + return const Text("0"); + }, + itemCount: 10))); + + expect(find.text("0", skipOffstage: false), findsOneWidget); + }); + + testWidgets('Default Swiper loop:false', (WidgetTester tester) async { + // Build our app and trigger a frame. + await tester.pumpWidget(MaterialApp( + home: Swiper( + onTap: (int inde) {}, + itemBuilder: (context, index) { + return const Text("0"); + }, + itemCount: 10, + loop: false, + ))); + + expect(find.text("0", skipOffstage: true), findsOneWidget); + }); + + testWidgets('Create Swiper with children', (WidgetTester tester) async { + // Build our app and trigger a frame. + await tester.pumpWidget(MaterialApp( + home: Swiper.children( + children: const [Text("0"), Text("1")], + ))); + + expect(find.text("0", skipOffstage: false), findsOneWidget); + }); + + testWidgets('Create Swiper with list', (WidgetTester tester) async { + // Build our app and trigger a frame. + await tester.pumpWidget(MaterialApp( + home: Swiper.list( + list: const ["0", "1"], + builder: (BuildContext context, dynamic data, int index) { + return Text(data); + }, + ))); + + expect(find.text("0", skipOffstage: false), findsOneWidget); + }); + + testWidgets('Swiper with default plugins', (WidgetTester tester) async { + // Build our app and trigger a frame. + SwiperController controller = SwiperController(); + await tester.pumpWidget(MaterialApp( + home: Swiper( + controller: controller, + itemBuilder: (context, index) { + return const Text("0"); + }, + itemCount: 10, + pagination: const SwiperPagination(), + control: const SwiperControl(), + ))); + + expect(find.text("0", skipOffstage: false), findsOneWidget); + }); + + const List titles = [ + "Flutter Swiper is awosome", + "Really nice", + "Yeap" + ]; + + testWidgets('Customize pagination', (WidgetTester tester) async { + // Build our app and trigger a frame. + SwiperController controller = SwiperController(); + await tester.pumpWidget(MaterialApp( + home: Swiper( + controller: controller, + itemBuilder: (context, index) { + return const Text("0"); + }, + itemCount: 10, + pagination: SwiperCustomPagination( + builder: (BuildContext context, SwiperPluginConfig config) { + return ConstrainedBox( + constraints: const BoxConstraints.expand(height: 50.0), + child: Row( + children: [ + Text( + "${titles[config.activeIndex]} ${config.activeIndex + 1}/${config.itemCount}", + style: const TextStyle(fontSize: 20.0), + ), + Expanded( + child: Align( + alignment: Alignment.centerRight, + child: const DotSwiperPaginationBuilder( + color: Colors.black12, + activeColor: Colors.black, + size: 10.0, + activeSize: 20.0) + .build(context, config), + ), + ) + ], + ), + ); + }), + control: const SwiperControl(), + ))); + + controller.startAutoplay(); + + controller.stopAutoplay(); + + await controller.move(0, animation: false); + await controller.move(0, animation: false); + + await controller.next(animation: false); + await controller.previous(animation: false); + + expect(find.text("0", skipOffstage: false), findsOneWidget); + }); + + testWidgets('Swiper fraction', (WidgetTester tester) async { + // Build our app and trigger a frame. + SwiperController controller = SwiperController(); + await tester.pumpWidget(MaterialApp( + home: Swiper( + controller: controller, + itemBuilder: (context, index) { + return const Text("0"); + }, + itemCount: 10, + pagination: const SwiperPagination(builder: SwiperPagination.fraction), + control: const SwiperControl(), + ))); + + expect(find.text("0", skipOffstage: false), findsOneWidget); + }); + + testWidgets('Zero itemCount', (WidgetTester tester) async { + // Build our app and trigger a frame. + SwiperController controller = SwiperController(); + await tester.pumpWidget(MaterialApp( + home: Swiper( + controller: controller, + itemBuilder: (context, index) { + return const Text("0"); + }, + itemCount: 0, + pagination: const SwiperPagination(builder: SwiperPagination.fraction), + control: const SwiperControl(), + ))); + + expect(find.text("0", skipOffstage: false), findsNothing); + }); +} diff --git a/package/flutter_swiper_view/test/layout_test.dart b/package/flutter_swiper_view/test/layout_test.dart new file mode 100644 index 00000000..1120101c --- /dev/null +++ b/package/flutter_swiper_view/test/layout_test.dart @@ -0,0 +1,82 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_swiper_view/flutter_swiper_view.dart'; +import 'package:flutter_test/flutter_test.dart'; + +void main() { + testWidgets('STACK', (WidgetTester tester) async { + await tester.pumpWidget(MaterialApp( + home: Swiper( + layout: SwiperLayout.STACK, + itemWidth: 300.0, + itemHeight: 200.0, + itemBuilder: (context, index) { + return Container( + color: Colors.grey, + child: Center( + child: Text("$index"), + ), + ); + }, + itemCount: 10))); + }); + + testWidgets('TINDER', (WidgetTester tester) async { + await tester.pumpWidget(MaterialApp( + home: Swiper( + layout: SwiperLayout.TINDER, + itemWidth: 300.0, + itemHeight: 200.0, + itemBuilder: (context, index) { + return Container( + color: Colors.grey, + child: Center( + child: Text("$index"), + ), + ); + }, + itemCount: 10))); + }); + + testWidgets('DEFAULT', (WidgetTester tester) async { + await tester.pumpWidget(MaterialApp( + home: Swiper( + layout: SwiperLayout.DEFAULT, + viewportFraction: 0.8, + scale: 0.9, + itemBuilder: (context, index) { + return Container( + color: Colors.grey, + child: Center( + child: Text("$index"), + ), + ); + }, + itemCount: 10))); + }); + + testWidgets('CUSTOM', (WidgetTester tester) async { + CustomLayoutOption customLayoutOption; + customLayoutOption = CustomLayoutOption(startIndex: -1, stateCount: 3) + ..addRotate([-45.0 / 180, 0.0, 45.0 / 180]) + ..addTranslate([ + const Offset(-370.0, -40.0), + const Offset(0.0, 0.0), + const Offset(370.0, -40.0) + ]); + await tester.pumpWidget(MaterialApp( + home: Swiper( + layout: SwiperLayout.CUSTOM, + itemWidth: 300.0, + itemHeight: 200.0, + customLayoutOption: customLayoutOption, + itemBuilder: (context, index) { + return Container( + color: Colors.grey, + child: Center( + child: Text("$index"), + ), + ); + }, + itemCount: 10))); + }); +} diff --git a/package/flutter_swiper_view/test/pagination_test.dart b/package/flutter_swiper_view/test/pagination_test.dart new file mode 100644 index 00000000..f34551ab --- /dev/null +++ b/package/flutter_swiper_view/test/pagination_test.dart @@ -0,0 +1,129 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_swiper_view/flutter_swiper_view.dart'; +import 'package:flutter_test/flutter_test.dart'; + +void main() { + testWidgets('Pagination', (WidgetTester tester) async { + SwiperController controller = SwiperController(); + + SwiperPluginConfig config = SwiperPluginConfig( + activeIndex: 0, + controller: controller, + itemCount: 10, + scrollDirection: Axis.horizontal); + + Key key = UniqueKey(); + await tester.pumpWidget(MaterialApp( + home: Scaffold(body: Builder(builder: (BuildContext context) { + return DotSwiperPaginationBuilder( + key: key, + activeColor: const Color(0xff000000), + color: const Color(0xffffffff), + space: 10.0, + size: 10.0, + activeSize: 20.0) + .build(context, config); + })), + )); + + for (int i = 0; i < 10; ++i) { + expect(find.byWidgetPredicate((Widget widget) { + if (widget.key != null && + widget.key is ValueKey && + (widget.key as ValueKey).value == 'pagination_$i') return true; + + return false; + }), findsOneWidget); + } + + expect(find.byKey(key), findsOneWidget); + }); + + testWidgets('Pagination vertical', (WidgetTester tester) async { + SwiperController controller = SwiperController(); + + SwiperPluginConfig config = SwiperPluginConfig( + activeIndex: 0, + controller: controller, + itemCount: 10, + scrollDirection: Axis.vertical); + + Key key = UniqueKey(); + await tester.pumpWidget(MaterialApp( + home: Scaffold(body: Builder(builder: (BuildContext context) { + return DotSwiperPaginationBuilder( + key: key, + activeColor: const Color(0xff000000), + color: const Color(0xffffffff), + space: 10.0, + size: 10.0, + activeSize: 20.0) + .build(context, config); + })), + )); + + for (int i = 0; i < 10; ++i) { + expect(find.byWidgetPredicate((Widget widget) { + if (widget.key != null && + widget.key is ValueKey && + (widget.key as ValueKey).value == 'pagination_$i') return true; + + return false; + }), findsOneWidget); + } + + expect(find.byKey(key), findsOneWidget); + }); + + testWidgets('Pagination fraction', (WidgetTester tester) async { + SwiperController controller = SwiperController(); + + SwiperPluginConfig config = SwiperPluginConfig( + activeIndex: 0, + controller: controller, + itemCount: 10, + scrollDirection: Axis.horizontal); + + Key key = UniqueKey(); + await tester.pumpWidget(MaterialApp( + home: Scaffold(body: Builder(builder: (BuildContext context) { + return FractionPaginationBuilder( + key: key, + activeColor: const Color(0xff000000), + color: const Color(0xffffffff), + ).build(context, config); + })), + )); + + expect(find.text("1"), findsOneWidget); + expect(find.text(" / 10"), findsOneWidget); + + expect(find.byKey(key), findsOneWidget); + }); + + testWidgets('Pagination fraction vertical', (WidgetTester tester) async { + SwiperController controller = SwiperController(); + + SwiperPluginConfig config = SwiperPluginConfig( + activeIndex: 0, + controller: controller, + itemCount: 10, + scrollDirection: Axis.vertical); + + Key key = UniqueKey(); + await tester.pumpWidget(MaterialApp( + home: Scaffold(body: Builder(builder: (BuildContext context) { + return FractionPaginationBuilder( + key: key, + activeColor: const Color(0xff000000), + color: const Color(0xffffffff), + ).build(context, config); + })), + )); + + expect(find.text("1"), findsOneWidget); + expect(find.text("10"), findsOneWidget); + + expect(find.byKey(key), findsOneWidget); + }); +} diff --git a/pubspec.yaml b/pubspec.yaml index 3b112673..7f14f57c 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -2,8 +2,8 @@ name: hmg_patient_app_new description: "New HMG Patient App" publish_to: 'none' # Remove this line if you wish to publish to pub.dev -#version: 0.0.15+12 -version: 0.0.1+16 +version: 0.0.16+13 +#version: 0.0.1+16 environment: sdk: ">=3.6.0 <4.0.0" @@ -84,7 +84,8 @@ dependencies: keyboard_actions: ^4.2.0 path_provider: ^2.0.8 open_filex: ^4.7.0 - flutter_swiper_view: ^1.1.8 + flutter_swiper_view: + path: package/flutter_swiper_view flutter_callkit_incoming: ^3.0.0 in_app_update: ^4.2.5 # device_preview: ^1.3.1 @@ -96,6 +97,7 @@ dependencies: intl: ^0.20.2 flutter_widget_from_html: ^0.17.1 huawei_map: ^6.12.0+301 + flutter_image_compress: ^2.3.0 scrollable_positioned_list: ^0.3.8 @@ -126,6 +128,19 @@ flutter: - assets/animations/lottie/ fonts: + - family: CairoArabic + fonts: + - asset: assets/fonts/cairo/Cairo-ExtraBold.ttf + weight: 700 + - asset: assets/fonts/cairo/Cairo-Bold.ttf + weight: 600 + - asset: assets/fonts/cairo/Cairo-SemiBold.ttf + weight: 500 + - asset: assets/fonts/cairo/Cairo-Medium.ttf + weight: 400 + - asset: assets/fonts/cairo/Cairo-Regular.ttf + weight: 300 + - family: Poppins fonts: # - asset: assets/fonts/poppins/Poppins-Black.ttf @@ -148,7 +163,7 @@ flutter: # weight: 100 - - family: GESSTwo + - family: GESSTwo22 fonts: - asset: assets/fonts/gess_two/GE_SS_Two_Bold.otf weight: 600