diff --git a/assets/animations/lottie/ErrorAnimation.json b/assets/animations/lottie/ErrorAnimation.json new file mode 100644 index 0000000..a9fc775 --- /dev/null +++ b/assets/animations/lottie/ErrorAnimation.json @@ -0,0 +1 @@ +{"nm":"Bouncy Fail","ddd":0,"h":512,"w":512,"meta":{"g":"@lottiefiles/toolkit-js 0.33.2"},"layers":[{"ty":4,"nm":"X line 2","sr":1,"st":0,"op":60,"ip":26,"hd":false,"ddd":0,"bm":0,"hasMask":false,"ao":0,"ks":{"a":{"a":0,"k":[41,-3,0],"ix":1},"s":{"a":0,"k":[100,100,100],"ix":6},"sk":{"a":0,"k":0},"p":{"a":0,"k":[256,256,0],"ix":2},"r":{"a":0,"k":90,"ix":10},"sa":{"a":0,"k":0},"o":{"a":0,"k":100,"ix":11}},"ef":[],"shapes":[{"ty":"gr","bm":0,"hd":false,"mn":"ADBE Vector Group","nm":"Shape 1","ix":1,"cix":2,"np":3,"it":[{"ty":"sh","bm":0,"hd":false,"mn":"ADBE Vector Shape - Group","nm":"Path 1","ix":1,"d":1,"ks":{"a":0,"k":{"c":false,"i":[[0,0],[0,0]],"o":[[0,0],[0,0]],"v":[[112,-74],[-30,68]]},"ix":2}},{"ty":"st","bm":0,"hd":false,"mn":"ADBE Vector Graphic - Stroke","nm":"Stroke 1","lc":2,"lj":2,"ml":1,"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":40,"ix":5},"c":{"a":0,"k":[1,1,1],"ix":3}},{"ty":"fl","bm":0,"hd":false,"mn":"ADBE Vector Graphic - Fill","nm":"Fill 1","c":{"a":0,"k":[0.8863,0.1176,0.1882],"ix":4},"r":1,"o":{"a":0,"k":100,"ix":5}},{"ty":"tr","a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"sk":{"a":0,"k":0,"ix":4},"p":{"a":0,"k":[0,0],"ix":2},"r":{"a":0,"k":0,"ix":6},"sa":{"a":0,"k":0,"ix":5},"o":{"a":0,"k":100,"ix":7}}]},{"ty":"tm","bm":0,"hd":false,"mn":"ADBE Vector Filter - Trim","nm":"Trim Paths 1","ix":2,"e":{"a":0,"k":100,"ix":2},"o":{"a":0,"k":0,"ix":3},"s":{"a":1,"k":[{"o":{"x":0.333,"y":0},"i":{"x":0.667,"y":1},"s":[100],"t":26},{"s":[0],"t":40}],"ix":1},"m":1}],"ind":1},{"ty":4,"nm":"X line 1","sr":1,"st":0,"op":60,"ip":26,"hd":false,"ddd":0,"bm":0,"hasMask":false,"ao":0,"ks":{"a":{"a":0,"k":[41,-3,0],"ix":1},"s":{"a":0,"k":[100,100,100],"ix":6},"sk":{"a":0,"k":0},"p":{"a":0,"k":[256,256,0],"ix":2},"r":{"a":0,"k":0,"ix":10},"sa":{"a":0,"k":0},"o":{"a":0,"k":100,"ix":11}},"ef":[],"shapes":[{"ty":"gr","bm":0,"hd":false,"mn":"ADBE Vector Group","nm":"Shape 1","ix":1,"cix":2,"np":3,"it":[{"ty":"sh","bm":0,"hd":false,"mn":"ADBE Vector Shape - Group","nm":"Path 1","ix":1,"d":1,"ks":{"a":0,"k":{"c":false,"i":[[0,0],[0,0]],"o":[[0,0],[0,0]],"v":[[112,-74],[-30,68]]},"ix":2}},{"ty":"st","bm":0,"hd":false,"mn":"ADBE Vector Graphic - Stroke","nm":"Stroke 1","lc":2,"lj":2,"ml":1,"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":40,"ix":5},"c":{"a":0,"k":[1,1,1],"ix":3}},{"ty":"fl","bm":0,"hd":false,"mn":"ADBE Vector Graphic - Fill","nm":"Fill 1","c":{"a":0,"k":[0.8863,0.1176,0.1882],"ix":4},"r":1,"o":{"a":0,"k":100,"ix":5}},{"ty":"tr","a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"sk":{"a":0,"k":0,"ix":4},"p":{"a":0,"k":[0,0],"ix":2},"r":{"a":0,"k":0,"ix":6},"sa":{"a":0,"k":0,"ix":5},"o":{"a":0,"k":100,"ix":7}}]},{"ty":"tm","bm":0,"hd":false,"mn":"ADBE Vector Filter - Trim","nm":"Trim Paths 1","ix":2,"e":{"a":1,"k":[{"o":{"x":0.333,"y":0},"i":{"x":0.667,"y":1},"s":[0],"t":26},{"s":[100],"t":40}],"ix":2},"o":{"a":0,"k":0,"ix":3},"s":{"a":0,"k":0,"ix":1},"m":1}],"ind":2},{"ty":4,"nm":"Circle 2","sr":1,"st":0,"op":360,"ip":10,"hd":false,"ddd":0,"bm":0,"hasMask":false,"ao":0,"ks":{"a":{"a":0,"k":[140.061,140.061,0],"ix":1},"s":{"a":1,"k":[{"o":{"x":0.167,"y":0.167},"i":{"x":0.667,"y":1},"s":[0,0,100],"t":10},{"s":[140,140,100],"t":20}],"ix":6,"x":"var $bm_rt;\nvar scaleInertialBounce, scaleBounceBack, n, n, t, t, v, amp, freq, decay, v, amp, freq, decay, e, g, nMax, e, g, nMax, n, n, t, v, vl, vu, vu, tCur, segDur, tNext, nb, delta;\nscaleInertialBounce = effect('Bounce & Drop - ukramedia.com')(16);\nscaleBounceBack = effect('Bounce & Drop - ukramedia.com')(17);\ntry {\n if (scaleInertialBounce == 1) {\n $bm_rt = n = 0;\n if (numKeys > 0) {\n $bm_rt = n = nearestKey(time).index;\n if (key(n).time > time) {\n n--;\n }\n }\n if (n == 0) {\n $bm_rt = t = 0;\n } else {\n $bm_rt = t = $bm_sub(time, key(n).time);\n }\n if (effect('Bounce & Drop - ukramedia.com')(58) == 1) {\n if (n > 0 && t < 1) {\n v = velocityAtTime($bm_sub(key(n).time, $bm_div(thisComp.frameDuration, 10)));\n amp = effect('Bounce & Drop - ukramedia.com')(59);\n freq = effect('Bounce & Drop - ukramedia.com')(60);\n decay = effect('Bounce & Drop - ukramedia.com')(61);\n $bm_rt = $bm_sum(value, $bm_div($bm_mul($bm_mul(v, amp), Math.sin($bm_mul($bm_mul($bm_mul(freq, t), 2), Math.PI))), Math.exp($bm_mul(decay, t))));\n } else {\n $bm_rt = value;\n }\n } else {\n if (n > 0 && t < 1) {\n v = velocityAtTime($bm_sub(key(n).time, $bm_div(thisComp.frameDuration, 10)));\n amp = effect('Bounce & Drop - ukramedia.com')(19);\n freq = effect('Bounce & Drop - ukramedia.com')(20);\n decay = effect('Bounce & Drop - ukramedia.com')(21);\n $bm_rt = $bm_sum(value, $bm_div($bm_mul($bm_mul(v, amp), Math.sin($bm_mul($bm_mul($bm_mul(freq, t), 2), Math.PI))), Math.exp($bm_mul(decay, t))));\n } else {\n $bm_rt = value;\n }\n }\n } else if (scaleBounceBack == 1) {\n if (effect('Bounce & Drop - ukramedia.com')(64) == 1) {\n e = effect('Bounce & Drop - ukramedia.com')(65);\n g = effect('Bounce & Drop - ukramedia.com')(66);\n nMax = effect('Bounce & Drop - ukramedia.com')(67);\n } else {\n e = effect('Bounce & Drop - ukramedia.com')(24);\n g = effect('Bounce & Drop - ukramedia.com')(25);\n nMax = effect('Bounce & Drop - ukramedia.com')(26);\n }\n $bm_rt = n = 0;\n if (numKeys > 0) {\n $bm_rt = n = nearestKey(time).index;\n if (key(n).time > time)\n n--;\n }\n if (n > 0) {\n t = $bm_sub(time, key(n).time);\n v = $bm_mul($bm_neg(velocityAtTime($bm_sub(key(n).time, 0.001))), e);\n vl = length(v);\n if ($bm_isInstanceOfArray(value)) {\n vu = vl > 0 ? normalize(v) : [\n 0,\n 0,\n 0\n ];\n } else {\n vu = v < 0 ? -1 : 1;\n }\n tCur = 0;\n segDur = $bm_div($bm_mul(2, vl), g);\n tNext = segDur;\n nb = 1;\n while (tNext < t && nb <= nMax) {\n vl *= e;\n segDur *= e;\n tCur = tNext;\n tNext = $bm_sum(tNext, segDur);\n nb++;\n }\n if (nb <= nMax) {\n delta = $bm_sub(t, tCur);\n $bm_rt = $bm_sum(value, $bm_mul($bm_mul(vu, delta), $bm_sub(vl, $bm_div($bm_mul(g, delta), 2))));\n } else {\n $bm_rt = value;\n }\n } else\n $bm_rt = value;\n } else {\n $bm_rt = value;\n }\n} catch (err) {\n $bm_rt = value;\n}"},"sk":{"a":0,"k":0},"p":{"a":0,"k":[256,256,0],"ix":2,"x":"var $bm_rt;\nvar positionInertialBounce, positionBounceBack, n, n, t, t, v, amp, freq, decay, v, amp, freq, decay, e, g, nMax, e, g, nMax, n, n, t, v, vl, vu, vu, tCur, segDur, tNext, nb, delta;\npositionInertialBounce = effect('Bounce & Drop - ukramedia.com')(2);\npositionBounceBack = effect('Bounce & Drop - ukramedia.com')(3);\ntry {\n if (positionInertialBounce == 1) {\n $bm_rt = n = 0;\n if (numKeys > 0) {\n $bm_rt = n = nearestKey(time).index;\n if (key(n).time > time) {\n n--;\n }\n }\n if (n == 0) {\n $bm_rt = t = 0;\n } else {\n $bm_rt = t = $bm_sub(time, key(n).time);\n }\n if (effect('Bounce & Drop - ukramedia.com')(58) == 1) {\n if (n > 0 && t < 1) {\n v = velocityAtTime($bm_sub(key(n).time, $bm_div(thisComp.frameDuration, 10)));\n amp = effect('Bounce & Drop - ukramedia.com')(59);\n freq = effect('Bounce & Drop - ukramedia.com')(60);\n decay = effect('Bounce & Drop - ukramedia.com')(61);\n $bm_rt = $bm_sum(value, $bm_div($bm_mul($bm_mul(v, amp), Math.sin($bm_mul($bm_mul($bm_mul(freq, t), 2), Math.PI))), Math.exp($bm_mul(decay, t))));\n } else {\n $bm_rt = value;\n }\n } else {\n if (n > 0 && t < 1) {\n v = velocityAtTime($bm_sub(key(n).time, $bm_div(thisComp.frameDuration, 10)));\n amp = effect('Bounce & Drop - ukramedia.com')(5);\n freq = effect('Bounce & Drop - ukramedia.com')(6);\n decay = effect('Bounce & Drop - ukramedia.com')(7);\n $bm_rt = $bm_sum(value, $bm_div($bm_mul($bm_mul(v, amp), Math.sin($bm_mul($bm_mul($bm_mul(freq, t), 2), Math.PI))), Math.exp($bm_mul(decay, t))));\n } else {\n $bm_rt = value;\n }\n }\n } else if (positionBounceBack == 1) {\n if (effect('Bounce & Drop - ukramedia.com')(64) == 1) {\n e = effect('Bounce & Drop - ukramedia.com')(65);\n g = effect('Bounce & Drop - ukramedia.com')(66);\n nMax = effect('Bounce & Drop - ukramedia.com')(67);\n } else {\n e = effect('Bounce & Drop - ukramedia.com')(10);\n g = effect('Bounce & Drop - ukramedia.com')(11);\n nMax = effect('Bounce & Drop - ukramedia.com')(12);\n }\n $bm_rt = n = 0;\n if (numKeys > 0) {\n $bm_rt = n = nearestKey(time).index;\n if (key(n).time > time)\n n--;\n }\n if (n > 0) {\n t = $bm_sub(time, key(n).time);\n v = $bm_mul($bm_neg(velocityAtTime($bm_sub(key(n).time, 0.001))), e);\n vl = length(v);\n if ($bm_isInstanceOfArray(value)) {\n vu = vl > 0 ? normalize(v) : [\n 0,\n 0,\n 0\n ];\n } else {\n vu = v < 0 ? -1 : 1;\n }\n tCur = 0;\n segDur = $bm_div($bm_mul(2, vl), g);\n tNext = segDur;\n nb = 1;\n while (tNext < t && nb <= nMax) {\n vl *= e;\n segDur *= e;\n tCur = tNext;\n tNext = $bm_sum(tNext, segDur);\n nb++;\n }\n if (nb <= nMax) {\n delta = $bm_sub(t, tCur);\n $bm_rt = $bm_sum(value, $bm_mul($bm_mul(vu, delta), $bm_sub(vl, $bm_div($bm_mul(g, delta), 2))));\n } else {\n $bm_rt = value;\n }\n } else\n $bm_rt = value;\n } else {\n $bm_rt = value;\n }\n} catch (err) {\n $bm_rt = value;\n}"},"r":{"a":0,"k":0,"ix":10,"x":"var $bm_rt;\nvar rotationInertialBounce, rotationBounceBack, n, n, t, t, v, amp, freq, decay, v, amp, freq, decay, e, g, nMax, e, g, nMax, n, n, t, v, vl, vu, vu, tCur, segDur, tNext, nb, delta;\nrotationInertialBounce = effect('Bounce & Drop - ukramedia.com')(30);\nrotationBounceBack = effect('Bounce & Drop - ukramedia.com')(31);\ntry {\n if (rotationInertialBounce == 1) {\n $bm_rt = n = 0;\n if (numKeys > 0) {\n $bm_rt = n = nearestKey(time).index;\n if (key(n).time > time) {\n n--;\n }\n }\n if (n == 0) {\n $bm_rt = t = 0;\n } else {\n $bm_rt = t = $bm_sub(time, key(n).time);\n }\n if (effect('Bounce & Drop - ukramedia.com')(58) == 1) {\n if (n > 0 && t < 1) {\n v = velocityAtTime($bm_sub(key(n).time, $bm_div(thisComp.frameDuration, 10)));\n amp = effect('Bounce & Drop - ukramedia.com')(59);\n freq = effect('Bounce & Drop - ukramedia.com')(60);\n decay = effect('Bounce & Drop - ukramedia.com')(61);\n $bm_rt = $bm_sum(value, $bm_div($bm_mul($bm_mul(v, amp), Math.sin($bm_mul($bm_mul($bm_mul(freq, t), 2), Math.PI))), Math.exp($bm_mul(decay, t))));\n } else {\n $bm_rt = value;\n }\n } else {\n if (n > 0 && t < 1) {\n v = velocityAtTime($bm_sub(key(n).time, $bm_div(thisComp.frameDuration, 10)));\n amp = effect('Bounce & Drop - ukramedia.com')(33);\n freq = effect('Bounce & Drop - ukramedia.com')(34);\n decay = effect('Bounce & Drop - ukramedia.com')(35);\n $bm_rt = $bm_sum(value, $bm_div($bm_mul($bm_mul(v, amp), Math.sin($bm_mul($bm_mul($bm_mul(freq, t), 2), Math.PI))), Math.exp($bm_mul(decay, t))));\n } else {\n $bm_rt = value;\n }\n }\n } else if (rotationBounceBack == 1) {\n if (effect('Bounce & Drop - ukramedia.com')(64) == 1) {\n e = effect('Bounce & Drop - ukramedia.com')(65);\n g = effect('Bounce & Drop - ukramedia.com')(66);\n nMax = effect('Bounce & Drop - ukramedia.com')(67);\n } else {\n e = effect('Bounce & Drop - ukramedia.com')(38);\n g = effect('Bounce & Drop - ukramedia.com')(39);\n nMax = effect('Bounce & Drop - ukramedia.com')(40);\n }\n $bm_rt = n = 0;\n if (numKeys > 0) {\n $bm_rt = n = nearestKey(time).index;\n if (key(n).time > time)\n n--;\n }\n if (n > 0) {\n t = $bm_sub(time, key(n).time);\n v = $bm_mul($bm_neg(velocityAtTime($bm_sub(key(n).time, 0.001))), e);\n vl = length(v);\n if ($bm_isInstanceOfArray(value)) {\n vu = vl > 0 ? normalize(v) : [\n 0,\n 0,\n 0\n ];\n } else {\n vu = v < 0 ? -1 : 1;\n }\n tCur = 0;\n segDur = $bm_div($bm_mul(2, vl), g);\n tNext = segDur;\n nb = 1;\n while (tNext < t && nb <= nMax) {\n vl *= e;\n segDur *= e;\n tCur = tNext;\n tNext = $bm_sum(tNext, segDur);\n nb++;\n }\n if (nb <= nMax) {\n delta = $bm_sub(t, tCur);\n $bm_rt = $bm_sum(value, $bm_mul($bm_mul(vu, delta), $bm_sub(vl, $bm_div($bm_mul(g, delta), 2))));\n } else {\n $bm_rt = value;\n }\n } else\n $bm_rt = value;\n } else {\n $bm_rt = value;\n }\n} catch (err) {\n $bm_rt = value;\n}"},"sa":{"a":0,"k":0},"o":{"a":0,"k":100,"ix":11,"x":"var $bm_rt;\nvar opacityInertialBounce, opacityBounceBack, n, n, t, t, v, amp, freq, decay, v, amp, freq, decay, e, g, nMax, e, g, nMax, n, n, t, v, vl, vu, vu, tCur, segDur, tNext, nb, delta;\nopacityInertialBounce = effect('Bounce & Drop - ukramedia.com')(44);\nopacityBounceBack = effect('Bounce & Drop - ukramedia.com')(45);\ntry {\n if (opacityInertialBounce == 1) {\n $bm_rt = n = 0;\n if (numKeys > 0) {\n $bm_rt = n = nearestKey(time).index;\n if (key(n).time > time) {\n n--;\n }\n }\n if (n == 0) {\n $bm_rt = t = 0;\n } else {\n $bm_rt = t = $bm_sub(time, key(n).time);\n }\n if (effect('Bounce & Drop - ukramedia.com')(58) == 1) {\n if (n > 0 && t < 1) {\n v = velocityAtTime($bm_sub(key(n).time, $bm_div(thisComp.frameDuration, 10)));\n amp = effect('Bounce & Drop - ukramedia.com')(59);\n freq = effect('Bounce & Drop - ukramedia.com')(60);\n decay = effect('Bounce & Drop - ukramedia.com')(61);\n $bm_rt = $bm_sum(value, $bm_div($bm_mul($bm_mul(v, amp), Math.sin($bm_mul($bm_mul($bm_mul(freq, t), 2), Math.PI))), Math.exp($bm_mul(decay, t))));\n } else {\n $bm_rt = value;\n }\n } else {\n if (n > 0 && t < 1) {\n v = velocityAtTime($bm_sub(key(n).time, $bm_div(thisComp.frameDuration, 10)));\n amp = effect('Bounce & Drop - ukramedia.com')(47);\n freq = effect('Bounce & Drop - ukramedia.com')(48);\n decay = effect('Bounce & Drop - ukramedia.com')(49);\n $bm_rt = $bm_sum(value, $bm_div($bm_mul($bm_mul(v, amp), Math.sin($bm_mul($bm_mul($bm_mul(freq, t), 2), Math.PI))), Math.exp($bm_mul(decay, t))));\n } else {\n $bm_rt = value;\n }\n }\n } else if (opacityBounceBack == 1) {\n if (effect('Bounce & Drop - ukramedia.com')(64) == 1) {\n e = effect('Bounce & Drop - ukramedia.com')(65);\n g = effect('Bounce & Drop - ukramedia.com')(66);\n nMax = effect('Bounce & Drop - ukramedia.com')(67);\n } else {\n e = effect('Bounce & Drop - ukramedia.com')(52);\n g = effect('Bounce & Drop - ukramedia.com')(53);\n nMax = effect('Bounce & Drop - ukramedia.com')(54);\n }\n $bm_rt = n = 0;\n if (numKeys > 0) {\n $bm_rt = n = nearestKey(time).index;\n if (key(n).time > time)\n n--;\n }\n if (n > 0) {\n t = $bm_sub(time, key(n).time);\n v = $bm_mul($bm_neg(velocityAtTime($bm_sub(key(n).time, 0.001))), e);\n vl = length(v);\n if ($bm_isInstanceOfArray(value)) {\n vu = vl > 0 ? normalize(v) : [\n 0,\n 0,\n 0\n ];\n } else {\n vu = v < 0 ? -1 : 1;\n }\n tCur = 0;\n segDur = $bm_div($bm_mul(2, vl), g);\n tNext = segDur;\n nb = 1;\n while (tNext < t && nb <= nMax) {\n vl *= e;\n segDur *= e;\n tCur = tNext;\n tNext = $bm_sum(tNext, segDur);\n nb++;\n }\n if (nb <= nMax) {\n delta = $bm_sub(t, tCur);\n $bm_rt = $bm_sum(value, $bm_mul($bm_mul(vu, delta), $bm_sub(vl, $bm_div($bm_mul(g, delta), 2))));\n } else {\n $bm_rt = value;\n }\n } else\n $bm_rt = value;\n } else {\n $bm_rt = value;\n }\n} catch (err) {\n $bm_rt = value;\n}"}},"ef":[{"ty":5,"mn":"Pseudo/animationControl","nm":"Bounce & Drop - ukramedia.com","ix":1,"en":1,"ef":[{"ty":6,"mn":"Pseudo/animationControl-0001","nm":"Position","ix":1,"v":0},{"ty":7,"mn":"Pseudo/animationControl-0002","nm":"Enable Inertial Bounce","ix":2,"v":{"a":0,"k":0,"ix":2}},{"ty":7,"mn":"Pseudo/animationControl-0003","nm":"Enable Bounce Back","ix":3,"v":{"a":0,"k":0,"ix":3}},{"ty":6,"mn":"Pseudo/animationControl-0004","nm":"Inertial Bounce Options","ix":4,"v":0},{"ty":0,"mn":"Pseudo/animationControl-0005","nm":"Amplitude","ix":5,"v":{"a":0,"k":0.05,"ix":5}},{"ty":0,"mn":"Pseudo/animationControl-0006","nm":"Frequency","ix":6,"v":{"a":0,"k":4,"ix":6}},{"ty":0,"mn":"Pseudo/animationControl-0007","nm":"Decay","ix":7,"v":{"a":0,"k":8,"ix":7}},{"ty":6,"mn":"Pseudo/animationControl-0008","nm":"","ix":8,"v":0},{"ty":6,"mn":"Pseudo/animationControl-0009","nm":"Bounce Back Options","ix":9,"v":0},{"ty":0,"mn":"Pseudo/animationControl-0010","nm":"Elasticity","ix":10,"v":{"a":0,"k":0.7,"ix":10}},{"ty":0,"mn":"Pseudo/animationControl-0011","nm":"Gravity","ix":11,"v":{"a":0,"k":5000,"ix":11}},{"ty":0,"mn":"Pseudo/animationControl-0012","nm":"nMax","ix":12,"v":{"a":0,"k":9,"ix":12}},{"ty":6,"mn":"Pseudo/animationControl-0013","nm":"","ix":13,"v":0},{"ty":6,"mn":"Pseudo/animationControl-0014","nm":"","ix":14,"v":0},{"ty":6,"mn":"Pseudo/animationControl-0015","nm":"Scale","ix":15,"v":0},{"ty":7,"mn":"Pseudo/animationControl-0016","nm":"Enable Inertial Bounce","ix":16,"v":{"a":0,"k":1,"ix":16}},{"ty":7,"mn":"Pseudo/animationControl-0017","nm":"Enable Bounce Back","ix":17,"v":{"a":0,"k":0,"ix":17}},{"ty":6,"mn":"Pseudo/animationControl-0018","nm":"Inertial Bounce Options","ix":18,"v":0},{"ty":0,"mn":"Pseudo/animationControl-0019","nm":"Amplitude","ix":19,"v":{"a":0,"k":0.8,"ix":19}},{"ty":0,"mn":"Pseudo/animationControl-0020","nm":"Frequency","ix":20,"v":{"a":0,"k":4,"ix":20}},{"ty":0,"mn":"Pseudo/animationControl-0021","nm":"Decay","ix":21,"v":{"a":0,"k":8,"ix":21}},{"ty":6,"mn":"Pseudo/animationControl-0022","nm":"","ix":22,"v":0},{"ty":6,"mn":"Pseudo/animationControl-0023","nm":"Bounce Back Options","ix":23,"v":0},{"ty":0,"mn":"Pseudo/animationControl-0024","nm":"Elasticity","ix":24,"v":{"a":0,"k":0.7,"ix":24}},{"ty":0,"mn":"Pseudo/animationControl-0025","nm":"Gravity","ix":25,"v":{"a":0,"k":5000,"ix":25}},{"ty":0,"mn":"Pseudo/animationControl-0026","nm":"nMax","ix":26,"v":{"a":0,"k":9,"ix":26}},{"ty":6,"mn":"Pseudo/animationControl-0027","nm":"","ix":27,"v":0},{"ty":6,"mn":"Pseudo/animationControl-0028","nm":"","ix":28,"v":0},{"ty":6,"mn":"Pseudo/animationControl-0029","nm":"Rotation","ix":29,"v":0},{"ty":7,"mn":"Pseudo/animationControl-0030","nm":"Enable Inertial Bounce","ix":30,"v":{"a":0,"k":0,"ix":30}},{"ty":7,"mn":"Pseudo/animationControl-0031","nm":"Enable Bounce Back","ix":31,"v":{"a":0,"k":0,"ix":31}},{"ty":6,"mn":"Pseudo/animationControl-0032","nm":"Inertial Bounce Options","ix":32,"v":0},{"ty":0,"mn":"Pseudo/animationControl-0033","nm":"Amplitude","ix":33,"v":{"a":0,"k":0.05,"ix":33}},{"ty":0,"mn":"Pseudo/animationControl-0034","nm":"Frequency","ix":34,"v":{"a":0,"k":4,"ix":34}},{"ty":0,"mn":"Pseudo/animationControl-0035","nm":"Decay","ix":35,"v":{"a":0,"k":8,"ix":35}},{"ty":6,"mn":"Pseudo/animationControl-0036","nm":"","ix":36,"v":0},{"ty":6,"mn":"Pseudo/animationControl-0037","nm":"Bounce Back Options","ix":37,"v":0},{"ty":0,"mn":"Pseudo/animationControl-0038","nm":"Elasticity","ix":38,"v":{"a":0,"k":0.7,"ix":38}},{"ty":0,"mn":"Pseudo/animationControl-0039","nm":"Gravity","ix":39,"v":{"a":0,"k":5000,"ix":39}},{"ty":0,"mn":"Pseudo/animationControl-0040","nm":"nMax","ix":40,"v":{"a":0,"k":9,"ix":40}},{"ty":6,"mn":"Pseudo/animationControl-0041","nm":"","ix":41,"v":0},{"ty":6,"mn":"Pseudo/animationControl-0042","nm":"","ix":42,"v":0},{"ty":6,"mn":"Pseudo/animationControl-0043","nm":"Opacity","ix":43,"v":0},{"ty":7,"mn":"Pseudo/animationControl-0044","nm":"Enable Inertial Bounce","ix":44,"v":{"a":0,"k":0,"ix":44}},{"ty":7,"mn":"Pseudo/animationControl-0045","nm":"Enable Bounce Back","ix":45,"v":{"a":0,"k":0,"ix":45}},{"ty":6,"mn":"Pseudo/animationControl-0046","nm":"Inertial Bounce Options","ix":46,"v":0},{"ty":0,"mn":"Pseudo/animationControl-0047","nm":"Amplitude","ix":47,"v":{"a":0,"k":0.05,"ix":47}},{"ty":0,"mn":"Pseudo/animationControl-0048","nm":"Frequency","ix":48,"v":{"a":0,"k":4,"ix":48}},{"ty":0,"mn":"Pseudo/animationControl-0049","nm":"Decay","ix":49,"v":{"a":0,"k":8,"ix":49}},{"ty":6,"mn":"Pseudo/animationControl-0050","nm":"","ix":50,"v":0},{"ty":6,"mn":"Pseudo/animationControl-0051","nm":"Bounce Back Options","ix":51,"v":0},{"ty":0,"mn":"Pseudo/animationControl-0052","nm":"Elasticity","ix":52,"v":{"a":0,"k":0.7,"ix":52}},{"ty":0,"mn":"Pseudo/animationControl-0053","nm":"Gravity","ix":53,"v":{"a":0,"k":5000,"ix":53}},{"ty":0,"mn":"Pseudo/animationControl-0054","nm":"nMax","ix":54,"v":{"a":0,"k":9,"ix":54}},{"ty":6,"mn":"Pseudo/animationControl-0055","nm":"","ix":55,"v":0},{"ty":6,"mn":"Pseudo/animationControl-0056","nm":"","ix":56,"v":0},{"ty":6,"mn":"Pseudo/animationControl-0057","nm":"Global Inertial Bounce Options","ix":57,"v":0},{"ty":7,"mn":"Pseudo/animationControl-0058","nm":"Enable Global Inertial Bounce","ix":58,"v":{"a":0,"k":0,"ix":58}},{"ty":0,"mn":"Pseudo/animationControl-0059","nm":"Amplitude","ix":59,"v":{"a":0,"k":0.05,"ix":59}},{"ty":0,"mn":"Pseudo/animationControl-0060","nm":"Frequency","ix":60,"v":{"a":0,"k":4,"ix":60}},{"ty":0,"mn":"Pseudo/animationControl-0061","nm":"Decay","ix":61,"v":{"a":0,"k":8,"ix":61}},{"ty":6,"mn":"Pseudo/animationControl-0062","nm":"","ix":62,"v":0},{"ty":6,"mn":"Pseudo/animationControl-0063","nm":"Global Bounce Back Options","ix":63,"v":0},{"ty":7,"mn":"Pseudo/animationControl-0064","nm":"Enable Global Bounce Back","ix":64,"v":{"a":0,"k":0,"ix":64}},{"ty":0,"mn":"Pseudo/animationControl-0065","nm":"Elasticity","ix":65,"v":{"a":0,"k":0.7,"ix":65}},{"ty":0,"mn":"Pseudo/animationControl-0066","nm":"Gravity","ix":66,"v":{"a":0,"k":5000,"ix":66}},{"ty":0,"mn":"Pseudo/animationControl-0067","nm":"nMax","ix":67,"v":{"a":0,"k":9,"ix":67}},{"ty":6,"mn":"Pseudo/animationControl-0068","nm":"","ix":68,"v":0}]}],"shapes":[{"ty":"gr","bm":0,"hd":false,"mn":"ADBE Vector Group","nm":"Group 1","ix":1,"cix":2,"np":2,"it":[{"ty":"sh","bm":0,"hd":false,"mn":"ADBE Vector Shape - Group","nm":"Path 1","ix":1,"d":1,"ks":{"a":0,"k":{"c":true,"i":[[0,-77.215],[77.216,0],[0,77.215],[-77.215,0]],"o":[[0,77.215],[-77.215,0],[0,-77.215],[77.216,0]],"v":[[139.811,0],[0,139.811],[-139.811,0],[0,-139.811]]},"ix":2}},{"ty":"fl","bm":0,"hd":false,"mn":"ADBE Vector Graphic - Fill","nm":"Fill 1","c":{"a":0,"k":[0.8863,0.1176,0.1882],"ix":4},"r":1,"o":{"a":0,"k":100,"ix":5}},{"ty":"tr","a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"sk":{"a":0,"k":0,"ix":4},"p":{"a":0,"k":[140.061,140.061],"ix":2},"r":{"a":0,"k":0,"ix":6},"sa":{"a":0,"k":0,"ix":5},"o":{"a":0,"k":100,"ix":7}}]}],"ind":3},{"ty":4,"nm":"Circle 1","sr":1,"st":0,"op":360,"ip":0,"hd":false,"ddd":0,"bm":0,"hasMask":false,"ao":0,"ks":{"a":{"a":0,"k":[140.061,140.061,0],"ix":1},"s":{"a":1,"k":[{"o":{"x":0.167,"y":0.167},"i":{"x":0.667,"y":1},"s":[0,0,100],"t":0},{"o":{"x":0.167,"y":0.167},"i":{"x":0.667,"y":1},"s":[132,132,100],"t":10},{"o":{"x":0.167,"y":0.167},"i":{"x":0.667,"y":1},"s":[130,130,100],"t":35},{"s":[175,175,100],"t":55}],"ix":6,"x":"var $bm_rt;\nvar scaleInertialBounce, scaleBounceBack, n, n, t, t, v, amp, freq, decay, v, amp, freq, decay, e, g, nMax, e, g, nMax, n, n, t, v, vl, vu, vu, tCur, segDur, tNext, nb, delta;\nscaleInertialBounce = effect('Bounce & Drop - ukramedia.com')(16);\nscaleBounceBack = effect('Bounce & Drop - ukramedia.com')(17);\ntry {\n if (scaleInertialBounce == 1) {\n $bm_rt = n = 0;\n if (numKeys > 0) {\n $bm_rt = n = nearestKey(time).index;\n if (key(n).time > time) {\n n--;\n }\n }\n if (n == 0) {\n $bm_rt = t = 0;\n } else {\n $bm_rt = t = $bm_sub(time, key(n).time);\n }\n if (effect('Bounce & Drop - ukramedia.com')(58) == 1) {\n if (n > 0 && t < 1) {\n v = velocityAtTime($bm_sub(key(n).time, $bm_div(thisComp.frameDuration, 10)));\n amp = effect('Bounce & Drop - ukramedia.com')(59);\n freq = effect('Bounce & Drop - ukramedia.com')(60);\n decay = effect('Bounce & Drop - ukramedia.com')(61);\n $bm_rt = $bm_sum(value, $bm_div($bm_mul($bm_mul(v, amp), Math.sin($bm_mul($bm_mul($bm_mul(freq, t), 2), Math.PI))), Math.exp($bm_mul(decay, t))));\n } else {\n $bm_rt = value;\n }\n } else {\n if (n > 0 && t < 1) {\n v = velocityAtTime($bm_sub(key(n).time, $bm_div(thisComp.frameDuration, 10)));\n amp = effect('Bounce & Drop - ukramedia.com')(19);\n freq = effect('Bounce & Drop - ukramedia.com')(20);\n decay = effect('Bounce & Drop - ukramedia.com')(21);\n $bm_rt = $bm_sum(value, $bm_div($bm_mul($bm_mul(v, amp), Math.sin($bm_mul($bm_mul($bm_mul(freq, t), 2), Math.PI))), Math.exp($bm_mul(decay, t))));\n } else {\n $bm_rt = value;\n }\n }\n } else if (scaleBounceBack == 1) {\n if (effect('Bounce & Drop - ukramedia.com')(64) == 1) {\n e = effect('Bounce & Drop - ukramedia.com')(65);\n g = effect('Bounce & Drop - ukramedia.com')(66);\n nMax = effect('Bounce & Drop - ukramedia.com')(67);\n } else {\n e = effect('Bounce & Drop - ukramedia.com')(24);\n g = effect('Bounce & Drop - ukramedia.com')(25);\n nMax = effect('Bounce & Drop - ukramedia.com')(26);\n }\n $bm_rt = n = 0;\n if (numKeys > 0) {\n $bm_rt = n = nearestKey(time).index;\n if (key(n).time > time)\n n--;\n }\n if (n > 0) {\n t = $bm_sub(time, key(n).time);\n v = $bm_mul($bm_neg(velocityAtTime($bm_sub(key(n).time, 0.001))), e);\n vl = length(v);\n if ($bm_isInstanceOfArray(value)) {\n vu = vl > 0 ? normalize(v) : [\n 0,\n 0,\n 0\n ];\n } else {\n vu = v < 0 ? -1 : 1;\n }\n tCur = 0;\n segDur = $bm_div($bm_mul(2, vl), g);\n tNext = segDur;\n nb = 1;\n while (tNext < t && nb <= nMax) {\n vl *= e;\n segDur *= e;\n tCur = tNext;\n tNext = $bm_sum(tNext, segDur);\n nb++;\n }\n if (nb <= nMax) {\n delta = $bm_sub(t, tCur);\n $bm_rt = $bm_sum(value, $bm_mul($bm_mul(vu, delta), $bm_sub(vl, $bm_div($bm_mul(g, delta), 2))));\n } else {\n $bm_rt = value;\n }\n } else\n $bm_rt = value;\n } else {\n $bm_rt = value;\n }\n} catch (err) {\n $bm_rt = value;\n}"},"sk":{"a":0,"k":0},"p":{"a":0,"k":[256,256,0],"ix":2,"x":"var $bm_rt;\nvar positionInertialBounce, positionBounceBack, n, n, t, t, v, amp, freq, decay, v, amp, freq, decay, e, g, nMax, e, g, nMax, n, n, t, v, vl, vu, vu, tCur, segDur, tNext, nb, delta;\npositionInertialBounce = effect('Bounce & Drop - ukramedia.com')(2);\npositionBounceBack = effect('Bounce & Drop - ukramedia.com')(3);\ntry {\n if (positionInertialBounce == 1) {\n $bm_rt = n = 0;\n if (numKeys > 0) {\n $bm_rt = n = nearestKey(time).index;\n if (key(n).time > time) {\n n--;\n }\n }\n if (n == 0) {\n $bm_rt = t = 0;\n } else {\n $bm_rt = t = $bm_sub(time, key(n).time);\n }\n if (effect('Bounce & Drop - ukramedia.com')(58) == 1) {\n if (n > 0 && t < 1) {\n v = velocityAtTime($bm_sub(key(n).time, $bm_div(thisComp.frameDuration, 10)));\n amp = effect('Bounce & Drop - ukramedia.com')(59);\n freq = effect('Bounce & Drop - ukramedia.com')(60);\n decay = effect('Bounce & Drop - ukramedia.com')(61);\n $bm_rt = $bm_sum(value, $bm_div($bm_mul($bm_mul(v, amp), Math.sin($bm_mul($bm_mul($bm_mul(freq, t), 2), Math.PI))), Math.exp($bm_mul(decay, t))));\n } else {\n $bm_rt = value;\n }\n } else {\n if (n > 0 && t < 1) {\n v = velocityAtTime($bm_sub(key(n).time, $bm_div(thisComp.frameDuration, 10)));\n amp = effect('Bounce & Drop - ukramedia.com')(5);\n freq = effect('Bounce & Drop - ukramedia.com')(6);\n decay = effect('Bounce & Drop - ukramedia.com')(7);\n $bm_rt = $bm_sum(value, $bm_div($bm_mul($bm_mul(v, amp), Math.sin($bm_mul($bm_mul($bm_mul(freq, t), 2), Math.PI))), Math.exp($bm_mul(decay, t))));\n } else {\n $bm_rt = value;\n }\n }\n } else if (positionBounceBack == 1) {\n if (effect('Bounce & Drop - ukramedia.com')(64) == 1) {\n e = effect('Bounce & Drop - ukramedia.com')(65);\n g = effect('Bounce & Drop - ukramedia.com')(66);\n nMax = effect('Bounce & Drop - ukramedia.com')(67);\n } else {\n e = effect('Bounce & Drop - ukramedia.com')(10);\n g = effect('Bounce & Drop - ukramedia.com')(11);\n nMax = effect('Bounce & Drop - ukramedia.com')(12);\n }\n $bm_rt = n = 0;\n if (numKeys > 0) {\n $bm_rt = n = nearestKey(time).index;\n if (key(n).time > time)\n n--;\n }\n if (n > 0) {\n t = $bm_sub(time, key(n).time);\n v = $bm_mul($bm_neg(velocityAtTime($bm_sub(key(n).time, 0.001))), e);\n vl = length(v);\n if ($bm_isInstanceOfArray(value)) {\n vu = vl > 0 ? normalize(v) : [\n 0,\n 0,\n 0\n ];\n } else {\n vu = v < 0 ? -1 : 1;\n }\n tCur = 0;\n segDur = $bm_div($bm_mul(2, vl), g);\n tNext = segDur;\n nb = 1;\n while (tNext < t && nb <= nMax) {\n vl *= e;\n segDur *= e;\n tCur = tNext;\n tNext = $bm_sum(tNext, segDur);\n nb++;\n }\n if (nb <= nMax) {\n delta = $bm_sub(t, tCur);\n $bm_rt = $bm_sum(value, $bm_mul($bm_mul(vu, delta), $bm_sub(vl, $bm_div($bm_mul(g, delta), 2))));\n } else {\n $bm_rt = value;\n }\n } else\n $bm_rt = value;\n } else {\n $bm_rt = value;\n }\n} catch (err) {\n $bm_rt = value;\n}"},"r":{"a":0,"k":0,"ix":10,"x":"var $bm_rt;\nvar rotationInertialBounce, rotationBounceBack, n, n, t, t, v, amp, freq, decay, v, amp, freq, decay, e, g, nMax, e, g, nMax, n, n, t, v, vl, vu, vu, tCur, segDur, tNext, nb, delta;\nrotationInertialBounce = effect('Bounce & Drop - ukramedia.com')(30);\nrotationBounceBack = effect('Bounce & Drop - ukramedia.com')(31);\ntry {\n if (rotationInertialBounce == 1) {\n $bm_rt = n = 0;\n if (numKeys > 0) {\n $bm_rt = n = nearestKey(time).index;\n if (key(n).time > time) {\n n--;\n }\n }\n if (n == 0) {\n $bm_rt = t = 0;\n } else {\n $bm_rt = t = $bm_sub(time, key(n).time);\n }\n if (effect('Bounce & Drop - ukramedia.com')(58) == 1) {\n if (n > 0 && t < 1) {\n v = velocityAtTime($bm_sub(key(n).time, $bm_div(thisComp.frameDuration, 10)));\n amp = effect('Bounce & Drop - ukramedia.com')(59);\n freq = effect('Bounce & Drop - ukramedia.com')(60);\n decay = effect('Bounce & Drop - ukramedia.com')(61);\n $bm_rt = $bm_sum(value, $bm_div($bm_mul($bm_mul(v, amp), Math.sin($bm_mul($bm_mul($bm_mul(freq, t), 2), Math.PI))), Math.exp($bm_mul(decay, t))));\n } else {\n $bm_rt = value;\n }\n } else {\n if (n > 0 && t < 1) {\n v = velocityAtTime($bm_sub(key(n).time, $bm_div(thisComp.frameDuration, 10)));\n amp = effect('Bounce & Drop - ukramedia.com')(33);\n freq = effect('Bounce & Drop - ukramedia.com')(34);\n decay = effect('Bounce & Drop - ukramedia.com')(35);\n $bm_rt = $bm_sum(value, $bm_div($bm_mul($bm_mul(v, amp), Math.sin($bm_mul($bm_mul($bm_mul(freq, t), 2), Math.PI))), Math.exp($bm_mul(decay, t))));\n } else {\n $bm_rt = value;\n }\n }\n } else if (rotationBounceBack == 1) {\n if (effect('Bounce & Drop - ukramedia.com')(64) == 1) {\n e = effect('Bounce & Drop - ukramedia.com')(65);\n g = effect('Bounce & Drop - ukramedia.com')(66);\n nMax = effect('Bounce & Drop - ukramedia.com')(67);\n } else {\n e = effect('Bounce & Drop - ukramedia.com')(38);\n g = effect('Bounce & Drop - ukramedia.com')(39);\n nMax = effect('Bounce & Drop - ukramedia.com')(40);\n }\n $bm_rt = n = 0;\n if (numKeys > 0) {\n $bm_rt = n = nearestKey(time).index;\n if (key(n).time > time)\n n--;\n }\n if (n > 0) {\n t = $bm_sub(time, key(n).time);\n v = $bm_mul($bm_neg(velocityAtTime($bm_sub(key(n).time, 0.001))), e);\n vl = length(v);\n if ($bm_isInstanceOfArray(value)) {\n vu = vl > 0 ? normalize(v) : [\n 0,\n 0,\n 0\n ];\n } else {\n vu = v < 0 ? -1 : 1;\n }\n tCur = 0;\n segDur = $bm_div($bm_mul(2, vl), g);\n tNext = segDur;\n nb = 1;\n while (tNext < t && nb <= nMax) {\n vl *= e;\n segDur *= e;\n tCur = tNext;\n tNext = $bm_sum(tNext, segDur);\n nb++;\n }\n if (nb <= nMax) {\n delta = $bm_sub(t, tCur);\n $bm_rt = $bm_sum(value, $bm_mul($bm_mul(vu, delta), $bm_sub(vl, $bm_div($bm_mul(g, delta), 2))));\n } else {\n $bm_rt = value;\n }\n } else\n $bm_rt = value;\n } else {\n $bm_rt = value;\n }\n} catch (err) {\n $bm_rt = value;\n}"},"sa":{"a":0,"k":0},"o":{"a":1,"k":[{"o":{"x":0.167,"y":0.167},"i":{"x":0.667,"y":1},"s":[50],"t":41.25},{"s":[0],"t":55}],"ix":11,"x":"var $bm_rt;\nvar opacityInertialBounce, opacityBounceBack, n, n, t, t, v, amp, freq, decay, v, amp, freq, decay, e, g, nMax, e, g, nMax, n, n, t, v, vl, vu, vu, tCur, segDur, tNext, nb, delta;\nopacityInertialBounce = effect('Bounce & Drop - ukramedia.com')(44);\nopacityBounceBack = effect('Bounce & Drop - ukramedia.com')(45);\ntry {\n if (opacityInertialBounce == 1) {\n $bm_rt = n = 0;\n if (numKeys > 0) {\n $bm_rt = n = nearestKey(time).index;\n if (key(n).time > time) {\n n--;\n }\n }\n if (n == 0) {\n $bm_rt = t = 0;\n } else {\n $bm_rt = t = $bm_sub(time, key(n).time);\n }\n if (effect('Bounce & Drop - ukramedia.com')(58) == 1) {\n if (n > 0 && t < 1) {\n v = velocityAtTime($bm_sub(key(n).time, $bm_div(thisComp.frameDuration, 10)));\n amp = effect('Bounce & Drop - ukramedia.com')(59);\n freq = effect('Bounce & Drop - ukramedia.com')(60);\n decay = effect('Bounce & Drop - ukramedia.com')(61);\n $bm_rt = $bm_sum(value, $bm_div($bm_mul($bm_mul(v, amp), Math.sin($bm_mul($bm_mul($bm_mul(freq, t), 2), Math.PI))), Math.exp($bm_mul(decay, t))));\n } else {\n $bm_rt = value;\n }\n } else {\n if (n > 0 && t < 1) {\n v = velocityAtTime($bm_sub(key(n).time, $bm_div(thisComp.frameDuration, 10)));\n amp = effect('Bounce & Drop - ukramedia.com')(47);\n freq = effect('Bounce & Drop - ukramedia.com')(48);\n decay = effect('Bounce & Drop - ukramedia.com')(49);\n $bm_rt = $bm_sum(value, $bm_div($bm_mul($bm_mul(v, amp), Math.sin($bm_mul($bm_mul($bm_mul(freq, t), 2), Math.PI))), Math.exp($bm_mul(decay, t))));\n } else {\n $bm_rt = value;\n }\n }\n } else if (opacityBounceBack == 1) {\n if (effect('Bounce & Drop - ukramedia.com')(64) == 1) {\n e = effect('Bounce & Drop - ukramedia.com')(65);\n g = effect('Bounce & Drop - ukramedia.com')(66);\n nMax = effect('Bounce & Drop - ukramedia.com')(67);\n } else {\n e = effect('Bounce & Drop - ukramedia.com')(52);\n g = effect('Bounce & Drop - ukramedia.com')(53);\n nMax = effect('Bounce & Drop - ukramedia.com')(54);\n }\n $bm_rt = n = 0;\n if (numKeys > 0) {\n $bm_rt = n = nearestKey(time).index;\n if (key(n).time > time)\n n--;\n }\n if (n > 0) {\n t = $bm_sub(time, key(n).time);\n v = $bm_mul($bm_neg(velocityAtTime($bm_sub(key(n).time, 0.001))), e);\n vl = length(v);\n if ($bm_isInstanceOfArray(value)) {\n vu = vl > 0 ? normalize(v) : [\n 0,\n 0,\n 0\n ];\n } else {\n vu = v < 0 ? -1 : 1;\n }\n tCur = 0;\n segDur = $bm_div($bm_mul(2, vl), g);\n tNext = segDur;\n nb = 1;\n while (tNext < t && nb <= nMax) {\n vl *= e;\n segDur *= e;\n tCur = tNext;\n tNext = $bm_sum(tNext, segDur);\n nb++;\n }\n if (nb <= nMax) {\n delta = $bm_sub(t, tCur);\n $bm_rt = $bm_sum(value, $bm_mul($bm_mul(vu, delta), $bm_sub(vl, $bm_div($bm_mul(g, delta), 2))));\n } else {\n $bm_rt = value;\n }\n } else\n $bm_rt = value;\n } else {\n $bm_rt = value;\n }\n} catch (err) {\n $bm_rt = value;\n}"}},"ef":[{"ty":5,"mn":"Pseudo/animationControl","nm":"Bounce & Drop - ukramedia.com","ix":1,"en":1,"ef":[{"ty":6,"mn":"Pseudo/animationControl-0001","nm":"Position","ix":1,"v":0},{"ty":7,"mn":"Pseudo/animationControl-0002","nm":"Enable Inertial Bounce","ix":2,"v":{"a":0,"k":0,"ix":2}},{"ty":7,"mn":"Pseudo/animationControl-0003","nm":"Enable Bounce Back","ix":3,"v":{"a":0,"k":0,"ix":3}},{"ty":6,"mn":"Pseudo/animationControl-0004","nm":"Inertial Bounce Options","ix":4,"v":0},{"ty":0,"mn":"Pseudo/animationControl-0005","nm":"Amplitude","ix":5,"v":{"a":0,"k":0.05,"ix":5}},{"ty":0,"mn":"Pseudo/animationControl-0006","nm":"Frequency","ix":6,"v":{"a":0,"k":4,"ix":6}},{"ty":0,"mn":"Pseudo/animationControl-0007","nm":"Decay","ix":7,"v":{"a":0,"k":8,"ix":7}},{"ty":6,"mn":"Pseudo/animationControl-0008","nm":"","ix":8,"v":0},{"ty":6,"mn":"Pseudo/animationControl-0009","nm":"Bounce Back Options","ix":9,"v":0},{"ty":0,"mn":"Pseudo/animationControl-0010","nm":"Elasticity","ix":10,"v":{"a":0,"k":0.7,"ix":10}},{"ty":0,"mn":"Pseudo/animationControl-0011","nm":"Gravity","ix":11,"v":{"a":0,"k":5000,"ix":11}},{"ty":0,"mn":"Pseudo/animationControl-0012","nm":"nMax","ix":12,"v":{"a":0,"k":9,"ix":12}},{"ty":6,"mn":"Pseudo/animationControl-0013","nm":"","ix":13,"v":0},{"ty":6,"mn":"Pseudo/animationControl-0014","nm":"","ix":14,"v":0},{"ty":6,"mn":"Pseudo/animationControl-0015","nm":"Scale","ix":15,"v":0},{"ty":7,"mn":"Pseudo/animationControl-0016","nm":"Enable Inertial Bounce","ix":16,"v":{"a":0,"k":1,"ix":16}},{"ty":7,"mn":"Pseudo/animationControl-0017","nm":"Enable Bounce Back","ix":17,"v":{"a":0,"k":0,"ix":17}},{"ty":6,"mn":"Pseudo/animationControl-0018","nm":"Inertial Bounce Options","ix":18,"v":0},{"ty":0,"mn":"Pseudo/animationControl-0019","nm":"Amplitude","ix":19,"v":{"a":0,"k":0.8,"ix":19}},{"ty":0,"mn":"Pseudo/animationControl-0020","nm":"Frequency","ix":20,"v":{"a":0,"k":4,"ix":20}},{"ty":0,"mn":"Pseudo/animationControl-0021","nm":"Decay","ix":21,"v":{"a":0,"k":8,"ix":21}},{"ty":6,"mn":"Pseudo/animationControl-0022","nm":"","ix":22,"v":0},{"ty":6,"mn":"Pseudo/animationControl-0023","nm":"Bounce Back Options","ix":23,"v":0},{"ty":0,"mn":"Pseudo/animationControl-0024","nm":"Elasticity","ix":24,"v":{"a":0,"k":0.7,"ix":24}},{"ty":0,"mn":"Pseudo/animationControl-0025","nm":"Gravity","ix":25,"v":{"a":0,"k":5000,"ix":25}},{"ty":0,"mn":"Pseudo/animationControl-0026","nm":"nMax","ix":26,"v":{"a":0,"k":9,"ix":26}},{"ty":6,"mn":"Pseudo/animationControl-0027","nm":"","ix":27,"v":0},{"ty":6,"mn":"Pseudo/animationControl-0028","nm":"","ix":28,"v":0},{"ty":6,"mn":"Pseudo/animationControl-0029","nm":"Rotation","ix":29,"v":0},{"ty":7,"mn":"Pseudo/animationControl-0030","nm":"Enable Inertial Bounce","ix":30,"v":{"a":0,"k":0,"ix":30}},{"ty":7,"mn":"Pseudo/animationControl-0031","nm":"Enable Bounce Back","ix":31,"v":{"a":0,"k":0,"ix":31}},{"ty":6,"mn":"Pseudo/animationControl-0032","nm":"Inertial Bounce Options","ix":32,"v":0},{"ty":0,"mn":"Pseudo/animationControl-0033","nm":"Amplitude","ix":33,"v":{"a":0,"k":0.05,"ix":33}},{"ty":0,"mn":"Pseudo/animationControl-0034","nm":"Frequency","ix":34,"v":{"a":0,"k":4,"ix":34}},{"ty":0,"mn":"Pseudo/animationControl-0035","nm":"Decay","ix":35,"v":{"a":0,"k":8,"ix":35}},{"ty":6,"mn":"Pseudo/animationControl-0036","nm":"","ix":36,"v":0},{"ty":6,"mn":"Pseudo/animationControl-0037","nm":"Bounce Back Options","ix":37,"v":0},{"ty":0,"mn":"Pseudo/animationControl-0038","nm":"Elasticity","ix":38,"v":{"a":0,"k":0.7,"ix":38}},{"ty":0,"mn":"Pseudo/animationControl-0039","nm":"Gravity","ix":39,"v":{"a":0,"k":5000,"ix":39}},{"ty":0,"mn":"Pseudo/animationControl-0040","nm":"nMax","ix":40,"v":{"a":0,"k":9,"ix":40}},{"ty":6,"mn":"Pseudo/animationControl-0041","nm":"","ix":41,"v":0},{"ty":6,"mn":"Pseudo/animationControl-0042","nm":"","ix":42,"v":0},{"ty":6,"mn":"Pseudo/animationControl-0043","nm":"Opacity","ix":43,"v":0},{"ty":7,"mn":"Pseudo/animationControl-0044","nm":"Enable Inertial Bounce","ix":44,"v":{"a":0,"k":0,"ix":44}},{"ty":7,"mn":"Pseudo/animationControl-0045","nm":"Enable Bounce Back","ix":45,"v":{"a":0,"k":0,"ix":45}},{"ty":6,"mn":"Pseudo/animationControl-0046","nm":"Inertial Bounce Options","ix":46,"v":0},{"ty":0,"mn":"Pseudo/animationControl-0047","nm":"Amplitude","ix":47,"v":{"a":0,"k":0.05,"ix":47}},{"ty":0,"mn":"Pseudo/animationControl-0048","nm":"Frequency","ix":48,"v":{"a":0,"k":4,"ix":48}},{"ty":0,"mn":"Pseudo/animationControl-0049","nm":"Decay","ix":49,"v":{"a":0,"k":8,"ix":49}},{"ty":6,"mn":"Pseudo/animationControl-0050","nm":"","ix":50,"v":0},{"ty":6,"mn":"Pseudo/animationControl-0051","nm":"Bounce Back Options","ix":51,"v":0},{"ty":0,"mn":"Pseudo/animationControl-0052","nm":"Elasticity","ix":52,"v":{"a":0,"k":0.7,"ix":52}},{"ty":0,"mn":"Pseudo/animationControl-0053","nm":"Gravity","ix":53,"v":{"a":0,"k":5000,"ix":53}},{"ty":0,"mn":"Pseudo/animationControl-0054","nm":"nMax","ix":54,"v":{"a":0,"k":9,"ix":54}},{"ty":6,"mn":"Pseudo/animationControl-0055","nm":"","ix":55,"v":0},{"ty":6,"mn":"Pseudo/animationControl-0056","nm":"","ix":56,"v":0},{"ty":6,"mn":"Pseudo/animationControl-0057","nm":"Global Inertial Bounce Options","ix":57,"v":0},{"ty":7,"mn":"Pseudo/animationControl-0058","nm":"Enable Global Inertial Bounce","ix":58,"v":{"a":0,"k":0,"ix":58}},{"ty":0,"mn":"Pseudo/animationControl-0059","nm":"Amplitude","ix":59,"v":{"a":0,"k":0.05,"ix":59}},{"ty":0,"mn":"Pseudo/animationControl-0060","nm":"Frequency","ix":60,"v":{"a":0,"k":4,"ix":60}},{"ty":0,"mn":"Pseudo/animationControl-0061","nm":"Decay","ix":61,"v":{"a":0,"k":8,"ix":61}},{"ty":6,"mn":"Pseudo/animationControl-0062","nm":"","ix":62,"v":0},{"ty":6,"mn":"Pseudo/animationControl-0063","nm":"Global Bounce Back Options","ix":63,"v":0},{"ty":7,"mn":"Pseudo/animationControl-0064","nm":"Enable Global Bounce Back","ix":64,"v":{"a":0,"k":0,"ix":64}},{"ty":0,"mn":"Pseudo/animationControl-0065","nm":"Elasticity","ix":65,"v":{"a":0,"k":0.7,"ix":65}},{"ty":0,"mn":"Pseudo/animationControl-0066","nm":"Gravity","ix":66,"v":{"a":0,"k":5000,"ix":66}},{"ty":0,"mn":"Pseudo/animationControl-0067","nm":"nMax","ix":67,"v":{"a":0,"k":9,"ix":67}},{"ty":6,"mn":"Pseudo/animationControl-0068","nm":"","ix":68,"v":0}]}],"shapes":[{"ty":"gr","bm":0,"hd":false,"mn":"ADBE Vector Group","nm":"Group 1","ix":1,"cix":2,"np":2,"it":[{"ty":"sh","bm":0,"hd":false,"mn":"ADBE Vector Shape - Group","nm":"Path 1","ix":1,"d":1,"ks":{"a":0,"k":{"c":true,"i":[[0,-77.215],[77.216,0],[0,77.215],[-77.215,0]],"o":[[0,77.215],[-77.215,0],[0,-77.215],[77.216,0]],"v":[[139.811,0],[0,139.811],[-139.811,0],[0,-139.811]]},"ix":2}},{"ty":"fl","bm":0,"hd":false,"mn":"ADBE Vector Graphic - Fill","nm":"Fill 1","c":{"a":0,"k":[0.8863,0.1176,0.1882],"ix":4},"r":1,"o":{"a":0,"k":100,"ix":5}},{"ty":"tr","a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"sk":{"a":0,"k":0,"ix":4},"p":{"a":0,"k":[140.061,140.061],"ix":2},"r":{"a":0,"k":0,"ix":6},"sa":{"a":0,"k":0,"ix":5},"o":{"a":0,"k":100,"ix":7}}]}],"ind":4}],"v":"5.7.11","fr":30,"op":60,"ip":0,"assets":[]} \ No newline at end of file diff --git a/assets/images/svg/checkin_location_icon.svg b/assets/images/svg/checkin_location_icon.svg new file mode 100644 index 0000000..de096c9 --- /dev/null +++ b/assets/images/svg/checkin_location_icon.svg @@ -0,0 +1,6 @@ + + + + + + diff --git a/assets/images/svg/checkin_nfc_icon.svg b/assets/images/svg/checkin_nfc_icon.svg new file mode 100644 index 0000000..b754bc5 --- /dev/null +++ b/assets/images/svg/checkin_nfc_icon.svg @@ -0,0 +1,8 @@ + + + + + + + + diff --git a/assets/images/svg/checkin_qr_icon.svg b/assets/images/svg/checkin_qr_icon.svg new file mode 100644 index 0000000..8588453 --- /dev/null +++ b/assets/images/svg/checkin_qr_icon.svg @@ -0,0 +1,10 @@ + + + + + + + + + + diff --git a/assets/images/svg/lab_result_icon.svg b/assets/images/svg/lab_result_icon.svg index 245048d..a8c442d 100644 --- a/assets/images/svg/lab_result_icon.svg +++ b/assets/images/svg/lab_result_icon.svg @@ -1,4 +1,5 @@ - - - + + + + diff --git a/assets/images/svg/prescription_item_icon.svg b/assets/images/svg/prescription_item_icon.svg new file mode 100644 index 0000000..6aba05f --- /dev/null +++ b/assets/images/svg/prescription_item_icon.svg @@ -0,0 +1,6 @@ + + + + + + diff --git a/assets/images/svg/radiology_icon.svg b/assets/images/svg/radiology_icon.svg new file mode 100644 index 0000000..e1ae996 --- /dev/null +++ b/assets/images/svg/radiology_icon.svg @@ -0,0 +1,4 @@ + + + + diff --git a/assets/images/svg/rebook_appointment_icon.svg b/assets/images/svg/rebook_appointment_icon.svg new file mode 100644 index 0000000..da4533a --- /dev/null +++ b/assets/images/svg/rebook_appointment_icon.svg @@ -0,0 +1,4 @@ + + + + diff --git a/assets/images/svg/report_icon.svg b/assets/images/svg/report_icon.svg new file mode 100644 index 0000000..97b6db8 --- /dev/null +++ b/assets/images/svg/report_icon.svg @@ -0,0 +1,3 @@ + + + diff --git a/lib/core/api/api_client.dart b/lib/core/api/api_client.dart index 7555400..2fd8082 100644 --- a/lib/core/api/api_client.dart +++ b/lib/core/api/api_client.dart @@ -17,6 +17,7 @@ import '../exceptions/api_failure.dart'; abstract class ApiClient { static final NavigationService _navigationService = getIt.get(); + Future post( String endPoint, { required Map body, @@ -93,7 +94,7 @@ class ApiClientImp implements ApiClient { required Map body, required Function(dynamic response, int statusCode, {int? messageStatus, String? errorMessage}) onSuccess, required Function(String error, int statusCode, {int? messageStatus, Failure? failureType}) onFailure, - bool isAllowAny = true, + bool isAllowAny = false, bool isExternal = false, bool isRCService = false, bool bypassConnectionCheck = false, @@ -109,187 +110,186 @@ class ApiClientImp implements ApiClient { } } // try { - var user = _appState.getAuthenticatedUser(); - Map headers = {'Content-Type': 'application/json', 'Accept': 'application/json'}; - if (!isExternal) { - String? token = _appState.appAuthToken; + var user = _appState.getAuthenticatedUser(); + Map headers = {'Content-Type': 'application/json', 'Accept': 'application/json'}; + if (!isExternal) { + String? token = _appState.appAuthToken; - if (body.containsKey('SetupID')) { - body['SetupID'] = body.containsKey('SetupID') ? body['SetupID'] ?? body[''] : SETUP_ID; - } else {} + if (body.containsKey('SetupID')) { + body['SetupID'] = body.containsKey('SetupID') ? body['SetupID'] ?? body[''] : SETUP_ID; + } else {} - if (body.containsKey('isDentalAllowedBackend')) { - body['isDentalAllowedBackend'] = body.containsKey('isDentalAllowedBackend') ? body['isDentalAllowedBackend'] ?? IS_DENTAL_ALLOWED_BACKEND : IS_DENTAL_ALLOWED_BACKEND; - } + if (body.containsKey('isDentalAllowedBackend')) { + body['isDentalAllowedBackend'] = body.containsKey('isDentalAllowedBackend') ? body['isDentalAllowedBackend'] ?? IS_DENTAL_ALLOWED_BACKEND : IS_DENTAL_ALLOWED_BACKEND; + } - if (!body.containsKey('IsPublicRequest')) { - // if (!body.containsKey('PatientType')) { - if (user != null && user.patientType != null) { - body['PatientType'] = user.patientType; - } else { - body['PatientType'] = PATIENT_TYPE.toString(); - } + if (!body.containsKey('IsPublicRequest')) { + // if (!body.containsKey('PatientType')) { + if (user != null && user.patientType != null) { + body['PatientType'] = user.patientType; + } else { + body['PatientType'] = PATIENT_TYPE.toString(); + } - if (user != null && user.patientType != null) { - body['PatientTypeID'] = user.patientType; - } else { - body['PatientType'] = PATIENT_TYPE_ID.toString(); - } + if (user != null && user.patientType != null) { + body['PatientTypeID'] = user.patientType; + } else { + body['PatientType'] = PATIENT_TYPE_ID.toString(); + } - // TODO : These should be from the appState - if (user != null) { - body['TokenID'] = body['TokenID'] ?? token; - body['PatientID'] = body['PatientID'] ?? user.patientId; + // TODO : These should be from the appState + if (user != null) { + body['TokenID'] = body['TokenID'] ?? token; + body['PatientID'] = body['PatientID'] ?? user.patientId; - body['PatientOutSA'] = body.containsKey('PatientOutSA') ? body['PatientOutSA'] ?? user.outSa : user.outSa; - body['SessionID'] = body['TokenID'] == null ? ApiConsts.sessionID : getSessionId(body['TokenID'] ?? ""); //getSe - } - // else { - // body['SessionID'] = body['TokenID'] == null ? ApiConsts.sessionID : getSessionId(body['TokenID'] ?? ""); //getSe - // - // } + body['PatientOutSA'] = body.containsKey('PatientOutSA') ? body['PatientOutSA'] ?? user.outSa : user.outSa; + body['SessionID'] = body['TokenID'] == null ? ApiConsts.sessionID : getSessionId(body['TokenID'] ?? ""); //getSe } + // else { + // body['SessionID'] = body['TokenID'] == null ? ApiConsts.sessionID : getSessionId(body['TokenID'] ?? ""); //getSe + // + // } } + } - // request.versionID = VERSION_ID; - // request.channel = CHANNEL; - // request.iPAdress = IP_ADDRESS; - // request.generalid = GENERAL_ID; - // request.languageID = (languageID == 'ar' ? 1 : 2); - // request.patientOutSA = (request.zipCode == '966' || request.zipCode == '+966') ? 0 : 1; - - // body['VersionID'] = ApiConsts.appVersionID.toString(); - if (!isExternal) { - body['VersionID'] = "50.0"; - body['Channel'] = ApiConsts.appChannelId.toString(); - body['IPAdress'] = ApiConsts.appIpAddress; - body['generalid'] = ApiConsts.appGeneralId; - - body['LanguageID'] = _appState.getLanguageID().toString(); - body['Latitude'] = _appState.userLat.toString(); - body['Longitude'] = _appState.userLong.toString(); - body['DeviceTypeID'] = _appState.deviceTypeID; - if (_appState.appAuthToken.isNotEmpty) { - body[_appState.isAuthenticated ? 'TokenID' : 'LogInTokenID'] = _appState.appAuthToken; - } - - // body['TokenID'] = "@dm!n"; - // body['PatientID'] = "4767477"; + // request.versionID = VERSION_ID; + // request.channel = CHANNEL; + // request.iPAdress = IP_ADDRESS; + // request.generalid = GENERAL_ID; + // request.languageID = (languageID == 'ar' ? 1 : 2); + // request.patientOutSA = (request.zipCode == '966' || request.zipCode == '+966') ? 0 : 1; + + // body['VersionID'] = ApiConsts.appVersionID.toString(); + if (!isExternal) { + body['VersionID'] = "50.0"; + body['Channel'] = ApiConsts.appChannelId.toString(); + body['IPAdress'] = ApiConsts.appIpAddress; + body['generalid'] = ApiConsts.appGeneralId; + + body['LanguageID'] = _appState.getLanguageID().toString(); + body['Latitude'] = _appState.userLat.toString(); + body['Longitude'] = _appState.userLong.toString(); + body['DeviceTypeID'] = _appState.deviceTypeID; + if (_appState.appAuthToken.isNotEmpty) { + body[_appState.isAuthenticated ? 'TokenID' : 'LogInTokenID'] = _appState.appAuthToken; } - body.removeWhere((key, value) => value == null); - log("body: ${json.encode(body)}"); - log("uri: ${Uri.parse(url.trim())}"); + // body['TokenID'] = "@dm!n"; + // body['PatientID'] = "4767477"; + } - final bool networkStatus = await Utils.checkConnection(bypassConnectionCheck: bypassConnectionCheck); + body.removeWhere((key, value) => value == null); + log("body: ${json.encode(body)}"); + log("uri: ${Uri.parse(url.trim())}"); - if (!networkStatus) { - onFailure( - 'Please Check The Internet Connection 1', - -1, - failureType: ConnectivityFailure("Please Check The Internet Connection 1"), - ); - _analytics.errorTracking.log("internet_connectivity", error: "no internet available"); - return; - } + final bool networkStatus = await Utils.checkConnection(bypassConnectionCheck: bypassConnectionCheck); - final response = await http.post(Uri.parse(url.trim()), body: json.encode(body), headers: headers); - final int statusCode = response.statusCode; - log("response.body: ${response.body}"); - if (statusCode < 200 || statusCode >= 400) { - var parsed = json.decode(utf8.decode(response.bodyBytes)); - onFailure('Error While Fetching data', statusCode, failureType: StatusCodeFailure("Error While Fetching data")); - logApiEndpointError(endPoint, 'Error While Fetching data', statusCode); + if (!networkStatus) { + onFailure( + 'Please Check The Internet Connection 1', + -1, + failureType: ConnectivityFailure("Please Check The Internet Connection 1"), + ); + _analytics.errorTracking.log("internet_connectivity", error: "no internet available"); + return; + } + + final response = await http.post(Uri.parse(url.trim()), body: json.encode(body), headers: headers); + final int statusCode = response.statusCode; + log("response.body: ${response.body}"); + if (statusCode < 200 || statusCode >= 400) { + var parsed = json.decode(utf8.decode(response.bodyBytes)); + onFailure('Error While Fetching data', statusCode, failureType: StatusCodeFailure("Error While Fetching data")); + logApiEndpointError(endPoint, 'Error While Fetching data', statusCode); + } else { + var parsed = json.decode(utf8.decode(response.bodyBytes)); + if (isAllowAny) { + onSuccess(parsed, statusCode, messageStatus: parsed['MessageStatus'], errorMessage: parsed['ErrorEndUserMessage']); } else { - var parsed = json.decode(utf8.decode(response.bodyBytes)); - if (isAllowAny) { - onSuccess(parsed, statusCode, messageStatus: 1, errorMessage: ""); + if (parsed['Response_Message'] != null) { + onSuccess(parsed, statusCode, messageStatus: parsed['MessageStatus'], errorMessage: parsed['ErrorEndUserMessage'] ?? parsed['ErrorMessage']); } else { - if (parsed['Response_Message'] != null) { + if (parsed['ErrorType'] == 4) { + //TODO : handle app update + logApiEndpointError(endPoint, parsed['ErrorEndUserMessage'] ?? parsed['ErrorMessage'], statusCode); + } + if (parsed['ErrorType'] == 2) { + // todo: handle Logout + logApiEndpointError(endPoint, "session logged out", statusCode); + } + if (isAllowAny) { onSuccess(parsed, statusCode, messageStatus: parsed['MessageStatus'], errorMessage: parsed['ErrorEndUserMessage'] ?? parsed['ErrorMessage']); - } else { - if (parsed['ErrorType'] == 4) { - //TODO : handle app update + } else if (parsed['IsAuthenticated'] == null) { + if (parsed['isSMSSent'] == true) { + onSuccess(parsed, statusCode, messageStatus: parsed['MessageStatus'], errorMessage: parsed['ErrorEndUserMessage'] ?? parsed['ErrorMessage']); + } else if (parsed['MessageStatus'] == 1) { + onSuccess(parsed, statusCode, messageStatus: parsed['MessageStatus'], errorMessage: parsed['ErrorEndUserMessage'] ?? parsed['ErrorMessage']); + } else if (parsed['Result'] == 'OK') { + onSuccess(parsed, statusCode, messageStatus: parsed['MessageStatus'], errorMessage: parsed['ErrorEndUserMessage'] ?? parsed['ErrorMessage']); + } else { + onFailure( + parsed['ErrorEndUserMessage'] ?? parsed['ErrorMessage'], + statusCode, + failureType: MessageStatusFailure(parsed['ErrorEndUserMessage'] ?? parsed['ErrorMessage']), + ); logApiEndpointError(endPoint, parsed['ErrorEndUserMessage'] ?? parsed['ErrorMessage'], statusCode); } - if (parsed['ErrorType'] == 2) { - // todo: handle Logout - logApiEndpointError(endPoint, "session logged out", statusCode); - } - if (isAllowAny) { - onSuccess(parsed, statusCode, messageStatus: parsed['MessageStatus'], errorMessage: parsed['ErrorEndUserMessage'] ?? parsed['ErrorMessage']); - } else if (parsed['IsAuthenticated'] == null) { - if (parsed['isSMSSent'] == true) { - onSuccess(parsed, statusCode, messageStatus: parsed['MessageStatus'], errorMessage: parsed['ErrorEndUserMessage'] ?? parsed['ErrorMessage']); - } else if (parsed['MessageStatus'] == 1) { - onSuccess(parsed, statusCode, messageStatus: parsed['MessageStatus'], errorMessage: parsed['ErrorEndUserMessage'] ?? parsed['ErrorMessage']); - } else if (parsed['Result'] == 'OK') { - onSuccess(parsed, statusCode, messageStatus: parsed['MessageStatus'], errorMessage: parsed['ErrorEndUserMessage'] ?? parsed['ErrorMessage']); - } else { - onFailure( - parsed['ErrorEndUserMessage'] ?? parsed['ErrorMessage'], - statusCode, - failureType: MessageStatusFailure(parsed['ErrorEndUserMessage'] ?? parsed['ErrorMessage']), - ); - logApiEndpointError(endPoint, parsed['ErrorEndUserMessage'] ?? parsed['ErrorMessage'], statusCode); - } - } else if (parsed['MessageStatus'] == 1 || parsed['SMSLoginRequired'] == true) { + } else if (parsed['MessageStatus'] == 1 || parsed['SMSLoginRequired'] == true) { + onSuccess(parsed, statusCode, messageStatus: parsed['MessageStatus'], errorMessage: parsed['ErrorEndUserMessage'] ?? parsed['ErrorMessage']); + } else if (parsed['MessageStatus'] == 2 && parsed['IsAuthenticated']) { + if (parsed['SameClinicApptList'] != null) { onSuccess(parsed, statusCode, messageStatus: parsed['MessageStatus'], errorMessage: parsed['ErrorEndUserMessage'] ?? parsed['ErrorMessage']); - } else if (parsed['MessageStatus'] == 2 && parsed['IsAuthenticated']) { - if (parsed['SameClinicApptList'] != null) { - onSuccess(parsed, statusCode, messageStatus: parsed['MessageStatus'], errorMessage: parsed['ErrorEndUserMessage'] ?? parsed['ErrorMessage']); - } else { - if (parsed['message'] == null && parsed['ErrorEndUserMessage'] == null) { - if (parsed['ErrorSearchMsg'] == null) { - onFailure( - "Server Error found with no available message", - statusCode, - failureType: ServerFailure("Error While Fetching data"), - ); - logApiEndpointError(endPoint, "Server Error found with no available message", statusCode); - } else { - onFailure( - parsed['ErrorSearchMsg'], - statusCode, - failureType: ServerFailure("Error While Fetching data"), - ); - logApiEndpointError(endPoint, parsed['ErrorSearchMsg'], statusCode); - } - } else { - onFailure( - parsed['message'] ?? parsed['ErrorEndUserMessage'] ?? parsed['ErrorMessage'], - statusCode, - failureType: UserIntimationFailure(parsed['ErrorEndUserMessage'] ?? parsed['ErrorMessage']), - ); - logApiEndpointError(endPoint, parsed['message'] ?? parsed['message'], statusCode); - } - } - } else if (!parsed['IsAuthenticated']) { - } else { - if (parsed['SameClinicApptList'] != null) { - onSuccess(parsed, statusCode, messageStatus: parsed['MessageStatus'], errorMessage: parsed['ErrorEndUserMessage'] ?? parsed['ErrorMessage']); - } else { - if (parsed['message'] != null) { + if (parsed['message'] == null && parsed['ErrorEndUserMessage'] == null) { + if (parsed['ErrorSearchMsg'] == null) { onFailure( - parsed['message'] ?? parsed['message'], + "Server Error found with no available message", statusCode, failureType: ServerFailure("Error While Fetching data"), ); - logApiEndpointError(endPoint, parsed['message'] ?? parsed['message'], statusCode); + logApiEndpointError(endPoint, "Server Error found with no available message", statusCode); } else { onFailure( - parsed['ErrorEndUserMessage'] ?? parsed['ErrorMessage'], + parsed['ErrorSearchMsg'], statusCode, failureType: ServerFailure("Error While Fetching data"), ); - logApiEndpointError(endPoint, parsed['ErrorEndUserMessage'] ?? parsed['ErrorMessage'], statusCode); + logApiEndpointError(endPoint, parsed['ErrorSearchMsg'], statusCode); } + } else { + onFailure( + parsed['message'] ?? parsed['ErrorEndUserMessage'] ?? parsed['ErrorMessage'], + statusCode, + failureType: UserIntimationFailure(parsed['ErrorEndUserMessage'] ?? parsed['ErrorMessage']), + ); + logApiEndpointError(endPoint, parsed['message'] ?? parsed['message'], statusCode); + } + } + } else if (!parsed['IsAuthenticated']) { + } else { + if (parsed['SameClinicApptList'] != null) { + onSuccess(parsed, statusCode, messageStatus: parsed['MessageStatus'], errorMessage: parsed['ErrorEndUserMessage'] ?? parsed['ErrorMessage']); + } else { + if (parsed['message'] != null) { + onFailure( + parsed['message'] ?? parsed['message'], + statusCode, + failureType: ServerFailure("Error While Fetching data"), + ); + logApiEndpointError(endPoint, parsed['message'] ?? parsed['message'], statusCode); + } else { + onFailure( + parsed['ErrorEndUserMessage'] ?? parsed['ErrorMessage'], + statusCode, + failureType: ServerFailure("Error While Fetching data"), + ); + logApiEndpointError(endPoint, parsed['ErrorEndUserMessage'] ?? parsed['ErrorMessage'], statusCode); } } } } } + } // } catch (e, stackTrace) { // _loggerService.errorLogs(stackTrace.toString()); // if (e.toString().contains("ClientException")) { diff --git a/lib/core/api_consts.dart b/lib/core/api_consts.dart index 480ea2d..daa094e 100644 --- a/lib/core/api_consts.dart +++ b/lib/core/api_consts.dart @@ -766,6 +766,7 @@ class ApiConsts { SERVICE_URL = 'https://hmgwebservices.com/PayFortWeb/pages/SendPayFortRequest.aspx'; break; case AppEnvironmentTypeEnum.preProd: + baseUrl = "https://webservices.hmg.com/"; payFortEnvironment = FortEnvironment.production; applePayMerchantId = "merchant.com.hmgwebservices"; @@ -799,6 +800,11 @@ class ApiConsts { static final String checkPatientForRegistration = 'Services/Authentication.svc/REST/CheckPatientForRegisteration'; static final String checkUserStatus= 'Services/NHIC.svc/REST/GetPatientInfo'; + static final String insertPatientDeviceIMEIData = 'Services/Patients.svc/REST/Patient_INSERTDeviceIMEI'; + static final String insertPatientMobileData = 'Services/MobileNotifications.svc/REST/Insert_PatientMobileDeviceInfo'; + + static final String getPrivileges = 'Services/Patients.svc/REST/Service_Privilege'; + // static values for Api static final double appVersionID = 18.7; static final int appChannelId = 3; diff --git a/lib/core/app_assets.dart b/lib/core/app_assets.dart index 76c643b..a2cbc49 100644 --- a/lib/core/app_assets.dart +++ b/lib/core/app_assets.dart @@ -86,7 +86,13 @@ class AppAssets { static const String directions_icon = '$svgBasePath/directions_icon.svg'; static const String apple_pay_button = '$svgBasePath/pay_with_apple_pay.svg'; static const String reminder_bell = '$svgBasePath/reminder_bell.svg'; - + static const String rebook_appointment_icon = '$svgBasePath/rebook_appointment_icon.svg'; + static const String report_icon = '$svgBasePath/report_icon.svg'; + static const String radiology_icon = '$svgBasePath/radiology_icon.svg'; + static const String prescription_item_icon = '$svgBasePath/prescription_item_icon.svg'; + static const String checkin_location_icon = '$svgBasePath/checkin_location_icon.svg'; + static const String checkin_nfc_icon = '$svgBasePath/checkin_nfc_icon.svg'; + static const String checkin_qr_icon = '$svgBasePath/checkin_qr_icon.svg'; //bottom navigation// static const String homeBottom = '$svgBasePath/home_bottom.svg'; @@ -115,4 +121,5 @@ class AppAnimations { static const String register = '$lottieBasePath/register.json'; static const String checkmark = '$lottieBasePath/checkmark.json'; static const String loadingAnimation = '$lottieBasePath/Loader.json'; + static const String errorAnimation = '$lottieBasePath/ErrorAnimation.json'; } diff --git a/lib/core/app_state.dart b/lib/core/app_state.dart index e5537c6..3c4555e 100644 --- a/lib/core/app_state.dart +++ b/lib/core/app_state.dart @@ -1,12 +1,17 @@ import 'dart:io'; import 'package:easy_localization/easy_localization.dart'; -import 'package:hmg_patient_app_new/core/common_models/VidaPlusProjectListModel.dart'; +import 'package:hmg_patient_app_new/core/common_models/privilege/HMCProjectListModel.dart'; +import 'package:hmg_patient_app_new/core/common_models/privilege/PrivilegeModel.dart'; +import 'package:hmg_patient_app_new/core/common_models/privilege/ProjectDetailListModel.dart'; +import 'package:hmg_patient_app_new/core/common_models/privilege/VidaPlusProjectListModel.dart'; import 'package:hmg_patient_app_new/features/authentication/models/resp_models/authenticated_user_resp_model.dart'; 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 '../features/authentication/models/request_models/registration_payload_model.dart'; + class AppState { NavigationService navigationService; @@ -35,7 +40,7 @@ class AppState { if (isFamily) { _authenticatedChildUser = authenticatedUser; } else { - setIsAuthenticated = true; + setIsAuthenticated = true; _authenticatedRootUser = authenticatedUser; } } @@ -91,11 +96,26 @@ class AppState { set setDeviceTypeID(v) => deviceTypeID = v; List vidaPlusProjectList = []; + List privilegeModelList = []; + List hMCProjectListModel = []; + List projectDetailListModel = []; setVidaPlusProjectList(List vidaPlusProjectListModelInput) { vidaPlusProjectList = vidaPlusProjectListModelInput; } + setPrivilegeModelList(List privilegeModelListInput) { + privilegeModelList = privilegeModelListInput; + } + + setHMCProjectList(List hMCProjectListModelInput) { + hMCProjectListModel = hMCProjectListModelInput; + } + + setProjectsDetailList(List projectDetailListModelInput) { + projectDetailListModel = projectDetailListModelInput; + } + CheckUserStatusResponseNHIC? _nHICUserData; CheckUserStatusResponseNHIC get getNHICUserData => _nHICUserData!; @@ -103,4 +123,12 @@ class AppState { set setNHICUserData(CheckUserStatusResponseNHIC value) { _nHICUserData = value; } + + RegistrationDataModelPayload? _userRegistrationPayload; + + RegistrationDataModelPayload get getUserRegistrationPayload => _userRegistrationPayload ?? RegistrationDataModelPayload(); + + set setUserRegistrationPayload(RegistrationDataModelPayload value) { + _userRegistrationPayload = value; + } } diff --git a/lib/core/common_models/privilege/HMCProjectListModel.dart b/lib/core/common_models/privilege/HMCProjectListModel.dart new file mode 100644 index 0000000..6af7bfb --- /dev/null +++ b/lib/core/common_models/privilege/HMCProjectListModel.dart @@ -0,0 +1,15 @@ +class HMCProjectListModel { + int? projectID; + + HMCProjectListModel({this.projectID}); + + HMCProjectListModel.fromJson(Map json) { + projectID = json['ProjectID']; + } + + Map toJson() { + final Map data = new Map(); + data['ProjectID'] = this.projectID; + return data; + } +} diff --git a/lib/core/common_models/privilege/PrivilegeModel.dart b/lib/core/common_models/privilege/PrivilegeModel.dart new file mode 100644 index 0000000..587490b --- /dev/null +++ b/lib/core/common_models/privilege/PrivilegeModel.dart @@ -0,0 +1,24 @@ +class PrivilegeModel { + int? iD; + String? serviceName; + bool? privilege; + dynamic region; + + PrivilegeModel({this.iD, this.serviceName, this.privilege, this.region}); + + PrivilegeModel.fromJson(Map json) { + iD = json['ID']; + serviceName = json['ServiceName']; + privilege = json['Previlege']; + region = json['Region']; + } + + Map toJson() { + final Map data = new Map(); + data['ID'] = this.iD; + data['ServiceName'] = this.serviceName; + data['Previlege'] = this.privilege; + data['Region'] = this.region; + return data; + } +} diff --git a/lib/core/common_models/privilege/ProjectDetailListModel.dart b/lib/core/common_models/privilege/ProjectDetailListModel.dart new file mode 100644 index 0000000..782c376 --- /dev/null +++ b/lib/core/common_models/privilege/ProjectDetailListModel.dart @@ -0,0 +1,32 @@ +class ProjectDetailListModel { + int? projectID; + String? latitude; + String? longitude; + int? geofenceRadius; + String? checkInQrCode; + + ProjectDetailListModel( + {this.projectID, + this.latitude, + this.longitude, + this.geofenceRadius, + this.checkInQrCode}); + + ProjectDetailListModel.fromJson(Map json) { + projectID = json['ProjectID']; + latitude = json['Latitude']; + longitude = json['Longitude']; + geofenceRadius = json['GeofenceRadius']; + checkInQrCode = json['CheckInQrCode']; + } + + Map toJson() { + final Map data = new Map(); + data['ProjectID'] = this.projectID; + data['Latitude'] = this.latitude; + data['Longitude'] = this.longitude; + data['GeofenceRadius'] = this.geofenceRadius; + data['CheckInQrCode'] = this.checkInQrCode; + return data; + } +} diff --git a/lib/core/common_models/VidaPlusProjectListModel.dart b/lib/core/common_models/privilege/VidaPlusProjectListModel.dart similarity index 100% rename from lib/core/common_models/VidaPlusProjectListModel.dart rename to lib/core/common_models/privilege/VidaPlusProjectListModel.dart diff --git a/lib/core/utils/push_notification_handler.dart b/lib/core/utils/push_notification_handler.dart index 1d2141e..4568a15 100644 --- a/lib/core/utils/push_notification_handler.dart +++ b/lib/core/utils/push_notification_handler.dart @@ -351,6 +351,7 @@ class PushNotificationHandler { } onToken(String token) async { + print("Push Notification Token: " + token); await Utils.saveStringFromPrefs(CacheConst.pushToken, token); } diff --git a/lib/core/utils/request_utils.dart b/lib/core/utils/request_utils.dart index 1045d9c..f54432c 100644 --- a/lib/core/utils/request_utils.dart +++ b/lib/core/utils/request_utils.dart @@ -20,6 +20,9 @@ class RequestUtils { bool fileNo = false; if (nationId.isNotEmpty) { fileNo = nationId.length < 10; + if (fileNo) { + patientId = int.tryParse(nationId); + } } var request = SendActivationRequest(); if (phoneNumber.isNotEmpty) { @@ -51,17 +54,17 @@ class RequestUtils { return request; } - static dynamic getCommonRequestWelcome({ - required String phoneNumber, - required OTPTypeEnum otpTypeEnum, - required String? deviceToken, - required bool patientOutSA, - required String? loginTokenID, - required var registeredData, - int? patientId, - required String nationIdText, - required String countryCode, - }) { + static dynamic getCommonRequestWelcome( + {required String phoneNumber, + required OTPTypeEnum otpTypeEnum, + required String? deviceToken, + required bool patientOutSA, + required String? loginTokenID, + required var registeredData, + int? patientId, + required String nationIdText, + required String countryCode, + required int loginType}) { bool fileNo = false; if (nationIdText.isNotEmpty) { fileNo = nationIdText.length < 10; @@ -71,7 +74,7 @@ class RequestUtils { request.mobileNo = '0$phoneNumber'; request.deviceToken = deviceToken; request.projectOutSA = patientOutSA; - request.loginType = otpTypeEnum.toInt(); + request.loginType = loginType; request.oTPSendType = otpTypeEnum.toInt(); // could map OTPTypeEnum if needed request.zipCode = countryCode; // or countryCode if defined elsewhere request.logInTokenID = loginTokenID ?? ""; @@ -85,7 +88,7 @@ class RequestUtils { } else { if (fileNo) { request.patientID = patientId ?? int.parse(nationIdText); - request.patientIdentificationID = request.nationalID = "" as int?; + request.patientIdentificationID = request.nationalID = 0; request.searchType = 2; } else { request.patientID = 0; diff --git a/lib/core/utils/utils.dart b/lib/core/utils/utils.dart index 75c0cf9..ebb7037 100644 --- a/lib/core/utils/utils.dart +++ b/lib/core/utils/utils.dart @@ -10,6 +10,7 @@ import 'package:fluttertoast/fluttertoast.dart'; import 'package:google_api_availability/google_api_availability.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/privilege/ProjectDetailListModel.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'; @@ -22,6 +23,8 @@ import 'package:hmg_patient_app_new/widgets/loading_dialog.dart'; import 'package:lottie/lottie.dart'; import 'package:shared_preferences/shared_preferences.dart'; +import 'dart:math' as dartMath; + class Utils { static AppState appState = getIt.get(); static NavigationService navigationService = getIt.get(); @@ -323,6 +326,19 @@ class Utils { ).center; } + static Widget getErrorWidget({String? loadingText}) { + return Column( + mainAxisAlignment: MainAxisAlignment.center, + crossAxisAlignment: CrossAxisAlignment.center, + children: [ + Lottie.asset(AppAnimations.errorAnimation, repeat: true, reverse: false, frameRate: FrameRate(60), width: 100.h, height: 100.h, fit: BoxFit.fill), + SizedBox(height: 8.h), + (loadingText ?? LocaleKeys.loadingText.tr()).toText16(color: AppColors.blackColor), + SizedBox(height: 8.h), + ], + ).center; + } + static bool isVidaPlusProject(AppState appState, int projectID) { bool isVidaPlus = false; for (var element in appState.vidaPlusProjectList) { @@ -333,6 +349,34 @@ class Utils { return isVidaPlus; } + static ProjectDetailListModel getProjectDetailObj(AppState appState, int projectID) { + ProjectDetailListModel projectDetailListModel = ProjectDetailListModel(); + for (var element in appState.projectDetailListModel) { + if (element.projectID == projectID) { + projectDetailListModel = element; + } + } + return projectDetailListModel; + } + + static double distance(double lat1, double lon1, double lat2, double lon2) { + const r = 6372.8; // Earth radius in kilometers + + final dLat = _toRadians(lat2 - lat1); + final dLon = _toRadians(lon2 - lon1); + final lat1Radians = _toRadians(lat1); + final lat2Radians = _toRadians(lat2); + + final a = _haversin(dLat) + dartMath.cos(lat1Radians) * dartMath.cos(lat2Radians) * _haversin(dLon); + final c = 2 * dartMath.asin(dartMath.sqrt(a)); + + return r * c; + } + + static double _toRadians(double degrees) => degrees * dartMath.pi / 180; + + static num _haversin(double radians) => dartMath.pow(dartMath.sin(radians / 2), 2); + static getPhoneNumberWithoutZero(String number) { String newNumber = ""; if (number.startsWith('0')) { diff --git a/lib/features/authentication/authentication_repo.dart b/lib/features/authentication/authentication_repo.dart index 21061dc..d81094c 100644 --- a/lib/features/authentication/authentication_repo.dart +++ b/lib/features/authentication/authentication_repo.dart @@ -4,12 +4,15 @@ import 'package:dartz/dartz.dart'; import 'package:hmg_patient_app_new/core/api/api_client.dart'; import 'package:hmg_patient_app_new/core/api_consts.dart'; import 'package:hmg_patient_app_new/core/common_models/generic_api_model.dart'; +import 'package:hmg_patient_app_new/core/common_models/privilege/PrivilegeModel.dart'; import 'package:hmg_patient_app_new/core/exceptions/api_failure.dart'; import 'package:hmg_patient_app_new/features/authentication/models/request_models/check_activation_code_register_request_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/logger_service.dart'; abstract class AuthenticationRepo { + Future>> getServicePrivilege(); + Future>> selectDeviceByImei({required String firebaseToken}); Future>> checkPatientAuthentication({required dynamic checkPatientAuthenticationReq}); @@ -28,6 +31,10 @@ abstract class AuthenticationRepo { Future>> checkPatientForRegistration({required dynamic commonAuthanticatedRequest}); Future>> checkUserStatus({required dynamic commonAuthanticatedRequest}); + + Future>> insertPatientIMEIData({required dynamic patientIMEIDataRequest}); + + Future>> insertPatientDeviceData({required dynamic patientDeviceDataRequest}); } class AuthenticationRepoImp implements AuthenticationRepo { @@ -38,7 +45,7 @@ class AuthenticationRepoImp implements AuthenticationRepo { @override Future>> selectDeviceByImei({required String firebaseToken}) async { - final mapDevice = {"IMEI": firebaseToken}; + Map mapDevice = {"IMEI": firebaseToken}; try { GenericApiModel? apiResponse; Failure? failure; @@ -52,7 +59,13 @@ class AuthenticationRepoImp implements AuthenticationRepo { try { final list = response['Patient_SELECTDeviceIMEIbyIMEIList']; if (list == null || list.isEmpty) { - throw Exception("Device list is empty"); + apiResponse = GenericApiModel( + messageStatus: messageStatus, + statusCode: statusCode, + errorMessage: errorMessage, + data: null, + ); + return; } final model = SelectDeviceByImeiRespModelElement.fromJson(list[0] as Map); @@ -160,12 +173,18 @@ class AuthenticationRepoImp implements AuthenticationRepo { required String? activationCode, required bool isRegister, }) async { - 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; + 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; + } final endpoint = isRegister ? ApiConsts.checkActivationCodeRegister : ApiConsts.checkActivationCode; @@ -175,7 +194,7 @@ class AuthenticationRepoImp implements AuthenticationRepo { await apiClient.post( endpoint, - body: newRequest.toJson(), + body: isRegister ? newRequest : newRequest.toJson(), onFailure: (error, statusCode, {messageStatus, failureType}) { failure = failureType; }, @@ -337,4 +356,104 @@ class AuthenticationRepoImp implements AuthenticationRepo { return Left(UnknownFailure(e.toString())); } } + + @override + Future> insertPatientIMEIData({required patientIMEIDataRequest}) { + try { + GenericApiModel? apiResponse; + Failure? failure; + return apiClient.post( + ApiConsts.insertPatientDeviceIMEIData, + body: patientIMEIDataRequest, + 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()); + } + }, + ).then((_) { + if (failure != null) return Left(failure!); + if (apiResponse == null) return Left(ServerFailure("Unknown error")); + return Right(apiResponse!); + }); + } catch (e) { + return Future.value(Left(UnknownFailure(e.toString()))); + } + } + + @override + Future> insertPatientDeviceData({required patientDeviceDataRequest}) { + try { + GenericApiModel? apiResponse; + Failure? failure; + return apiClient.post( + ApiConsts.insertPatientMobileData, + body: patientDeviceDataRequest, + 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()); + } + }, + ).then((_) { + if (failure != null) return Left(failure!); + if (apiResponse == null) return Left(ServerFailure("Unknown error")); + return Right(apiResponse!); + }); + } catch (e) { + return Future.value(Left(UnknownFailure(e.toString()))); + } + } + + @override + Future>> getServicePrivilege() { + Map mapDevice = {}; + try { + GenericApiModel? apiResponse; + Failure? failure; + return apiClient.post( + ApiConsts.getPrivileges, + body: mapDevice, + 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()); + } + }, + ).then((_) { + if (failure != null) return Left(failure!); + if (apiResponse == null) return Left(ServerFailure("Unknown error")); + return Right(apiResponse!); + }); + } catch (e) { + return Future.value(Left(UnknownFailure(e.toString()))); + } + } } diff --git a/lib/features/authentication/authentication_view_model.dart b/lib/features/authentication/authentication_view_model.dart index c451c40..0add5a0 100644 --- a/lib/features/authentication/authentication_view_model.dart +++ b/lib/features/authentication/authentication_view_model.dart @@ -1,11 +1,15 @@ import 'dart:convert'; import 'dart:developer'; - +import 'package:flutter/services.dart' show rootBundle; import 'package:flutter/material.dart'; import 'package:hijri_gregorian_calendar/hijri_gregorian_calendar.dart'; import 'package:hmg_patient_app_new/core/app_state.dart'; import 'package:hmg_patient_app_new/core/cache_consts.dart'; import 'package:hmg_patient_app_new/core/common_models/nationality_country_model.dart'; +import 'package:hmg_patient_app_new/core/common_models/privilege/HMCProjectListModel.dart'; +import 'package:hmg_patient_app_new/core/common_models/privilege/PrivilegeModel.dart'; +import 'package:hmg_patient_app_new/core/common_models/privilege/ProjectDetailListModel.dart'; +import 'package:hmg_patient_app_new/core/common_models/privilege/VidaPlusProjectListModel.dart'; import 'package:hmg_patient_app_new/core/enums.dart'; import 'package:hmg_patient_app_new/core/utils/loading_utils.dart'; import 'package:hmg_patient_app_new/core/utils/request_utils.dart'; @@ -14,6 +18,7 @@ import 'package:hmg_patient_app_new/core/utils/validation_utils.dart'; import 'package:hmg_patient_app_new/extensions/string_extensions.dart'; import 'package:hmg_patient_app_new/features/authentication/authentication_repo.dart'; import 'package:hmg_patient_app_new/features/authentication/models/request_models/check_activation_code_register_request_model.dart'; +import 'package:hmg_patient_app_new/features/authentication/models/request_models/registration_payload_model.dart'; import 'package:hmg_patient_app_new/features/authentication/models/resp_models/check_activation_code_resp_model.dart'; 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'; @@ -26,6 +31,9 @@ import 'package:hmg_patient_app_new/services/error_handler_service.dart'; import 'package:hmg_patient_app_new/services/localauth_service.dart'; import 'package:hmg_patient_app_new/services/navigation_service.dart'; +import 'models/request_models/insert_patient_mobile_deviceinfo.dart'; +import 'models/request_models/patient_insert_device_imei_request.dart'; + class AuthenticationViewModel extends ChangeNotifier { final AuthenticationRepo _authenticationRepo; final AppState _appState; @@ -49,7 +57,11 @@ class AuthenticationViewModel extends ChangeNotifier { _authenticationRepo = authenticationRepo, _localAuthService = localAuthService; - final TextEditingController nationalIdController = TextEditingController(), phoneNumberController = TextEditingController(), dobController = TextEditingController(); + final TextEditingController nationalIdController = TextEditingController(), + phoneNumberController = TextEditingController(), + dobController = TextEditingController(), + nameController = TextEditingController(), + emailController = TextEditingController(); CountryEnum selectedCountrySignup = CountryEnum.saudiArabia; MaritalStatusTypeEnum? maritalStatus; GenderTypeEnum? genderType; @@ -59,26 +71,12 @@ class AuthenticationViewModel extends ChangeNotifier { NationalityCountries? pickedCountryByUAEUser; CalenderEnum calenderType = CalenderEnum.gregorian; + LoginTypeEnum loginTypeEnum = LoginTypeEnum.sms; //================== - bool isDubai = false; - bool authenticated = false; - late int mobileNumber; String errorMsg = ''; - var registerd_data; - bool isMoreOption = false; - var zipCode; - var patientOutSA; - var loginTokenID; - var loginType; - var deviceToken; - var lastLogin; final FocusNode myFocusNode = FocusNode(); - late int selectedOption = 1; - bool onlySMSBox = false; - int fingrePrintBefore = 0; - var healthId; Future onLoginPressed() async { @@ -134,8 +132,8 @@ class AuthenticationViewModel extends ChangeNotifier { notifyListeners(); } - void loadCountriesData({required BuildContext context}) async { - final String response = await DefaultAssetBundle.of(context).loadString('assets/json/countriesList.json'); + Future loadCountriesData() async { + final String response = await rootBundle.loadString('assets/json/countriesList.json'); final List data = json.decode(response); countriesList = data.map((e) => NationalityCountries.fromJson(e)).toList(); } @@ -164,11 +162,23 @@ class AuthenticationViewModel extends ChangeNotifier { notifyListeners(); } + bool isUserFromUAE() { + bool isFromUAE = false; + if (_appState.getUserRegistrationPayload.patientOutSa != 0) { + isFromUAE = true; + } + return isFromUAE; + } + + void savePushTokenToAppState() async { + _appState.deviceToken = await Utils.getStringFromPrefs(CacheConst.pushToken); + } + Future selectDeviceImei({required Function(dynamic data) onSuccess, Function(String)? onError}) async { // LoadingUtils.showFullScreenLoading(); // String firebaseToken = _appState.deviceToken; - // String firebaseToken = await Utils.getStringFromPrefs(CacheConst.pushToken); - String firebaseToken = "fY1fq_cITMmUCztA3UKKL9:APA91bEb2ZcdCPQPq3QsA0NW6a6btFvN-JjB1Pn3ZCoCzBMmVUhhh1ZQMtRn9tYPQ5G-jHDLiEpVAlBuRCVMkLDxa-zijsqbIui-4A-ynwclDWGFT4bUHTc"; + String firebaseToken = await Utils.getStringFromPrefs(CacheConst.pushToken); + // String firebaseToken = "fY1fq_cITMmUCztA3UKKL9:APA91bEb2ZcdCPQPq3QsA0NW6a6btFvN-JjB1Pn3ZCoCzBMmVUhhh1ZQMtRn9tYPQ5G-jHDLiEpVAlBuRCVMkLDxa-zijsqbIui-4A-ynwclDWGFT4bUHTc"; // == "" // ? "dOGRRszQQMGe_9wA5Hx3kO:APA91bFV5IcIJXvcCXXk0tc2ddtZgWwCPq7sGSuPr-YW7iiJpQZKgFGN9GAzCVOWL8MfheaP1slE8MdxB7lczdPBGdONQ7WbMmhgHcsUCUktq-hsapGXXqc" // : _appState.deviceToken; @@ -242,9 +252,11 @@ class AuthenticationViewModel extends ChangeNotifier { Future checkUserAuthentication({required OTPTypeEnum otpTypeEnum, Function(dynamic)? onSuccess, Function(String)? onError}) async { // TODO: THIS SHOULD BE REMOVED LATER ON AND PASSED FROM APP STATE DIRECTLY INTO API CLIENT. BECAUSE THIS API ONLY NEEDS FEW PARAMS FROM USER - // if (phoneNumberController.text.isEmpty) { - // phoneNumberController.text = "504278212"; - // } + loginTypeEnum = otpTypeEnum == OTPTypeEnum.sms ? LoginTypeEnum.sms : LoginTypeEnum.whatsapp; + + if (phoneNumberController.text.isEmpty) { + phoneNumberController.text = "504278212"; + } bool isValidated = ValidationUtils.isValidatePhoneAndId( phoneNumber: phoneNumberController.text, nationalId: nationalIdController.text, @@ -286,7 +298,7 @@ class AuthenticationViewModel extends ChangeNotifier { await checkActivationCode( otpTypeEnum: otpTypeEnum, onWrongActivationCode: (String? message) {}, - activationCode: 0000, + activationCode: null, //todo silent login case halded on the repo itself.. ); } } @@ -296,9 +308,9 @@ class AuthenticationViewModel extends ChangeNotifier { } Future sendActivationCode({required OTPTypeEnum otpTypeEnum, required String nationalIdOrFileNumber, required String phoneNumber, dynamic payload}) async { - bool isForRegister = await checkIsUserComingForRegister(request: payload); - bool isPatientOutSA = await isPatientOutsideSA(request: payload); - bool isFileNo = await isPatientHasFile(request: payload); + bool isForRegister = checkIsUserComingForRegister(request: payload); + bool isPatientOutSA = isPatientOutsideSA(request: payload); + bool isFileNo = isPatientHasFile(request: payload); var request = RequestUtils.getCommonRequestSendActivationCode( otpTypeEnum: otpTypeEnum, @@ -348,43 +360,45 @@ class AuthenticationViewModel extends ChangeNotifier { ); } - Future checkIsUserComingForRegister({required dynamic request}) async { + bool checkIsUserComingForRegister({required dynamic request}) { bool isUserComingForRegister = false; - print(request); - if (request != null && request['isRegister']) { + if (request != null && request['isRegister'] == true) { isUserComingForRegister = true; } return isUserComingForRegister; } Future checkActivationCode({ - required int activationCode, + required String? activationCode, required OTPTypeEnum otpTypeEnum, required Function(String? message) onWrongActivationCode, }) async { final request = RequestUtils.getCommonRequestWelcome( - phoneNumber: phoneNumberController.text, - otpTypeEnum: otpTypeEnum, - deviceToken: _appState.deviceToken, - patientOutSA: true, - loginTokenID: _appState.appAuthToken, - registeredData: null, - nationIdText: nationalIdController.text, - countryCode: selectedCountrySignup.countryCode, - ).toJson(); - - bool isForRegister = healthId != null || isDubai; + phoneNumber: phoneNumberController.text, + otpTypeEnum: otpTypeEnum, + deviceToken: _appState.deviceToken, + patientOutSA: true, + loginTokenID: _appState.appAuthToken, + registeredData: null, + nationIdText: nationalIdController.text, + countryCode: selectedCountrySignup.countryCode, + loginType: loginTypeEnum.toInt) + .toJson(); + + bool isForRegister = (_appState.getUserRegistrationPayload.healthId != null || _appState.getUserRegistrationPayload.patientOutSa == true); if (isForRegister) { - if (isDubai) request['DOB'] = dob; - request['HealthId'] = healthId; - request['IsHijri'] = calenderType.toInt; + if (_appState.getUserRegistrationPayload.patientOutSa == true) request['DOB'] = _appState.getUserRegistrationPayload.dob; + request['HealthId'] = _appState.getUserRegistrationPayload.healthId; + request['IsHijri'] = _appState.getUserRegistrationPayload.isHijri; final resultEither = await _authenticationRepo.checkActivationCodeRepo(newRequest: request, activationCode: activationCode.toString(), isRegister: true); resultEither.fold((failure) async => await _errorHandlerService.handleError(failure: failure), (apiResponse) { final activation = CheckActivationCode.fromJson(apiResponse.data as Map); - if (registerd_data?.isRegister == true) { - _navigationService.popUntilNamed(AppRoutes.registerNewScreen); + if (_appState.getUserRegistrationPayload.isRegister == true) { + //TODO: KSA Version Came Hre + loadCountriesData(); + _navigationService.pushAndReplace(AppRoutes.registerStepTwo); // Navigator.popUntil(context, (route) => Utils.route(route, equalsTo: RegisterNew)); return; } @@ -392,7 +406,7 @@ class AuthenticationViewModel extends ChangeNotifier { } else { final resultEither = await _authenticationRepo.checkActivationCodeRepo( newRequest: CheckActivationCodeRegisterReq.fromJson(request), - activationCode: activationCode.toString(), + activationCode: activationCode, isRegister: false, ); @@ -406,8 +420,8 @@ class AuthenticationViewModel extends ChangeNotifier { } else if (activation.messageStatus == 2) { onWrongActivationCode(activation.errorEndUserMessage); return; - } else if (registerd_data?.isRegister == true) { - _navigationService.popUntilNamed(AppRoutes.registerNewScreen); + } else if (_appState.getUserRegistrationPayload.isRegister == true) { + _navigationService.pushAndReplace(AppRoutes.registerStepTwo); // Navigator.popUntil(context, (route) => Utils.route(route, equalsTo: RegisterNew)); return; } else { @@ -418,6 +432,7 @@ class AuthenticationViewModel extends ChangeNotifier { _appState.setAppAuthToken = activation.authenticationTokenId; final request = RequestUtils.getAuthanticatedCommonRequest().toJson(); bool isUserAgreedBefore = await checkIfUserAgreedBefore(request: request); + insertPatientIMEIData(loginTypeEnum.toInt); clearDefaultInputValues(); if (isUserAgreedBefore) { navigateToHomeScreen(); @@ -487,7 +502,7 @@ class AuthenticationViewModel extends ChangeNotifier { phoneNumber: phoneNumber, checkActivationCode: (int activationCode) async { await checkActivationCode( - activationCode: activationCode, + activationCode: activationCode.toString(), otpTypeEnum: otpTypeEnum, onWrongActivationCode: (String? value) { onWrongActivationCode(message: value); @@ -503,12 +518,20 @@ class AuthenticationViewModel extends ChangeNotifier { await _dialogService.showErrorBottomSheet(message: message ?? "Something went wrong. ", onOkPressed: () {}); } - loginWithFingerPrintFace(int selectedOption) async { - _localAuthService.authenticate().then((value) { + loginWithFingerPrintFace() async { + _localAuthService.authenticate().then((value) async { if (value) { // we have to handle this if verification true; - checkActivationCode(otpTypeEnum: OTPTypeEnum.faceIDFingerprint, activationCode: 0000, onWrongActivationCode: (String? message) {}); - // authenticated = true; + if (!_appState.isAuthenticated) { + loginTypeEnum = (_appState.deviceTypeID == 1 ? LoginTypeEnum.face : LoginTypeEnum.fingerprint); + print(loginTypeEnum); + checkActivationCode(otpTypeEnum: OTPTypeEnum.faceIDFingerprint, activationCode: null, onWrongActivationCode: (String? message) {}); + insertPatientIMEIData((_appState.deviceTypeID == 1 ? LoginTypeEnum.face.toInt : LoginTypeEnum.fingerprint.toInt)); + } else { + // authenticated = true; + insertPatientIMEIData((_appState.deviceTypeID == 1 ? LoginTypeEnum.face.toInt : LoginTypeEnum.fingerprint.toInt)); + } + notifyListeners(); // navigateToHomeScreen(); } else { @@ -516,20 +539,27 @@ class AuthenticationViewModel extends ChangeNotifier { notifyListeners(); } }); - this.selectedOption = selectedOption; - notifyListeners(); } checkLastLoginStatus(Function() onSuccess) async { - if (_appState.getSelectDeviceByImeiRespModelElement != null && - (_appState.getSelectDeviceByImeiRespModelElement!.logInType == 1 || _appState.getSelectDeviceByImeiRespModelElement!.logInType == 4)) { - Future.delayed(Duration(seconds: 1), () { + Future.delayed(Duration(seconds: 1), () { + if (_appState.getSelectDeviceByImeiRespModelElement != null && + (_appState.getSelectDeviceByImeiRespModelElement!.logInType == 1 || _appState.getSelectDeviceByImeiRespModelElement!.logInType == 4)) { + phoneNumberController.text = + (_appState.getAuthenticatedUser()!.mobileNumber!.startsWith("0") ? _appState.getAuthenticatedUser()!.mobileNumber!.replaceFirst("0", "") : _appState.getAuthenticatedUser()!.mobileNumber)!; + nationalIdController.text = _appState.getAuthenticatedUser()!.nationalityId!; onSuccess(); - }); - } + } else if ((loginTypeEnum == LoginTypeEnum.sms || loginTypeEnum == LoginTypeEnum.whatsapp && _appState.getSelectDeviceByImeiRespModelElement == null) && + _appState.getAuthenticatedUser() != null) { + phoneNumberController.text = + (_appState.getAuthenticatedUser()!.mobileNumber!.startsWith("0") ? _appState.getAuthenticatedUser()!.mobileNumber!.replaceFirst("0", "") : _appState.getAuthenticatedUser()!.mobileNumber)!; + nationalIdController.text = _appState.getAuthenticatedUser()!.nationalityId!; + onSuccess(); + } + }); } - Future onRegisterPress({required OTPTypeEnum otpTypeEnum}) async { + Future onRegistrationStart({required OTPTypeEnum otpTypeEnum}) async { bool isOutSidePatient = selectedCountrySignup.countryCode == CountryEnum.unitedArabEmirates.countryCode ? true : false; final request = await RequestUtils.getPatientAuthenticationRequest( @@ -560,6 +590,17 @@ class AuthenticationViewModel extends ChangeNotifier { }); } + Future onRegistrationComplete() async { + // if (emailAddress.text.isEmpty) { + // Utils.showErrorToast(TranslationBase.of(context).enterEmailAddress); + // return; + // } else { + // Navigator.of(context).pop(); + // registerNow(); + // } + //authVM!.clearDefaultInputValues(); + } + Future checkUserStatusForRegistration({required dynamic response, required dynamic request}) async { if (response is Map) { _appState.setAppAuthToken = response["LogInTokenID"]; @@ -572,16 +613,16 @@ class AuthenticationViewModel extends ChangeNotifier { } else { request['forRegister'] = true; request['isRegister'] = true; - if (await isPatientOutsideSA(request: response)) { - print("=======IN SA======="); - chekUserNHICData(request: request); - } else { + if (isPatientOutsideSA(request: response)) { print("=======OUT SA======="); _appState.setAppAuthToken = response['LogInTokenID']; sendActivationCode( otpTypeEnum: OTPTypeEnumExtension.fromInt(request["OTP_SendType"]), nationalIdOrFileNumber: request["PatientIdentificationID"].toString(), phoneNumber: request["PatientMobileNumber"].toString()); + } else { + print("=======IN SA======="); + chekUserNHICData(request: request); } } } else { @@ -590,23 +631,20 @@ class AuthenticationViewModel extends ChangeNotifier { } } - Future isPatientOutsideSA({required dynamic request}) { - try { - if (request is Map && request.containsKey("PatientOutSA")) { - if (!request["PatientOutSA"]) { - return Future.value(true); - } else { - return Future.value(false); - } + bool isPatientOutsideSA({required dynamic request}) { + bool isOutSideSa = false; + if (request is Map && request.containsKey("PatientOutSA")) { + if (request["PatientOutSA"] == true) { + isOutSideSa = true; } else { - return Future.value(false); + isOutSideSa = false; } - } catch (e) { - return Future.value(false); } + print(isOutSideSa); + return isOutSideSa; } - Future isPatientHasFile({required dynamic request}) async { + bool isPatientHasFile({required dynamic request}) { bool isFile = false; if (request != null && request["NationalID"] != null) { isFile = request["NationalID"].length < 10; @@ -618,13 +656,12 @@ class AuthenticationViewModel extends ChangeNotifier { final resultEither = await _authenticationRepo.checkUserStatus(commonAuthanticatedRequest: request); resultEither.fold((failure) async => await _errorHandlerService.handleError(failure: failure), (apiResponse) async { if (apiResponse.data is Map) { - _appState.setNHICUserData = CheckUserStatusResponseNHIC.fromJson(apiResponse.data as Map); + setNHICData(apiResponse.data, request); sendActivationCode( - otpTypeEnum: OTPTypeEnumExtension.fromInt(request["OTP_SendType"]), - nationalIdOrFileNumber: request["PatientIdentificationID"].toString(), - phoneNumber: request["PatientMobileNumber"].toString(), - payload: request, - ); + otpTypeEnum: OTPTypeEnumExtension.fromInt(request["OTP_SendType"]), + nationalIdOrFileNumber: request["PatientIdentificationID"].toString(), + phoneNumber: request["PatientMobileNumber"].toString(), + payload: request); } }); @@ -664,6 +701,67 @@ class AuthenticationViewModel extends ChangeNotifier { // }); } + void setNHICData(dynamic data, dynamic request) { + _appState.setNHICUserData = CheckUserStatusResponseNHIC.fromJson(data as Map); + request["healthId"] = _appState.getNHICUserData.healthId; + _appState.setUserRegistrationPayload = RegistrationDataModelPayload.fromJson(request); + } + + Future insertPatientIMEIData(int loginType) async { + final resultEither = await _authenticationRepo.insertPatientIMEIData( + patientIMEIDataRequest: PatientInsertDeviceImei( + imei: _appState.deviceToken, + deviceTypeId: _appState.getDeviceTypeID(), + patientId: _appState.getAuthenticatedUser()!.patientId!, + patientIdentificationNo: _appState.getAuthenticatedUser()!.patientIdentificationNo!, + firstName: _appState.getAuthenticatedUser()!.firstName!, + lastName: _appState.getAuthenticatedUser()!.lastName!, + patientTypeId: _appState.getAuthenticatedUser()!.patientType, + mobileNo: _appState.getAuthenticatedUser()!.mobileNumber!, + logInTypeId: loginType, + patientOutSa: _appState.getAuthenticatedUser()!.outSa!, + outSa: _appState.getAuthenticatedUser()!.outSa == 1 ? true : false, + biometricEnabled: loginType == 1 || loginType == 2 ? false : true, + firstNameN: _appState.getAuthenticatedUser()!.firstNameN, + lastNameN: _appState.getAuthenticatedUser()!.lastNameN, + ).toJson()); + resultEither.fold((failure) async => await _errorHandlerService.handleError(failure: failure), (apiResponse) async { + if (apiResponse.messageStatus == 1) { + log("Insert IMEI Success"); + insertPatientDeviceData(loginType); + } else { + log("Insert IMEI Failed"); + } + }); + } + + Future insertPatientDeviceData(int loginType) async { + final resultEither = await _authenticationRepo.insertPatientDeviceData( + patientDeviceDataRequest: InsertPatientMobileDeviceInfo( + deviceToken: _appState.deviceToken, + deviceTypeId: _appState.getDeviceTypeID(), + patientId: _appState.getAuthenticatedUser()!.patientId!, + patientTypeId: _appState.getAuthenticatedUser()!.patientType, + patientOutSa: _appState.getAuthenticatedUser()!.outSa!, + loginType: loginType, + languageId: _appState.getLanguageID(), + latitude: _appState.userLat, + longitude: _appState.userLong, + voipToken: "", + deviceType: _appState.deviceTypeID, + patientMobileNumber: _appState.getAuthenticatedUser()!.mobileNumber, + nationalId: _appState.getAuthenticatedUser()!.patientIdentificationNo, + gender: _appState.getAuthenticatedUser()!.gender) + .toJson()); + resultEither.fold((failure) async => await _errorHandlerService.handleError(failure: failure), (apiResponse) async { + if (apiResponse.messageStatus == 1) { + log("Insert Device Data Success"); + } else { + log("Insert IMEI Failed"); + } + }); + } + @override void dispose() { nationalIdController.dispose(); @@ -671,4 +769,41 @@ class AuthenticationViewModel extends ChangeNotifier { myFocusNode.dispose(); super.dispose(); } + + Future getServicePrivilege() async { + final resultEither = await _authenticationRepo.getServicePrivilege(); + List privilegeModelList = []; + List vidaPlusProjectListModel = []; + List hMCProjectListModel = []; + List projectDetailListModel = []; + + resultEither.fold( + (failure) async => await _errorHandlerService.handleError(failure: failure), + (apiResponse) async { + if (apiResponse.messageStatus == 2) { + await _dialogService.showErrorBottomSheet(message: apiResponse.errorMessage ?? "ErrorEmpty"); + } else { + apiResponse.data["ServicePrivilegeList"].forEach((v) { + privilegeModelList.add(PrivilegeModel.fromJson(v)); + }); + _appState.setPrivilegeModelList(privilegeModelList); + + apiResponse.data["ProjectListVidaPlus"].forEach((v) { + vidaPlusProjectListModel.add(VidaPlusProjectListModel.fromJson(v)); + }); + _appState.setVidaPlusProjectList(vidaPlusProjectListModel); + + apiResponse.data["HMCProjectList"].forEach((v) { + hMCProjectListModel.add(HMCProjectListModel.fromJson(v)); + }); + _appState.setHMCProjectList(hMCProjectListModel); + + apiResponse.data["ProjectDetailList"].forEach((v) { + projectDetailListModel.add(ProjectDetailListModel.fromJson(v)); + }); + _appState.setProjectsDetailList(projectDetailListModel); + } + }, + ); + } } diff --git a/lib/features/authentication/models/request_models/insert_patient_mobile_deviceinfo.dart b/lib/features/authentication/models/request_models/insert_patient_mobile_deviceinfo.dart new file mode 100644 index 0000000..dbfb0e1 --- /dev/null +++ b/lib/features/authentication/models/request_models/insert_patient_mobile_deviceinfo.dart @@ -0,0 +1,109 @@ +import 'dart:convert'; + +class InsertPatientMobileDeviceInfo { + double? versionId; + int? channel; + int? languageId; + String? ipAdress; + String? generalid; + int? patientOutSa; + bool? isDentalAllowedBackend; + int? deviceTypeId; + int? patientId; + String? tokenId; + String? voipToken; + int? patientTypeId; + int? patientType; + String? deviceToken; + String? deviceType; + String? patientMobileNumber; + String? nationalId; + int? gender; + int? loginType; + String? macAddress; + double? latitude; + double? longitude; + String? sessionId; + + InsertPatientMobileDeviceInfo({ + this.versionId, + this.channel, + this.languageId, + this.ipAdress, + this.generalid, + this.patientOutSa, + this.isDentalAllowedBackend, + this.deviceTypeId, + this.patientId, + this.tokenId, + this.voipToken, + this.patientTypeId, + this.patientType, + this.deviceToken, + this.deviceType, + this.patientMobileNumber, + this.nationalId, + this.gender, + this.loginType, + this.macAddress, + this.latitude, + this.longitude, + this.sessionId, + }); + + factory InsertPatientMobileDeviceInfo.fromRawJson(String str) => InsertPatientMobileDeviceInfo.fromJson(json.decode(str)); + + String toRawJson() => json.encode(toJson()); + + factory InsertPatientMobileDeviceInfo.fromJson(Map json) => InsertPatientMobileDeviceInfo( + versionId: json["VersionID"]?.toDouble(), + channel: json["Channel"], + languageId: json["LanguageID"], + ipAdress: json["IPAdress"], + generalid: json["generalid"], + patientOutSa: json["PatientOutSA"], + isDentalAllowedBackend: json["isDentalAllowedBackend"], + deviceTypeId: json["DeviceTypeID"], + patientId: json["PatientID"], + tokenId: json["TokenID"], + voipToken: json["VoipToken"], + patientTypeId: json["PatientTypeID"], + patientType: json["PatientType"], + deviceToken: json["DeviceToken"], + deviceType: json["DeviceType"], + patientMobileNumber: json["PatientMobileNumber"], + nationalId: json["NationalID"], + gender: json["Gender"], + loginType: json["LoginType"], + macAddress: json["MACAddress"], + latitude: json["Latitude"], + longitude: json["Longitude"], + sessionId: json[" SessionID"], + ); + + Map toJson() => { + "VersionID": versionId, + "Channel": channel, + "LanguageID": languageId, + "IPAdress": ipAdress, + "generalid": generalid, + "PatientOutSA": patientOutSa, + "isDentalAllowedBackend": isDentalAllowedBackend, + "DeviceTypeID": deviceTypeId, + "PatientID": patientId, + "TokenID": tokenId, + "VoipToken": voipToken, + "PatientTypeID": patientTypeId, + "PatientType": patientType, + "DeviceToken": deviceToken, + "DeviceType": deviceType, + "PatientMobileNumber": patientMobileNumber, + "NationalID": nationalId, + "Gender": gender, + "LoginType": loginType, + "MACAddress": macAddress, + "Latitude": latitude, + "Longitude": longitude, + " SessionID": sessionId, + }; +} diff --git a/lib/features/authentication/models/request_models/patient_insert_device_imei_request.dart b/lib/features/authentication/models/request_models/patient_insert_device_imei_request.dart new file mode 100644 index 0000000..0b2a99f --- /dev/null +++ b/lib/features/authentication/models/request_models/patient_insert_device_imei_request.dart @@ -0,0 +1,126 @@ +import 'dart:convert'; + +class PatientInsertDeviceImei { + String? setupId; + int? patientType; + int? patientId; + String? firstName; + String? firstNameN; + String? lastNameN; + int? preferredLanguage; + String? patientIdentificationNo; + bool? outSa; + String? identificationNo; + String? mobileNo; + String? tokenId; + String? imei; + bool? biometricEnabled; + int? logInTypeId; + double? versionId; + int? channel; + int? languageId; + String? ipAdress; + String? generalid; + int? latitude; + int? longitude; + int? deviceTypeId; + int? patientTypeId; + int? patientOutSa; + String? sessionId; + String? lastName; + + PatientInsertDeviceImei({ + this.setupId, + this.patientType, + this.patientId, + this.firstName, + this.firstNameN, + this.lastNameN, + this.preferredLanguage, + this.patientIdentificationNo, + this.outSa, + this.identificationNo, + this.mobileNo, + this.tokenId, + this.imei, + this.biometricEnabled, + this.logInTypeId, + this.versionId, + this.channel, + this.languageId, + this.ipAdress, + this.generalid, + this.latitude, + this.longitude, + this.deviceTypeId, + this.patientTypeId, + this.patientOutSa, + this.sessionId, + this.lastName + }); + + factory PatientInsertDeviceImei.fromRawJson(String str) => PatientInsertDeviceImei.fromJson(json.decode(str)); + + String toRawJson() => json.encode(toJson()); + + factory PatientInsertDeviceImei.fromJson(Map json) => PatientInsertDeviceImei( + setupId: json["SetupID"], + patientType: json["PatientType"], + patientId: json["PatientID"], + firstName: json["FirstName"], + firstNameN: json["FirstNameN"], + lastNameN: json["LastNameN"], + preferredLanguage: json["PreferredLanguage"], + patientIdentificationNo: json["PatientIdentificationNo"], + outSa: json["OutSA"], + identificationNo: json["IdentificationNo"], + mobileNo: json["MobileNo"], + tokenId: json["TokenID"], + imei: json["IMEI"], + biometricEnabled: json["BiometricEnabled"], + logInTypeId: json["LogInTypeID"], + versionId: json["VersionID"]?.toDouble(), + channel: json["Channel"], + languageId: json["LanguageID"], + ipAdress: json["IPAdress"], + generalid: json["generalid"], + latitude: json["Latitude"], + longitude: json["Longitude"], + deviceTypeId: json["DeviceTypeID"], + patientTypeId: json["PatientTypeID"], + patientOutSa: json["PatientOutSA"], + sessionId: json["SessionID"], + lastName: json["LastName"], + + ); + + Map toJson() => { + "SetupID": setupId, + "PatientType": patientType, + "PatientID": patientId, + "FirstName": firstName, + "FirstNameN": firstNameN, + "LastNameN": lastNameN, + "PreferredLanguage": preferredLanguage, + "PatientIdentificationNo": patientIdentificationNo, + "OutSA": outSa, + "IdentificationNo": identificationNo, + "MobileNo": mobileNo, + "TokenID": tokenId, + "IMEI": imei, + "BiometricEnabled": biometricEnabled, + "LogInTypeID": logInTypeId, + "VersionID": versionId, + "Channel": channel, + "LanguageID": languageId, + "IPAdress": ipAdress, + "generalid": generalid, + "Latitude": latitude, + "Longitude": longitude, + "DeviceTypeID": deviceTypeId, + "PatientTypeID": patientTypeId, + "PatientOutSA": patientOutSa, + "SessionID": sessionId, + "LastName": lastName, + }; +} diff --git a/lib/features/authentication/models/request_models/registration_payload_model.dart b/lib/features/authentication/models/request_models/registration_payload_model.dart new file mode 100644 index 0000000..fa4f612 --- /dev/null +++ b/lib/features/authentication/models/request_models/registration_payload_model.dart @@ -0,0 +1,69 @@ +import 'dart:convert'; + +class RegistrationDataModelPayload { + int? patientMobileNumber; + String? zipCode; + int? searchType; + int? patientId; + int? patientIdentificationId; + int? otpSendType; + int? patientOutSa; + bool? isDentalAllowedBackend; + String? dob; + int? isHijri; + bool? forRegister; + bool? isRegister; + String? healthId; + + RegistrationDataModelPayload({ + this.patientMobileNumber, + this.zipCode, + this.searchType, + this.patientId, + this.patientIdentificationId, + this.otpSendType, + this.patientOutSa, + this.isDentalAllowedBackend, + this.dob, + this.isHijri, + this.forRegister, + this.isRegister, + this.healthId, + }); + + factory RegistrationDataModelPayload.fromRawJson(String str) => RegistrationDataModelPayload.fromJson(json.decode(str)); + + String toRawJson() => json.encode(toJson()); + + factory RegistrationDataModelPayload.fromJson(Map json) => RegistrationDataModelPayload( + patientMobileNumber: json["PatientMobileNumber"], + zipCode: json["ZipCode"], + searchType: json["SearchType"], + patientId: json["PatientID"], + patientIdentificationId: json["PatientIdentificationID"], + otpSendType: json["OTP_SendType"], + patientOutSa: json["PatientOutSA"], + isDentalAllowedBackend: json["isDentalAllowedBackend"], + dob: json["DOB"], + isHijri: json["IsHijri"], + forRegister: json["forRegister"], + isRegister: json["isRegister"], + healthId: json["healthId"], + ); + + Map toJson() => { + "PatientMobileNumber": patientMobileNumber, + "ZipCode": zipCode, + "SearchType": searchType, + "PatientID": patientId, + "PatientIdentificationID": patientIdentificationId, + "OTP_SendType": otpSendType, + "PatientOutSA": patientOutSa, + "isDentalAllowedBackend": isDentalAllowedBackend, + "DOB": dob, + "IsHijri": isHijri, + "forRegister": forRegister, + "isRegister": isRegister, + "healthId": healthId, + }; +} diff --git a/lib/features/insurance/insurance_repo.dart b/lib/features/insurance/insurance_repo.dart index 68b53e4..874cc28 100644 --- a/lib/features/insurance/insurance_repo.dart +++ b/lib/features/insurance/insurance_repo.dart @@ -106,7 +106,7 @@ class InsuranceRepoImp implements InsuranceRepo { @override Future>> getPatientInsuranceDetailsForUpdate({required String patientId, required String identificationNo}) async { - final mapDevice = {"SetupID": "010266", "ProjectID": 15, "PatientIdentificationID": identificationNo, "IsFamily": false, "ParentID": 0}; + Map mapDevice = {"SetupID": "010266", "ProjectID": 15, "PatientIdentificationID": identificationNo, "IsFamily": false, "ParentID": 0}; try { GenericApiModel? apiResponse; diff --git a/lib/features/my_appointments/my_appointments_repo.dart b/lib/features/my_appointments/my_appointments_repo.dart index 0815b94..804f9f9 100644 --- a/lib/features/my_appointments/my_appointments_repo.dart +++ b/lib/features/my_appointments/my_appointments_repo.dart @@ -8,8 +8,7 @@ import 'package:hmg_patient_app_new/features/my_appointments/models/resp_models/ import 'package:hmg_patient_app_new/services/logger_service.dart'; abstract class MyAppointmentsRepo { - Future>>> getPatientAppointments( - {required bool isActiveAppointment, required bool isArrivedAppointments}); + Future>>> getPatientAppointments({required bool isActiveAppointment, required bool isArrivedAppointments}); Future>> getPatientShareAppointment({required int projectID, required int clinicID, required String appointmentNo}); @@ -28,6 +27,11 @@ abstract class MyAppointmentsRepo { Future>> generateAppointmentQR({required int clinicID, required int projectID, required String appointmentNo, required int isFollowUp}); Future>> cancelAppointment({required PatientAppointmentHistoryResponseModel patientAppointmentHistoryResponseModel}); + + Future>> confirmAppointment({required PatientAppointmentHistoryResponseModel patientAppointmentHistoryResponseModel}); + + Future>> sendCheckInNfcRequest( + {required PatientAppointmentHistoryResponseModel patientAppointmentHistoryResponseModel, required String scannedCode, required int checkInType}); } class MyAppointmentsRepoImp implements MyAppointmentsRepo { @@ -37,8 +41,7 @@ class MyAppointmentsRepoImp implements MyAppointmentsRepo { MyAppointmentsRepoImp({required this.loggerService, required this.apiClient}); @override - Future>>> getPatientAppointments( - {required bool isActiveAppointment, required bool isArrivedAppointments}) async { + Future>>> getPatientAppointments({required bool isActiveAppointment, required bool isArrivedAppointments}) async { Map mapDevice = { "IsActiveAppointment": isActiveAppointment, "isDentalAllowedBackend": false, @@ -310,4 +313,86 @@ class MyAppointmentsRepoImp implements MyAppointmentsRepo { return Left(UnknownFailure(e.toString())); } } + + @override + Future> confirmAppointment({required PatientAppointmentHistoryResponseModel patientAppointmentHistoryResponseModel}) async { + Map requestBody = { + "AppointmentNumber": patientAppointmentHistoryResponseModel.appointmentNo, + "IsLiveCareAppointment": patientAppointmentHistoryResponseModel.isLiveCareAppointment, + "ClinicID": patientAppointmentHistoryResponseModel.clinicID, + "ProjectID": patientAppointmentHistoryResponseModel.projectID, + "ConfirmationBy": 102, + }; + + try { + GenericApiModel? apiResponse; + Failure? failure; + await apiClient.post( + CONFIRM_APPOINTMENT, + body: requestBody, + onFailure: (error, statusCode, {messageStatus, failureType}) { + failure = failureType; + }, + onSuccess: (response, statusCode, {messageStatus, errorMessage}) { + try { + apiResponse = GenericApiModel( + messageStatus: messageStatus, + statusCode: statusCode, + errorMessage: null, + data: response, + ); + } catch (e) { + failure = DataParsingFailure(e.toString()); + } + }, + ); + if (failure != null) return Left(failure!); + if (apiResponse == null) return Left(ServerFailure("Unknown error")); + return Right(apiResponse!); + } catch (e) { + return Left(UnknownFailure(e.toString())); + } + } + + @override + Future> sendCheckInNfcRequest( + {required PatientAppointmentHistoryResponseModel patientAppointmentHistoryResponseModel, required String scannedCode, required int checkInType}) async { + Map requestBody = { + "AppointmentNo": patientAppointmentHistoryResponseModel.appointmentNo, + "NFC_Code": scannedCode, + "ProjectID": patientAppointmentHistoryResponseModel.projectID, + "ClinicID": patientAppointmentHistoryResponseModel.clinicID, + "CheckinBy": checkInType, + }; + + try { + GenericApiModel? apiResponse; + Failure? failure; + await apiClient.post( + SEND_CHECK_IN_NFC_REQUEST, + body: requestBody, + 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()); + } + }, + isAllowAny: true, + ); + if (failure != null) return Left(failure!); + if (apiResponse == null) return Left(ServerFailure("Unknown error")); + return Right(apiResponse!); + } catch (e) { + return Left(UnknownFailure(e.toString())); + } + } } diff --git a/lib/features/my_appointments/my_appointments_view_model.dart b/lib/features/my_appointments/my_appointments_view_model.dart index 5c952e9..4a921da 100644 --- a/lib/features/my_appointments/my_appointments_view_model.dart +++ b/lib/features/my_appointments/my_appointments_view_model.dart @@ -14,6 +14,10 @@ class MyAppointmentsViewModel extends ChangeNotifier { bool isAppointmentPatientShareLoading = false; List patientAppointmentsHistoryList = []; + + List patientUpcomingAppointmentsHistoryList = []; + List patientArrivedAppointmentsHistoryList = []; + PatientAppointmentShareResponseModel? patientAppointmentShareResponseModel; MyAppointmentsViewModel({required this.myAppointmentsRepo, required this.errorHandlerService}); @@ -25,6 +29,8 @@ class MyAppointmentsViewModel extends ChangeNotifier { initAppointmentsViewModel() { patientAppointmentsHistoryList.clear(); + patientUpcomingAppointmentsHistoryList.clear(); + patientArrivedAppointmentsHistoryList.clear(); isMyAppointmentsLoading = true; isAppointmentPatientShareLoading = true; notifyListeners(); @@ -50,6 +56,7 @@ class MyAppointmentsViewModel extends ChangeNotifier { Future getPatientAppointments(bool isActiveAppointment, bool isArrivedAppointments, {Function(dynamic)? onSuccess, Function(String)? onError}) async { final result = await myAppointmentsRepo.getPatientAppointments(isActiveAppointment: isActiveAppointment, isArrivedAppointments: isArrivedAppointments); + final resultArrived = await myAppointmentsRepo.getPatientAppointments(isActiveAppointment: false, isArrivedAppointments: true); result.fold( (failure) async => await errorHandlerService.handleError(failure: failure), @@ -57,7 +64,7 @@ class MyAppointmentsViewModel extends ChangeNotifier { if (apiResponse.messageStatus == 2) { // dialogService.showErrorDialog(message: apiResponse.errorMessage!, onOkPressed: () {}); } else if (apiResponse.messageStatus == 1) { - patientAppointmentsHistoryList = apiResponse.data!; + patientUpcomingAppointmentsHistoryList = apiResponse.data!; isMyAppointmentsLoading = false; notifyListeners(); if (onSuccess != null) { @@ -66,6 +73,29 @@ class MyAppointmentsViewModel extends ChangeNotifier { } }, ); + + resultArrived.fold( + (failure) async => await errorHandlerService.handleError(failure: failure), + (apiResponse) { + if (apiResponse.messageStatus == 2) { + // dialogService.showErrorDialog(message: apiResponse.errorMessage!, onOkPressed: () {}); + } else if (apiResponse.messageStatus == 1) { + patientArrivedAppointmentsHistoryList = apiResponse.data!; + isMyAppointmentsLoading = false; + notifyListeners(); + if (onSuccess != null) { + onSuccess(apiResponse); + } + } + }, + ); + + patientAppointmentsHistoryList.addAll(patientUpcomingAppointmentsHistoryList); + patientAppointmentsHistoryList.addAll(patientArrivedAppointmentsHistoryList); + + print('Upcoming Appointments: ${patientUpcomingAppointmentsHistoryList.length}'); + print('Arrived Appointments: ${patientArrivedAppointmentsHistoryList.length}'); + print('All Appointments: ${patientAppointmentsHistoryList.length}'); } Future getPatientShareAppointment(int projectID, int clinicID, String appointmentNo, {Function(dynamic)? onSuccess, Function(String)? onError}) async { @@ -145,6 +175,25 @@ class MyAppointmentsViewModel extends ChangeNotifier { ); } + Future confirmAppointment({required PatientAppointmentHistoryResponseModel patientAppointmentHistoryResponseModel, Function(dynamic)? onSuccess, Function(String)? onError}) async { + final result = await myAppointmentsRepo.confirmAppointment(patientAppointmentHistoryResponseModel: patientAppointmentHistoryResponseModel); + + result.fold( + (failure) async => await errorHandlerService.handleError(failure: failure), + (apiResponse) { + if (apiResponse.messageStatus == 2) { + onError!(apiResponse.errorMessage!); + // dialogService.showErrorDialog(message: apiResponse.errorMessage!, onOkPressed: () {}); + } else if (apiResponse.messageStatus == 1) { + notifyListeners(); + if (onSuccess != null) { + onSuccess(apiResponse); + } + } + }, + ); + } + Future createAdvancePayment( {required String paymentMethodName, required int projectID, @@ -180,4 +229,28 @@ class MyAppointmentsViewModel extends ChangeNotifier { }, ); } + + Future sendCheckInNfcRequest( + {required PatientAppointmentHistoryResponseModel patientAppointmentHistoryResponseModel, + required String scannedCode, + required int checkInType, + Function(dynamic)? onSuccess, + Function(String)? onError}) async { + final result = await myAppointmentsRepo.sendCheckInNfcRequest(patientAppointmentHistoryResponseModel: patientAppointmentHistoryResponseModel, scannedCode: scannedCode, checkInType: checkInType); + + result.fold( + (failure) async => await errorHandlerService.handleError(failure: failure), + (apiResponse) { + if (apiResponse.messageStatus == 2) { + onError!(apiResponse.errorMessage!); + // dialogService.showErrorDialog(message: apiResponse.errorMessage!, onOkPressed: () {}); + } else if (apiResponse.messageStatus == 1) { + notifyListeners(); + if (onSuccess != null) { + onSuccess(apiResponse); + } + } + }, + ); + } } diff --git a/lib/features/my_appointments/utils/appointment_type.dart b/lib/features/my_appointments/utils/appointment_type.dart index cd8bb56..abc23dc 100644 --- a/lib/features/my_appointments/utils/appointment_type.dart +++ b/lib/features/my_appointments/utils/appointment_type.dart @@ -43,6 +43,25 @@ class AppointmentType { } } + static Color getNextActionTextColor(nextAction) { + switch (nextAction) { + case 0: + return AppColors.successColor; + case 10: + return AppColors.successColor; + case 15: + return AppColors.textColor; + case 20: + return AppColors.infoColor; + case 50: + return AppColors.successColor; + case 90: + return AppColors.alertColor; + default: + return AppColors.successColor; + } + } + static Color getNextActionButtonColor(nextAction) { switch (nextAction) { case 0: diff --git a/lib/features/prescriptions/prescriptions_repo.dart b/lib/features/prescriptions/prescriptions_repo.dart index f584f7b..5f2db30 100644 --- a/lib/features/prescriptions/prescriptions_repo.dart +++ b/lib/features/prescriptions/prescriptions_repo.dart @@ -21,23 +21,7 @@ class PrescriptionsRepoImp implements PrescriptionsRepo { @override Future>>> getPatientPrescriptionOrders({required String patientId}) async { - final mapDevice = { - "isDentalAllowedBackend": false, - "VersionID": 50.0, - "Channel": 3, - "LanguageID": 2, - "IPAdress": "10.20.10.20", - "generalid": "Cs2020@2016\$2958", - "Latitude": 0.0, - "Longitude": 0.0, - "DeviceTypeID": 1, - "PatientType": 1, - "PatientTypeID": 1, - "TokenID": "@dm!n", - "PatientID": "1018977", - "PatientOutSA": "0", - "SessionID": "03478TYC02N80874CTYN04883475!?" - }; + Map mapDevice = {}; try { GenericApiModel>? apiResponse; @@ -52,7 +36,7 @@ class PrescriptionsRepoImp implements PrescriptionsRepo { try { final list = response['PatientPrescriptionList']; if (list == null || list.isEmpty) { - throw Exception("lab list is empty"); + // throw Exception("lab list is empty"); } final prescriptionOrders = list.map((item) => PatientPrescriptionsResponseModel.fromJson(item as Map)).toList().cast(); @@ -78,28 +62,13 @@ class PrescriptionsRepoImp implements PrescriptionsRepo { @override Future>>> getPatientPrescriptionDetails({required PatientPrescriptionsResponseModel prescriptionsResponseModel}) async { - final mapDevice = { + Map mapDevice = { "AppointmentNo": prescriptionsResponseModel.appointmentNo.toString(), "SetupID": prescriptionsResponseModel.setupID, "EpisodeID": prescriptionsResponseModel.episodeID.toString(), "ClinicID": prescriptionsResponseModel.clinicID.toString(), "ProjectID": prescriptionsResponseModel.projectID.toString(), - "DischargeNo": prescriptionsResponseModel.dischargeNo.toString(), - "isDentalAllowedBackend": false, - "VersionID": 50.0, - "Channel": 3, - "LanguageID": 2, - "IPAdress": "10.20.10.20", - "generalid": "Cs2020@2016\$2958", - "Latitude": 0.0, - "Longitude": 0.0, - "DeviceTypeID": 1, - "PatientType": 1, - "PatientTypeID": 1, - "TokenID": "@dm!n", - "PatientID": "1018977", - "PatientOutSA": "0", - "SessionID": "03478TYC02N80874CTYN04883475!?" + "DischargeNo": prescriptionsResponseModel.dischargeNo.toString() }; try { @@ -115,7 +84,7 @@ class PrescriptionsRepoImp implements PrescriptionsRepo { try { final list = prescriptionsResponseModel.isInOutPatient! ? response['ListPRM'] : response['INP_GetPrescriptionReport_List']; if (list == null || list.isEmpty) { - throw Exception("prescription list is empty"); + // throw Exception("prescription list is empty"); } final prescriptionOrders = list.map((item) => PrescriptionDetailResponseModel.fromJson(item as Map)).toList().cast(); diff --git a/lib/features/prescriptions/prescriptions_view_model.dart b/lib/features/prescriptions/prescriptions_view_model.dart index 306551d..535d78f 100644 --- a/lib/features/prescriptions/prescriptions_view_model.dart +++ b/lib/features/prescriptions/prescriptions_view_model.dart @@ -31,7 +31,6 @@ class PrescriptionsViewModel extends ChangeNotifier { patientPrescriptionOrdersByHospital.clear(); patientPrescriptionOrdersViewList.clear(); isPrescriptionsOrdersLoading = true; - isPrescriptionsDetailsLoading = true; isSortByClinic = true; getPatientPrescriptionOrders(); notifyListeners(); diff --git a/lib/presentation/appointments/appointment_details_page.dart b/lib/presentation/appointments/appointment_details_page.dart index 4255c71..7188620 100644 --- a/lib/presentation/appointments/appointment_details_page.dart +++ b/lib/presentation/appointments/appointment_details_page.dart @@ -1,6 +1,9 @@ +import 'dart:async'; + import 'package:easy_localization/easy_localization.dart'; import 'package:flutter/cupertino.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'; @@ -11,16 +14,24 @@ import 'package:hmg_patient_app_new/extensions/widget_extensions.dart'; import 'package:hmg_patient_app_new/features/my_appointments/models/resp_models/patient_appointment_history_response_model.dart'; import 'package:hmg_patient_app_new/features/my_appointments/my_appointments_view_model.dart'; import 'package:hmg_patient_app_new/features/my_appointments/utils/appointment_type.dart'; +import 'package:hmg_patient_app_new/features/prescriptions/models/resp_models/patient_prescriptions_response_model.dart'; +import 'package:hmg_patient_app_new/features/prescriptions/prescriptions_view_model.dart'; 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/prescriptions/prescription_detail_page.dart'; +import 'package:hmg_patient_app_new/presentation/prescriptions/prescriptions_list_page.dart'; import 'package:hmg_patient_app_new/theme/colors.dart'; import 'package:hmg_patient_app_new/widgets/buttons/custom_button.dart'; import 'package:hmg_patient_app_new/widgets/common_bottom_sheet.dart'; +import 'package:hmg_patient_app_new/widgets/shimmer/movies_shimmer_widget.dart'; import 'package:hmg_patient_app_new/widgets/transitions/fade_page.dart'; import 'package:maps_launcher/maps_launcher.dart'; import 'package:provider/provider.dart'; +import '../medical_file/widgets/medical_file_card.dart'; + class AppointmentDetailsPage extends StatefulWidget { AppointmentDetailsPage({super.key, required this.patientAppointmentHistoryResponseModel}); @@ -32,10 +43,23 @@ class AppointmentDetailsPage extends StatefulWidget { class _AppointmentDetailsPageState extends State { late MyAppointmentsViewModel myAppointmentsViewModel; + late PrescriptionsViewModel prescriptionsViewModel; + + @override + void initState() { + scheduleMicrotask(() { + if (AppointmentType.isArrived(widget.patientAppointmentHistoryResponseModel)) { + prescriptionsViewModel.setPrescriptionsDetailsLoading(); + prescriptionsViewModel.getPrescriptionDetails(getPrescriptionRequestModel()); + } + }); + super.initState(); + } @override Widget build(BuildContext context) { myAppointmentsViewModel = Provider.of(context); + prescriptionsViewModel = Provider.of(context); return Scaffold( backgroundColor: AppColors.bgScaffoldColor, appBar: AppBar( @@ -52,7 +76,23 @@ class _AppointmentDetailsPageState extends State { Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ - "Appointment Details".needTranslation.toText24(isBold: true), + "Appointment Details".needTranslation.toText20(isBold: true), + if (AppointmentType.isArrived(widget.patientAppointmentHistoryResponseModel)) + CustomButton( + text: "Report".needTranslation, + onPressed: () {}, + 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, + iconSize: 16.h, + icon: AppAssets.report_icon, + iconColor: AppColors.primaryRedColor, + ) ], ), SizedBox(height: 24.h), @@ -74,8 +114,7 @@ class _AppointmentDetailsPageState extends State { Navigator.of(context).pop(); showCommonBottomSheet(context, child: Utils.getSuccessWidget(loadingText: "Appointment Cancelled Successfully".needTranslation), - callBackFunc: (str) { - }, + callBackFunc: (str) {}, title: "", height: ResponsiveExtension.screenHeight * 0.3, isCloseButtonVisible: false, @@ -89,106 +128,266 @@ class _AppointmentDetailsPageState extends State { onRescheduleTap: () {}, ), SizedBox(height: 16.h), - if (!AppointmentType.isArrived(widget.patientAppointmentHistoryResponseModel)) - Column( - children: [ - Container( - decoration: RoundedRectangleBorder().toSmoothCornerDecoration( - color: AppColors.whiteColor, - borderRadius: 20.h, - hasShadow: false, - ), - child: Padding( - padding: EdgeInsets.all(16.h), - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Row( - children: [ - "Appointment Status".needTranslation.toText16(isBold: true), - ], - ), - SizedBox(height: 4.h), - (!AppointmentType.isConfirmed(widget.patientAppointmentHistoryResponseModel) - ? "Not Confirmed".needTranslation.toText12(color: AppColors.primaryRedColor, fontWeight: FontWeight.w500) - : "Confirmed".needTranslation.toText12(color: AppColors.successColor, fontWeight: FontWeight.w500)), - SizedBox(height: 16.h), - Stack( + !AppointmentType.isArrived(widget.patientAppointmentHistoryResponseModel) + ? Column( + children: [ + Container( + decoration: RoundedRectangleBorder().toSmoothCornerDecoration( + color: AppColors.whiteColor, + borderRadius: 20.h, + hasShadow: false, + ), + child: Padding( + padding: EdgeInsets.all(16.h), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, children: [ - ClipRRect( - clipBehavior: Clip.hardEdge, - borderRadius: BorderRadius.circular(24), - child: Image.network( - "https://maps.googleapis.com/maps/api/staticmap?center=${widget.patientAppointmentHistoryResponseModel.latitude},${widget.patientAppointmentHistoryResponseModel.longitude}&zoom=14&size=350x165&maptype=roadmap&markers=color:red%7C${widget.patientAppointmentHistoryResponseModel.latitude},${widget.patientAppointmentHistoryResponseModel.longitude}&key=AIzaSyB6TERnxIr0yJ3qG4ULBZbu0sAD4tGqtng", - fit: BoxFit.contain, - ), + Row( + children: [ + "Appointment Status".needTranslation.toText16(isBold: true), + ], ), - Positioned( - bottom: 0, - child: SizedBox( - width: MediaQuery.of(context).size.width * 0.785, - child: CustomButton( - text: "Get Directions".needTranslation, - onPressed: () { - MapsLauncher.launchCoordinates(double.parse(widget.patientAppointmentHistoryResponseModel.latitude!), - double.parse(widget.patientAppointmentHistoryResponseModel.longitude!), widget.patientAppointmentHistoryResponseModel.projectName); - }, - backgroundColor: AppColors.textColor.withOpacity(0.8), - borderColor: AppointmentType.getNextActionButtonColor(widget.patientAppointmentHistoryResponseModel.nextAction).withOpacity(0.01), - textColor: AppColors.whiteColor, - fontSize: 14, - fontWeight: FontWeight.w500, - borderRadius: 12.h, - padding: EdgeInsets.fromLTRB(10, 0, 10, 0), - height: 40.h, - icon: AppAssets.directions_icon, - iconColor: AppColors.whiteColor, - iconSize: 13.h, - ).paddingAll(12.h), - ), + SizedBox(height: 4.h), + (!AppointmentType.isConfirmed(widget.patientAppointmentHistoryResponseModel) + ? "Not Confirmed".needTranslation.toText12(color: AppColors.primaryRedColor, fontWeight: FontWeight.w500) + : "Confirmed".needTranslation.toText12(color: AppColors.successColor, fontWeight: FontWeight.w500)), + SizedBox(height: 16.h), + Stack( + children: [ + ClipRRect( + clipBehavior: Clip.hardEdge, + borderRadius: BorderRadius.circular(24), + child: Image.network( + "https://maps.googleapis.com/maps/api/staticmap?center=${widget.patientAppointmentHistoryResponseModel.latitude},${widget.patientAppointmentHistoryResponseModel.longitude}&zoom=14&size=350x165&maptype=roadmap&markers=color:red%7C${widget.patientAppointmentHistoryResponseModel.latitude},${widget.patientAppointmentHistoryResponseModel.longitude}&key=AIzaSyB6TERnxIr0yJ3qG4ULBZbu0sAD4tGqtng", + fit: BoxFit.contain, + ), + ), + Positioned( + bottom: 0, + child: SizedBox( + width: MediaQuery.of(context).size.width * 0.785, + child: CustomButton( + text: "Get Directions".needTranslation, + onPressed: () { + MapsLauncher.launchCoordinates(double.parse(widget.patientAppointmentHistoryResponseModel.latitude!), + double.parse(widget.patientAppointmentHistoryResponseModel.longitude!), widget.patientAppointmentHistoryResponseModel.projectName); + }, + backgroundColor: AppColors.textColor.withOpacity(0.8), + borderColor: AppointmentType.getNextActionButtonColor(widget.patientAppointmentHistoryResponseModel.nextAction).withOpacity(0.01), + textColor: AppColors.whiteColor, + fontSize: 14, + fontWeight: FontWeight.w500, + borderRadius: 12.h, + padding: EdgeInsets.fromLTRB(10, 0, 10, 0), + height: 40.h, + icon: AppAssets.directions_icon, + iconColor: AppColors.whiteColor, + iconSize: 13.h, + ).paddingAll(12.h), + ), + ), + ], ), ], ), - ], + ), ), - ), - ), - SizedBox(height: 16.h), - Container( - decoration: RoundedRectangleBorder().toSmoothCornerDecoration( - color: AppColors.whiteColor, - borderRadius: 20.h, - hasShadow: false, - ), - child: Row( - mainAxisSize: MainAxisSize.max, - children: [ - Utils.buildSvgWithAssets(icon: AppAssets.prescription_reminder_icon, width: 35.h, height: 35.h), - SizedBox(width: 8.h), - Column( - crossAxisAlignment: CrossAxisAlignment.start, + SizedBox(height: 16.h), + Container( + decoration: RoundedRectangleBorder().toSmoothCornerDecoration( + color: AppColors.whiteColor, + borderRadius: 20.h, + hasShadow: false, + ), + child: Row( + mainAxisSize: MainAxisSize.max, children: [ - LocaleKeys.setReminder.tr(context: context).toText13(isBold: true), - "Notify me before the appointment".needTranslation.toText11(color: AppColors.textColorLight, weight: FontWeight.w500), + Utils.buildSvgWithAssets(icon: AppAssets.prescription_reminder_icon, width: 35.h, height: 35.h), + SizedBox(width: 8.h), + Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + LocaleKeys.setReminder.tr(context: context).toText13(isBold: true), + "Notify me before the appointment".needTranslation.toText11(color: AppColors.textColorLight, weight: FontWeight.w500), + ], + ), + const Spacer(), + Switch( + activeColor: AppColors.successColor, + activeTrackColor: AppColors.successColor.withValues(alpha: .15), + value: widget.patientAppointmentHistoryResponseModel.hasReminder!, + onChanged: (newValue) { + setState(() { + myAppointmentsViewModel.setAppointmentReminder(newValue, widget.patientAppointmentHistoryResponseModel); + }); + }, + ), ], - ), - const Spacer(), - Switch( - activeColor: AppColors.successColor, - activeTrackColor: AppColors.successColor.withValues(alpha: .15), - value: widget.patientAppointmentHistoryResponseModel.hasReminder!, - onChanged: (newValue) { - setState(() { - myAppointmentsViewModel.setAppointmentReminder(newValue, widget.patientAppointmentHistoryResponseModel); - }); - }, - ), - ], - ).paddingSymmetrical(16.h, 16.h), + ).paddingSymmetrical(16.h, 16.h), + ), + SizedBox(height: 16.h), + ], + ) + : Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + "Lab & Radiology".needTranslation.toText18(isBold: true), + SizedBox(height: 16.h), + GridView( + gridDelegate: SliverGridDelegateWithFixedCrossAxisCount(crossAxisCount: 2, crossAxisSpacing: 13.h, mainAxisSpacing: 13.h, childAspectRatio: 7 / 6), + physics: NeverScrollableScrollPhysics(), + shrinkWrap: true, + children: [ + MedicalFileCard( + label: LocaleKeys.labResults.tr(context: context), + textColor: AppColors.blackColor, + backgroundColor: AppColors.whiteColor, + svgIcon: AppAssets.lab_result_icon, + iconSize: 40, + isLargeText: true, + ), + MedicalFileCard( + label: LocaleKeys.radiology.tr(context: context), + textColor: AppColors.blackColor, + backgroundColor: AppColors.whiteColor, + svgIcon: AppAssets.radiology_icon, + iconSize: 40, + isLargeText: true, + ), + ], + ), + LocaleKeys.prescriptions.tr().toText18(isBold: true), + SizedBox(height: 16.h), + Consumer(builder: (context, prescriptionVM, child) { + return prescriptionVM.isPrescriptionsDetailsLoading + ? const MoviesShimmerWidget() + : Container( + decoration: RoundedRectangleBorder().toSmoothCornerDecoration( + color: Colors.white, + borderRadius: 20.h, + ), + child: Padding( + padding: EdgeInsets.all(16.h), + child: Column( + children: [ + ListView.separated( + itemCount: prescriptionVM.prescriptionDetailsList.length, + shrinkWrap: true, + padding: const EdgeInsets.only(left: 0, right: 8), + physics: NeverScrollableScrollPhysics(), + itemBuilder: (context, index) { + return AnimationConfiguration.staggeredList( + position: index, + duration: const Duration(milliseconds: 500), + child: SlideAnimation( + verticalOffset: 100.0, + child: FadeInAnimation( + child: Row( + children: [ + Utils.buildSvgWithAssets( + icon: AppAssets.prescription_item_icon, + width: 40.h, + height: 40.h, + ), + SizedBox(width: 8.h), + Row( + mainAxisSize: MainAxisSize.max, + children: [ + Column( + children: [ + SizedBox(width: 150.h, child: prescriptionVM.prescriptionDetailsList[index].itemDescription!.toText12(isBold: true, maxLine: 1)), + SizedBox( + width: 150.h, + child: + "Prescribed By: ${widget.patientAppointmentHistoryResponseModel.doctorTitle} ${widget.patientAppointmentHistoryResponseModel.doctorNameObj}" + .needTranslation + .toText10(weight: FontWeight.w500, color: AppColors.greyTextColor, letterSpacing: -0.4), + ), + ], + ), + SizedBox(width: 68.h), + Utils.buildSvgWithAssets( + icon: AppAssets.forward_arrow_icon, + iconColor: AppColors.blackColor, + width: 18.h, + height: 13.h, + fit: BoxFit.contain, + ), + ], + ), + ], + ), + ), + ), + ); + }, + separatorBuilder: (BuildContext cxt, int index) => SizedBox(height: 16.h), + ).onPress(() { + prescriptionVM.setPrescriptionsDetailsLoading(); + Navigator.of(context).push( + FadePage( + page: PrescriptionDetailPage(prescriptionsResponseModel: getPrescriptionRequestModel()), + ), + ); + }), + SizedBox(height: 16.h), + const Divider(color: AppColors.dividerColor), + SizedBox(height: 16.h), + Row( + children: [ + // Expanded( + // child: CustomButton( + // text: widget.prescriptionsResponseModel.isHomeMedicineDeliverySupported! ? LocaleKeys.resendOrder.tr(context: context) : LocaleKeys.prescriptionDeliveryError.tr(context: context), + // onPressed: () {}, + // backgroundColor: AppColors.secondaryLightRedColor, + // borderColor: AppColors.secondaryLightRedColor, + // textColor: AppColors.primaryRedColor, + // fontSize: 14, + // fontWeight: FontWeight.w500, + // borderRadius: 12.h, + // height: 40.h, + // icon: AppAssets.appointment_calendar_icon, + // iconColor: AppColors.primaryRedColor, + // iconSize: 16.h, + // ), + // ), + // SizedBox(width: 16.h), + Expanded( + child: CustomButton( + text: "All Prescriptions".needTranslation, + onPressed: () { + Navigator.of(context) + .push( + FadePage( + page: PrescriptionsListPage(), + ), + ) + .then((val) { + prescriptionsViewModel.setPrescriptionsDetailsLoading(); + prescriptionsViewModel.getPrescriptionDetails(getPrescriptionRequestModel()); + }); + }, + backgroundColor: AppColors.secondaryLightRedColor, + borderColor: AppColors.secondaryLightRedColor, + textColor: AppColors.primaryRedColor, + fontSize: 14, + fontWeight: FontWeight.w500, + borderRadius: 12.h, + height: 40.h, + icon: AppAssets.requests, + iconColor: AppColors.primaryRedColor, + iconSize: 16.h, + ), + ), + ], + ), + ], + ), + ), + ); + }), + ], ), - SizedBox(height: 16.h), - ], - ), ], ).paddingSymmetrical(24.h, 24.h), ), @@ -242,28 +441,39 @@ class _AppointmentDetailsPageState extends State { ) ], ).paddingOnly(left: 16.h, top: 24.h, right: 16.h, bottom: 0.h), - CustomButton( - text: AppointmentType.getNextActionText(widget.patientAppointmentHistoryResponseModel.nextAction), - onPressed: () { - myAppointmentsViewModel.setIsPatientAppointmentShareLoading(true); - Navigator.of(context).push( - FadePage( - page: AppointmentPaymentPage(patientAppointmentHistoryResponseModel: widget.patientAppointmentHistoryResponseModel), - ), - ); - }, - backgroundColor: AppointmentType.getNextActionButtonColor(widget.patientAppointmentHistoryResponseModel.nextAction), - borderColor: AppointmentType.getNextActionButtonColor(widget.patientAppointmentHistoryResponseModel.nextAction).withOpacity(0.01), - textColor: AppColors.whiteColor, - fontSize: 16, - fontWeight: FontWeight.w500, - borderRadius: 12, - padding: EdgeInsets.fromLTRB(10, 0, 10, 0), - height: 50.h, - icon: AppointmentType.getNextActionIcon(widget.patientAppointmentHistoryResponseModel.nextAction), - iconColor: AppColors.whiteColor, - iconSize: 18.h, - ).paddingSymmetrical(16.h, 24.h), + AppointmentType.isArrived(widget.patientAppointmentHistoryResponseModel) + ? CustomButton( + text: "Re-book Appointment".needTranslation, + onPressed: () {}, + backgroundColor: AppColors.primaryRedColor, + borderColor: AppColors.primaryRedColor, + textColor: AppColors.whiteColor, + fontSize: 16, + fontWeight: FontWeight.w500, + borderRadius: 12, + padding: EdgeInsets.fromLTRB(10, 0, 10, 0), + height: 50.h, + icon: AppAssets.add_icon, + iconColor: AppColors.whiteColor, + iconSize: 18.h, + ).paddingSymmetrical(16.h, 24.h) + : CustomButton( + text: AppointmentType.getNextActionText(widget.patientAppointmentHistoryResponseModel.nextAction), + onPressed: () { + handleAppointmentNextAction(widget.patientAppointmentHistoryResponseModel.nextAction); + }, + backgroundColor: AppointmentType.getNextActionButtonColor(widget.patientAppointmentHistoryResponseModel.nextAction), + borderColor: AppointmentType.getNextActionButtonColor(widget.patientAppointmentHistoryResponseModel.nextAction).withOpacity(0.01), + textColor: widget.patientAppointmentHistoryResponseModel.nextAction == 15 ? AppColors.textColor : AppColors.whiteColor, + fontSize: 16, + fontWeight: FontWeight.w500, + borderRadius: 12, + padding: EdgeInsets.fromLTRB(10, 0, 10, 0), + height: 50.h, + icon: AppointmentType.getNextActionIcon(widget.patientAppointmentHistoryResponseModel.nextAction), + iconColor: AppColors.whiteColor, + iconSize: 18.h, + ).paddingSymmetrical(16.h, 24.h), ], ), ), @@ -272,4 +482,71 @@ class _AppointmentDetailsPageState extends State { ), ); } + + Future handleAppointmentNextAction(nextAction) async { + switch (nextAction) { + case 0: + break; + case 10: + showCommonBottomSheet(context, + child: Utils.getLoadingWidget(), callBackFunc: (str) {}, title: "", height: ResponsiveExtension.screenHeight * 0.3, isCloseButtonVisible: false, isDismissible: false, isFullScreen: false); + await myAppointmentsViewModel.confirmAppointment( + patientAppointmentHistoryResponseModel: widget.patientAppointmentHistoryResponseModel, + onSuccess: (apiResponse) { + Navigator.of(context).pop(); + showCommonBottomSheet(context, + child: Utils.getSuccessWidget(loadingText: "Appointment Confirmed Successfully".needTranslation), + callBackFunc: (str) {}, + title: "", + height: ResponsiveExtension.screenHeight * 0.3, + isCloseButtonVisible: false, + isDismissible: false, + isFullScreen: false, + isSuccessDialog: true); + }); + Navigator.of(context).pop(); + Navigator.of(context).pop(); + case 15: + break; + case 20: + myAppointmentsViewModel.setIsPatientAppointmentShareLoading(true); + Navigator.of(context).push( + FadePage( + page: AppointmentPaymentPage(patientAppointmentHistoryResponseModel: widget.patientAppointmentHistoryResponseModel), + ), + ); + case 50: + // return LocaleKeys.confirmLiveCare.tr(); + case 90: + showCommonBottomSheetWithoutHeight(context, + title: LocaleKeys.onlineCheckIn.tr(), + child: AppointmentCheckinBottomSheet( + patientAppointmentHistoryResponseModel: widget.patientAppointmentHistoryResponseModel, + myAppointmentsViewModel: myAppointmentsViewModel, + ), + callBackFunc: () {}, + isFullScreen: false); + default: + // return "No Action".needTranslation; + } + } + + PatientPrescriptionsResponseModel getPrescriptionRequestModel() { + return PatientPrescriptionsResponseModel( + appointmentNo: widget.patientAppointmentHistoryResponseModel.appointmentNo, + setupID: widget.patientAppointmentHistoryResponseModel.setupID, + episodeID: widget.patientAppointmentHistoryResponseModel.episodeID, + clinicID: widget.patientAppointmentHistoryResponseModel.clinicID, + projectID: widget.patientAppointmentHistoryResponseModel.projectID, + dischargeNo: 0, + isInOutPatient: widget.patientAppointmentHistoryResponseModel.isInOutPatient, + isHomeMedicineDeliverySupported: false, + doctorImageURL: widget.patientAppointmentHistoryResponseModel.doctorImageURL, + doctorName: "${widget.patientAppointmentHistoryResponseModel.doctorTitle} ${widget.patientAppointmentHistoryResponseModel.doctorNameObj}", + appointmentDate: widget.patientAppointmentHistoryResponseModel.appointmentDate, + clinicDescription: widget.patientAppointmentHistoryResponseModel.clinicName, + decimalDoctorRate: widget.patientAppointmentHistoryResponseModel.decimalDoctorRate, + name: widget.patientAppointmentHistoryResponseModel.projectName, + ); + } } diff --git a/lib/presentation/appointments/appointment_payment_page.dart b/lib/presentation/appointments/appointment_payment_page.dart index 861d5b9..7f2ea9d 100644 --- a/lib/presentation/appointments/appointment_payment_page.dart +++ b/lib/presentation/appointments/appointment_payment_page.dart @@ -19,12 +19,16 @@ import 'package:hmg_patient_app_new/features/my_appointments/models/resp_models/ import 'package:hmg_patient_app_new/features/my_appointments/my_appointments_view_model.dart'; import 'package:hmg_patient_app_new/features/payfort/payfort_view_model.dart'; import 'package:hmg_patient_app_new/generated/locale_keys.g.dart'; +import 'package:hmg_patient_app_new/presentation/appointments/my_appointments_page.dart'; +import 'package:hmg_patient_app_new/presentation/home/navigation_screen.dart'; +import 'package:hmg_patient_app_new/presentation/insurance/insurance_home_page.dart'; import 'package:hmg_patient_app_new/services/cache_service.dart'; import 'package:hmg_patient_app_new/theme/colors.dart'; import 'package:hmg_patient_app_new/widgets/buttons/custom_button.dart'; import 'package:hmg_patient_app_new/widgets/common_bottom_sheet.dart'; import 'package:hmg_patient_app_new/widgets/in_app_browser/InAppBrowser.dart'; import 'package:hmg_patient_app_new/widgets/shimmer/movies_shimmer_widget.dart'; +import 'package:hmg_patient_app_new/widgets/transitions/fade_page.dart'; import 'package:provider/provider.dart'; import 'package:smooth_corner/smooth_corner.dart'; @@ -220,7 +224,13 @@ class _AppointmentPaymentPageState extends State { "Insurance expired or inactive".needTranslation.toText14(color: AppColors.primaryRedColor, weight: FontWeight.w500).paddingSymmetrical(24.h, 0.h), CustomButton( text: LocaleKeys.updateInsurance.tr(context: context), - onPressed: () {}, + onPressed: () { + Navigator.of(context).push( + FadePage( + page: InsuranceHomePage(), + ), + ); + }, backgroundColor: AppColors.primaryRedColor, borderColor: AppColors.secondaryLightRedBorderColor, textColor: AppColors.whiteColor, @@ -366,14 +376,24 @@ class _AppointmentPaymentPageState extends State { clinicID: widget.patientAppointmentHistoryResponseModel.clinicID, projectID: widget.patientAppointmentHistoryResponseModel.projectID, appointmentNo: widget.patientAppointmentHistoryResponseModel.appointmentNo.toString(), - isFollowUp: myAppointmentsViewModel.patientAppointmentShareResponseModel!.isFollowup!); + isFollowUp: myAppointmentsViewModel.patientAppointmentShareResponseModel!.isFollowup!, + onSuccess: (apiResponse) { + Future.delayed(Duration(milliseconds: 500), () { + Navigator.of(context).pop(); + Navigator.pushAndRemoveUntil( + context, + FadePage( + page: LandingNavigation(), + ), + (r) => false); + Navigator.of(context).push( + FadePage(page: MyAppointmentsPage()), + ); + }); + }); } }); }); - Future.delayed(Duration(milliseconds: 500), () { - Navigator.of(context).pop(); - print(payfortViewModel.payfortCheckPaymentStatusResponseModel!.responseMessage!); - }); } else {} }); // checkPaymentStatus(appo); diff --git a/lib/presentation/appointments/my_appointments_page.dart b/lib/presentation/appointments/my_appointments_page.dart index 0850641..4a3a46e 100644 --- a/lib/presentation/appointments/my_appointments_page.dart +++ b/lib/presentation/appointments/my_appointments_page.dart @@ -64,49 +64,154 @@ class _MyAppointmentsPageState extends State { ], onTabChange: (index) { print(index); + myAppointmentsViewModel.onTabChange(index); }, ).paddingSymmetrical(24.h, 0.h), Consumer(builder: (context, myAppointmentsVM, child) { - return Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - SizedBox(height: 24.h), - // Expandable list - ListView.separated( - shrinkWrap: true, - physics: NeverScrollableScrollPhysics(), - itemCount: myAppointmentsVM.isMyAppointmentsLoading ? 5 : myAppointmentsVM.patientAppointmentsHistoryList.isNotEmpty ? myAppointmentsVM.patientAppointmentsHistoryList.length : 1, - itemBuilder: (context, index) { - return myAppointmentsVM.isMyAppointmentsLoading - ? const MoviesShimmerWidget().paddingSymmetrical(24.h, 0.h) - : myAppointmentsVM.patientAppointmentsHistoryList.isNotEmpty ? AnimationConfiguration.staggeredList( - position: index, - duration: const Duration(milliseconds: 500), - child: SlideAnimation( - verticalOffset: 100.0, - child: FadeInAnimation( - child: AnimatedContainer( - duration: Duration(milliseconds: 300), - curve: Curves.easeInOut, - margin: EdgeInsets.symmetric(vertical: 8.h), - decoration: RoundedRectangleBorder().toSmoothCornerDecoration(color: AppColors.whiteColor, borderRadius: 24.h, hasShadow: true), - child: AppointmentCard( - patientAppointmentHistoryResponseModel: myAppointmentsVM.patientAppointmentsHistoryList[index], - myAppointmentsViewModel: myAppointmentsViewModel, - ), - ).paddingSymmetrical(24.h, 0.h), - ), - ), - ) : Utils.getNoDataWidget(context); - }, - separatorBuilder: (BuildContext cxt, int index) => SizedBox(height: 16.h), - ), - ], - ); + return getSelectedTabData(myAppointmentsVM.selectedTabIndex, myAppointmentsVM); }), ], ), ), ); } + + Widget getSelectedTabData(int index, MyAppointmentsViewModel myAppointmentsVM) { + switch (index) { + case 0: + //All Appointments Tab Data + return Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + SizedBox(height: 24.h), + // Expandable list + ListView.separated( + shrinkWrap: true, + physics: NeverScrollableScrollPhysics(), + itemCount: myAppointmentsVM.isMyAppointmentsLoading + ? 5 + : myAppointmentsVM.patientAppointmentsHistoryList.isNotEmpty + ? myAppointmentsVM.patientAppointmentsHistoryList.length + : 1, + itemBuilder: (context, index) { + return myAppointmentsVM.isMyAppointmentsLoading + ? const MoviesShimmerWidget().paddingSymmetrical(24.h, 0.h) + : myAppointmentsVM.patientAppointmentsHistoryList.isNotEmpty + ? AnimationConfiguration.staggeredList( + position: index, + duration: const Duration(milliseconds: 500), + child: SlideAnimation( + verticalOffset: 100.0, + child: FadeInAnimation( + child: AnimatedContainer( + duration: Duration(milliseconds: 300), + curve: Curves.easeInOut, + margin: EdgeInsets.symmetric(vertical: 8.h), + decoration: RoundedRectangleBorder().toSmoothCornerDecoration(color: AppColors.whiteColor, borderRadius: 24.h, hasShadow: true), + child: AppointmentCard( + patientAppointmentHistoryResponseModel: myAppointmentsVM.patientAppointmentsHistoryList[index], + myAppointmentsViewModel: myAppointmentsViewModel, + ), + ).paddingSymmetrical(24.h, 0.h), + ), + ), + ) + : Utils.getNoDataWidget(context); + }, + separatorBuilder: (BuildContext cxt, int index) => SizedBox(height: 16.h), + ), + ], + ); + case 1: + //Upcoming Appointments Tab Data + return Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + SizedBox(height: 24.h), + // Expandable list + ListView.separated( + shrinkWrap: true, + physics: NeverScrollableScrollPhysics(), + itemCount: myAppointmentsVM.isMyAppointmentsLoading + ? 5 + : myAppointmentsVM.patientUpcomingAppointmentsHistoryList.isNotEmpty + ? myAppointmentsVM.patientUpcomingAppointmentsHistoryList.length + : 1, + itemBuilder: (context, index) { + return myAppointmentsVM.isMyAppointmentsLoading + ? const MoviesShimmerWidget().paddingSymmetrical(24.h, 0.h) + : myAppointmentsVM.patientUpcomingAppointmentsHistoryList.isNotEmpty + ? AnimationConfiguration.staggeredList( + position: index, + duration: const Duration(milliseconds: 500), + child: SlideAnimation( + verticalOffset: 100.0, + child: FadeInAnimation( + child: AnimatedContainer( + duration: Duration(milliseconds: 300), + curve: Curves.easeInOut, + margin: EdgeInsets.symmetric(vertical: 8.h), + decoration: RoundedRectangleBorder().toSmoothCornerDecoration(color: AppColors.whiteColor, borderRadius: 24.h, hasShadow: true), + child: AppointmentCard( + patientAppointmentHistoryResponseModel: myAppointmentsVM.patientUpcomingAppointmentsHistoryList[index], + myAppointmentsViewModel: myAppointmentsViewModel, + ), + ).paddingSymmetrical(24.h, 0.h), + ), + ), + ) + : Utils.getNoDataWidget(context); + }, + separatorBuilder: (BuildContext cxt, int index) => SizedBox(height: 16.h), + ), + ], + ); + case 2: + //Completed Appointments Tab Data + return Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + SizedBox(height: 24.h), + // Expandable list + ListView.separated( + shrinkWrap: true, + physics: NeverScrollableScrollPhysics(), + itemCount: myAppointmentsVM.isMyAppointmentsLoading + ? 5 + : myAppointmentsVM.patientArrivedAppointmentsHistoryList.isNotEmpty + ? myAppointmentsVM.patientArrivedAppointmentsHistoryList.length + : 1, + itemBuilder: (context, index) { + return myAppointmentsVM.isMyAppointmentsLoading + ? const MoviesShimmerWidget().paddingSymmetrical(24.h, 0.h) + : myAppointmentsVM.patientArrivedAppointmentsHistoryList.isNotEmpty + ? AnimationConfiguration.staggeredList( + position: index, + duration: const Duration(milliseconds: 500), + child: SlideAnimation( + verticalOffset: 100.0, + child: FadeInAnimation( + child: AnimatedContainer( + duration: Duration(milliseconds: 300), + curve: Curves.easeInOut, + margin: EdgeInsets.symmetric(vertical: 8.h), + decoration: RoundedRectangleBorder().toSmoothCornerDecoration(color: AppColors.whiteColor, borderRadius: 24.h, hasShadow: true), + child: AppointmentCard( + patientAppointmentHistoryResponseModel: myAppointmentsVM.patientArrivedAppointmentsHistoryList[index], + myAppointmentsViewModel: myAppointmentsViewModel, + ), + ).paddingSymmetrical(24.h, 0.h), + ), + ), + ) + : Utils.getNoDataWidget(context); + }, + separatorBuilder: (BuildContext cxt, int index) => SizedBox(height: 16.h), + ), + ], + ); + default: + return Container(); + } + } } diff --git a/lib/presentation/appointments/widgets/appointment_card.dart b/lib/presentation/appointments/widgets/appointment_card.dart index 51af492..e9babeb 100644 --- a/lib/presentation/appointments/widgets/appointment_card.dart +++ b/lib/presentation/appointments/widgets/appointment_card.dart @@ -1,3 +1,4 @@ +import 'package:easy_localization/easy_localization.dart'; import 'package:flutter/material.dart'; import 'package:hmg_patient_app_new/core/app_assets.dart'; import 'package:hmg_patient_app_new/core/app_state.dart'; @@ -10,6 +11,7 @@ import 'package:hmg_patient_app_new/extensions/widget_extensions.dart'; import 'package:hmg_patient_app_new/features/my_appointments/models/resp_models/patient_appointment_history_response_model.dart'; import 'package:hmg_patient_app_new/features/my_appointments/my_appointments_view_model.dart'; import 'package:hmg_patient_app_new/features/my_appointments/utils/appointment_type.dart'; +import 'package:hmg_patient_app_new/generated/locale_keys.g.dart'; import 'package:hmg_patient_app_new/presentation/appointments/appointment_details_page.dart'; import 'package:hmg_patient_app_new/theme/colors.dart'; import 'package:hmg_patient_app_new/widgets/buttons/custom_button.dart'; @@ -33,11 +35,16 @@ class _AppointmentCardState extends State { AppState appState = getIt.get(); return InkWell( onTap: () { - Navigator.of(context).push( + Navigator.of(context) + .push( FadePage( page: AppointmentDetailsPage(patientAppointmentHistoryResponseModel: widget.patientAppointmentHistoryResponseModel), ), - ); + ) + .then((val) { + widget.myAppointmentsViewModel.initAppointmentsViewModel(); + widget.myAppointmentsViewModel.getPatientAppointments(true, false); + }); }, child: Padding( padding: EdgeInsets.all(14.h), @@ -94,24 +101,26 @@ class _AppointmentCardState extends State { ), ), // TODO: Implement the logic to enable/disable the switch based on reminder status - Switch( - activeColor: AppColors.successColor, - activeTrackColor: AppColors.successColor.withValues(alpha: .15), - thumbIcon: WidgetStateProperty.resolveWith( - (Set states) { - if (states.contains(WidgetState.selected)) { - return const Icon(Icons.check); // Icon when switch is ON - } - return const Icon(Icons.close); // Icon when switch is OFF - }, - ), - value: widget.patientAppointmentHistoryResponseModel.hasReminder!, - onChanged: (newValue) { - setState(() { - widget.myAppointmentsViewModel.setAppointmentReminder(newValue, widget.patientAppointmentHistoryResponseModel); - }); - }, - ), + AppointmentType.isArrived(widget.patientAppointmentHistoryResponseModel) + ? SizedBox() + : Switch( + activeColor: AppColors.successColor, + activeTrackColor: AppColors.successColor.withValues(alpha: .15), + thumbIcon: WidgetStateProperty.resolveWith( + (Set states) { + if (states.contains(WidgetState.selected)) { + return const Icon(Icons.check); // Icon when switch is ON + } + return const Icon(Icons.close); // Icon when switch is OFF + }, + ), + value: widget.patientAppointmentHistoryResponseModel.hasReminder!, + onChanged: (newValue) { + setState(() { + widget.myAppointmentsViewModel.setAppointmentReminder(newValue, widget.patientAppointmentHistoryResponseModel); + }); + }, + ), ], ), SizedBox(height: 16.h), @@ -155,30 +164,32 @@ class _AppointmentCardState extends State { children: [ Expanded( flex: 6, - child: CustomButton( - text: AppointmentType.getNextActionText(widget.patientAppointmentHistoryResponseModel.nextAction), - onPressed: () { - Navigator.of(context) - .push(FadePage( - page: AppointmentDetailsPage(patientAppointmentHistoryResponseModel: widget.patientAppointmentHistoryResponseModel), - )) - .then((val) { - widget.myAppointmentsViewModel.initAppointmentsViewModel(); - widget.myAppointmentsViewModel.getPatientAppointments(true, false); - }); - }, - backgroundColor: AppointmentType.getNextActionButtonColor(widget.patientAppointmentHistoryResponseModel.nextAction).withOpacity(0.1), - borderColor: AppointmentType.getNextActionButtonColor(widget.patientAppointmentHistoryResponseModel.nextAction).withOpacity(0.01), - textColor: AppointmentType.getNextActionButtonColor(widget.patientAppointmentHistoryResponseModel.nextAction), - fontSize: 14, - fontWeight: FontWeight.w500, - borderRadius: 12, - padding: EdgeInsets.fromLTRB(10, 0, 10, 0), - height: 40.h, - icon: AppointmentType.getNextActionIcon(widget.patientAppointmentHistoryResponseModel.nextAction), - iconColor: AppointmentType.getNextActionButtonColor(widget.patientAppointmentHistoryResponseModel.nextAction), - iconSize: 14.h, - ), + child: AppointmentType.isArrived(widget.patientAppointmentHistoryResponseModel) + ? getArrivedAppointmentButton() + : CustomButton( + text: AppointmentType.getNextActionText(widget.patientAppointmentHistoryResponseModel.nextAction), + onPressed: () { + Navigator.of(context) + .push(FadePage( + page: AppointmentDetailsPage(patientAppointmentHistoryResponseModel: widget.patientAppointmentHistoryResponseModel), + )) + .then((val) { + widget.myAppointmentsViewModel.initAppointmentsViewModel(); + widget.myAppointmentsViewModel.getPatientAppointments(true, false); + }); + }, + backgroundColor: AppointmentType.getNextActionButtonColor(widget.patientAppointmentHistoryResponseModel.nextAction).withOpacity(0.15), + borderColor: AppointmentType.getNextActionButtonColor(widget.patientAppointmentHistoryResponseModel.nextAction).withOpacity(0.01), + textColor: AppointmentType.getNextActionTextColor(widget.patientAppointmentHistoryResponseModel.nextAction), + fontSize: 14, + fontWeight: FontWeight.w500, + borderRadius: 12, + padding: EdgeInsets.fromLTRB(10, 0, 10, 0), + height: 40.h, + icon: AppointmentType.getNextActionIcon(widget.patientAppointmentHistoryResponseModel.nextAction), + iconColor: AppointmentType.getNextActionTextColor(widget.patientAppointmentHistoryResponseModel.nextAction), + iconSize: 14.h, + ), ), SizedBox(width: 8.h), Expanded( @@ -200,11 +211,16 @@ class _AppointmentCardState extends State { ), ), ).onPress(() { - Navigator.of(context).push( + Navigator.of(context) + .push( FadePage( page: AppointmentDetailsPage(patientAppointmentHistoryResponseModel: widget.patientAppointmentHistoryResponseModel), ), - ); + ) + .then((val) { + widget.myAppointmentsViewModel.initAppointmentsViewModel(); + widget.myAppointmentsViewModel.getPatientAppointments(true, false); + }); }), ), ], @@ -215,5 +231,39 @@ class _AppointmentCardState extends State { ); } + Widget getArrivedAppointmentButton() { + return DateTime.now().difference(DateUtil.convertStringToDate(widget.patientAppointmentHistoryResponseModel.appointmentDate)).inDays <= 15 + ? CustomButton( + text: LocaleKeys.askDoctor.tr(context: context), + onPressed: () {}, + 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, + icon: AppAssets.ask_doctor_icon, + iconColor: AppColors.primaryRedColor, + iconSize: 16.h, + ) + : CustomButton( + text: "Rebook with same doctor".needTranslation, + onPressed: () {}, + backgroundColor: AppColors.greyColor, + borderColor: AppColors.greyColor, + textColor: AppColors.blackColor, + fontSize: 14, + fontWeight: FontWeight.w500, + borderRadius: 12, + padding: EdgeInsets.fromLTRB(10, 0, 10, 0), + height: 40.h, + icon: AppAssets.rebook_appointment_icon, + iconColor: AppColors.blackColor, + iconSize: 16.h, + ); + } + void performAppointmentNextAction(nextAction) {} } diff --git a/lib/presentation/appointments/widgets/appointment_checkin_bottom_sheet.dart b/lib/presentation/appointments/widgets/appointment_checkin_bottom_sheet.dart new file mode 100644 index 0000000..77fec61 --- /dev/null +++ b/lib/presentation/appointments/widgets/appointment_checkin_bottom_sheet.dart @@ -0,0 +1,164 @@ +import 'package:easy_localization/easy_localization.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter_nfc_kit/flutter_nfc_kit.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/privilege/ProjectDetailListModel.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/size_utils.dart'; +import 'package:hmg_patient_app_new/core/utils/utils.dart'; +import 'package:hmg_patient_app_new/extensions/string_extensions.dart'; +import 'package:hmg_patient_app_new/extensions/widget_extensions.dart'; +import 'package:hmg_patient_app_new/features/my_appointments/models/resp_models/patient_appointment_history_response_model.dart'; +import 'package:hmg_patient_app_new/features/my_appointments/my_appointments_view_model.dart'; +import 'package:hmg_patient_app_new/generated/locale_keys.g.dart'; +import 'package:hmg_patient_app_new/presentation/appointments/my_appointments_page.dart'; +import 'package:hmg_patient_app_new/presentation/home/navigation_screen.dart'; +import 'package:hmg_patient_app_new/theme/colors.dart'; +import 'package:barcode_scan2/barcode_scan2.dart'; +import 'package:hmg_patient_app_new/widgets/common_bottom_sheet.dart'; +import 'package:hmg_patient_app_new/widgets/nfc/nfc_reader_sheet.dart'; +import 'package:hmg_patient_app_new/widgets/transitions/fade_page.dart'; + +class AppointmentCheckinBottomSheet extends StatelessWidget { + AppointmentCheckinBottomSheet({super.key, required this.patientAppointmentHistoryResponseModel, required this.myAppointmentsViewModel}); + + PatientAppointmentHistoryResponseModel patientAppointmentHistoryResponseModel; + MyAppointmentsViewModel myAppointmentsViewModel; + + bool _supportsNFC = false; + + late LocationUtils locationUtils; + ProjectDetailListModel projectDetailListModel = ProjectDetailListModel(); + + @override + Widget build(BuildContext context) { + AppState _appState = getIt.get(); + FlutterNfcKit.nfcAvailability.then((value) { + _supportsNFC = (value == NFCAvailability.available); + }); + return Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + checkInOptionCard( + AppAssets.checkin_location_icon, + "Live Location".needTranslation, + "Verify your location to be at hospital to check in".needTranslation, + ).onPress(() { + // locationUtils = LocationUtils( + // isShowConfirmDialog: false, + // navigationService: myAppointmentsViewModel.navigationService, + // appState: myAppointmentsViewModel.appState, + // ); + locationUtils.getCurrentLocation(callBack: (value) { + projectDetailListModel = Utils.getProjectDetailObj(_appState, patientAppointmentHistoryResponseModel.projectID); + double dist = Utils.distance(value.latitude, value.longitude, double.parse(projectDetailListModel.latitude!), double.parse(projectDetailListModel.longitude!)).ceilToDouble() * 1000; + print(dist); + if (dist <= projectDetailListModel.geofenceRadius!) { + sendCheckInRequest(projectDetailListModel.checkInQrCode!, context); + } else { + showCommonBottomSheetWithoutHeight(context, + title: "Error".needTranslation, + child: Utils.getErrorWidget(loadingText: "Please ensure you're within the hospital location to perform online check-in.".needTranslation), callBackFunc: () { + Navigator.of(context).pop(); + }, isFullScreen: false); + } + }); + }), + SizedBox(height: 16.h), + checkInOptionCard( + AppAssets.checkin_nfc_icon, + "NFC (Near Field Communication)".needTranslation, + "Scan your phone via NFC board to check in".needTranslation, + ).onPress(() { + Future.delayed(const Duration(milliseconds: 500), () { + showNfcReader(context, onNcfScan: (String nfcId) { + Future.delayed(const Duration(milliseconds: 100), () { + sendCheckInRequest(nfcId, context); + }); + }, onCancel: () {}); + }); + }), + SizedBox(height: 16.h), + checkInOptionCard( + AppAssets.checkin_qr_icon, + "QR Code".needTranslation, + "Scan QR code with your camera to check in".needTranslation, + ).onPress(() async { + String onlineCheckInQRCode = (await BarcodeScanner.scan().then((value) => value.rawContent)); + if (onlineCheckInQRCode != "") { + sendCheckInRequest(onlineCheckInQRCode, context); + } else {} + }), + ], + ); + } + + 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: [ + Utils.buildSvgWithAssets(icon: icon, width: 40.h, height: 40.h, fit: BoxFit.fill), + SizedBox(height: 16.h), + Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + title.toText16(isBold: true, color: AppColors.textColor), + subTitle.toText12(fontWeight: FontWeight.w500, color: AppColors.greyTextColor), + ], + ), + Utils.buildSvgWithAssets( + icon: AppAssets.forward_arrow_icon, + iconColor: AppColors.blackColor, + width: 18.h, + height: 13.h, + fit: BoxFit.contain, + ), + ], + ), + ], + ).paddingAll(16.h), + ); + } + + void sendCheckInRequest(String scannedCode, BuildContext context) async { + showCommonBottomSheet(context, + child: Utils.getLoadingWidget(), callBackFunc: (str) {}, title: "", height: ResponsiveExtension.screenHeight * 0.3, isCloseButtonVisible: false, isDismissible: false, isFullScreen: false); + await myAppointmentsViewModel.sendCheckInNfcRequest( + patientAppointmentHistoryResponseModel: patientAppointmentHistoryResponseModel, + scannedCode: scannedCode, + checkInType: 2, + onSuccess: (apiResponse) { + Navigator.of(context).pop(); + showCommonBottomSheetWithoutHeight(context, title: "Success".needTranslation, child: Utils.getSuccessWidget(loadingText: LocaleKeys.success.tr()), callBackFunc: () { + Navigator.of(context).pop(); + Navigator.pushAndRemoveUntil( + context, + FadePage( + page: LandingNavigation(), + ), + (r) => false); + Navigator.of(context).push( + FadePage(page: MyAppointmentsPage()), + ); + }, isFullScreen: false); + }, + onError: (error) { + Navigator.of(context).pop(); + showCommonBottomSheetWithoutHeight(context, title: "Error".needTranslation, child: Utils.getErrorWidget(loadingText: error), callBackFunc: () { + Navigator.of(context).pop(); + }, isFullScreen: false); + }, + ); + } +} diff --git a/lib/presentation/appointments/widgets/appointment_doctor_card.dart b/lib/presentation/appointments/widgets/appointment_doctor_card.dart index 801fdca..e6d7338 100644 --- a/lib/presentation/appointments/widgets/appointment_doctor_card.dart +++ b/lib/presentation/appointments/widgets/appointment_doctor_card.dart @@ -59,10 +59,7 @@ class AppointmentDoctorCard extends StatelessWidget { icon: AppAssets.doctor_calendar_icon, labelText: "${DateUtil.formatDateToDate(DateUtil.convertStringToDate(patientAppointmentHistoryResponseModel.appointmentDate), false)}, ${DateUtil.formatDateToTimeLang(DateUtil.convertStringToDate(patientAppointmentHistoryResponseModel.appointmentDate), false)}"), - AppCustomChipWidget( - icon: AppAssets.rating_icon, - iconColor: AppColors.ratingColorYellow, - labelText: "Rating: ${patientAppointmentHistoryResponseModel.decimalDoctorRate}"), + AppCustomChipWidget(icon: AppAssets.rating_icon, iconColor: AppColors.ratingColorYellow, labelText: "Rating: ${patientAppointmentHistoryResponseModel.decimalDoctorRate}"), ], ), ], @@ -82,20 +79,37 @@ class AppointmentDoctorCard extends StatelessWidget { Widget getAppointmentActionButtons(bool isAppointmentArrived) { if (isAppointmentArrived) { - return CustomButton( - text: LocaleKeys.askDoctor.tr(), - onPressed: () {}, - 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, - icon: AppAssets.ask_doctor_icon, - iconColor: AppColors.primaryRedColor, - ); + return DateTime.now().difference(DateUtil.convertStringToDate(patientAppointmentHistoryResponseModel.appointmentDate)).inDays <= 15 + ? CustomButton( + text: LocaleKeys.askDoctor.tr(), + onPressed: () {}, + 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, + iconSize: 16.h, + icon: AppAssets.ask_doctor_icon, + iconColor: AppColors.primaryRedColor, + ) + : CustomButton( + text: "Rebook with same doctor".needTranslation, + onPressed: () {}, + backgroundColor: AppColors.greyColor, + borderColor: AppColors.greyColor, + textColor: AppColors.blackColor, + fontSize: 14, + fontWeight: FontWeight.w500, + borderRadius: 12, + padding: EdgeInsets.fromLTRB(10, 0, 10, 0), + height: 40.h, + icon: AppAssets.rebook_appointment_icon, + iconColor: AppColors.blackColor, + iconSize: 16.h, + ); } else { return Row( children: [ diff --git a/lib/presentation/appointments/widgets/appointment_queueing_screen.dart b/lib/presentation/appointments/widgets/appointment_queueing_screen.dart new file mode 100644 index 0000000..e69de29 diff --git a/lib/presentation/authentication/login.dart b/lib/presentation/authentication/login.dart index f6e725a..0b463cc 100644 --- a/lib/presentation/authentication/login.dart +++ b/lib/presentation/authentication/login.dart @@ -203,6 +203,7 @@ class LoginScreenState extends State { borderColor: AppColors.borderOnlyColor, textColor: AppColors.textColor, icon: AppAssets.whatsapp, + iconColor: null, ), ), ], diff --git a/lib/presentation/authentication/quick_login.dart b/lib/presentation/authentication/quick_login.dart index 9cf0431..ff70dab 100644 --- a/lib/presentation/authentication/quick_login.dart +++ b/lib/presentation/authentication/quick_login.dart @@ -19,6 +19,7 @@ class QuickLogin extends StatefulWidget { } class _QuickLogin extends State { + @override Widget build(BuildContext context) { return Container( @@ -70,7 +71,7 @@ class _QuickLogin extends State { height: 101, ), const SizedBox(height: 10), - LocaleKeys.enableQuickLogin.tr().toText26(isBold: true), + LocaleKeys.enableQuickLogin.tr().toText26(isBold: true), // Text( // ' TranslationBase.of(context).enableQuickLogin', // style: context.dynamicTextStyle( @@ -100,6 +101,9 @@ class _QuickLogin extends State { text:LocaleKeys.enableQuickLogin.tr(), onPressed: () { widget.onPressed(); + setState(() { + + }); }, backgroundColor: Color(0xffED1C2B), borderColor: Color(0xffED1C2B), @@ -118,7 +122,7 @@ class _QuickLogin extends State { child: CustomButton( text: LocaleKeys.notNow.tr(), onPressed: () { - Navigator.pop(context, true); + Navigator.pop(context, "true"); }, backgroundColor: Color(0xffFEE9EA), borderColor: Color(0xffFEE9EA), diff --git a/lib/presentation/authentication/register.dart b/lib/presentation/authentication/register.dart index 00f4808..f238582 100644 --- a/lib/presentation/authentication/register.dart +++ b/lib/presentation/authentication/register.dart @@ -222,7 +222,7 @@ class _RegisterNew extends State { child: CustomButton( text: LocaleKeys.sendOTPSMS.tr(), onPressed: () async { - await authVM.onRegisterPress(otpTypeEnum: OTPTypeEnum.sms); + await authVM.onRegistrationStart(otpTypeEnum: OTPTypeEnum.sms); }, backgroundColor: AppColors.primaryRedColor, borderColor: AppColors.primaryRedBorderColor, @@ -245,7 +245,7 @@ class _RegisterNew extends State { child: CustomButton( text: LocaleKeys.sendOTPWHATSAPP.tr(), onPressed: () async { - await authVM.onRegisterPress(otpTypeEnum: OTPTypeEnum.whatsapp); + await authVM.onRegistrationStart(otpTypeEnum: OTPTypeEnum.whatsapp); }, backgroundColor: AppColors.whiteColor, borderColor: AppColors.borderOnlyColor, diff --git a/lib/presentation/authentication/register_step2.dart b/lib/presentation/authentication/register_step2.dart index 1e4c650..bd23be1 100644 --- a/lib/presentation/authentication/register_step2.dart +++ b/lib/presentation/authentication/register_step2.dart @@ -19,31 +19,24 @@ import 'package:hmg_patient_app_new/widgets/input_widget.dart'; import 'package:provider/provider.dart'; class RegisterNewStep2 extends StatefulWidget { - var nHICData; - var payload; - - RegisterNewStep2(this.nHICData, this.payload, {Key? key}) : super(key: key); + RegisterNewStep2({Key? key}) : super(key: key); @override _RegisterNew createState() => _RegisterNew(); } class _RegisterNew extends State { - bool isFromDubai = true; AuthenticationViewModel? authVM; @override void initState() { super.initState(); authVM = context.read(); - authVM!.loadCountriesData(context: context); - // isFromDubai = widget.payload.zipCode!.contains("971") || widget.payload.zipCode!.contains("+971"); } @override void dispose() { super.dispose(); - authVM!.clearDefaultInputValues(); } @override @@ -72,22 +65,22 @@ class _RegisterNew extends State { child: Column( children: [ TextInputWidget( - labelText: isFromDubai ? LocaleKeys.fullName.tr() : LocaleKeys.name.tr(), - hintText: isFromDubai ? "name" ?? "" : (widget.nHICData!.firstNameEn!.toUpperCase() + " " + widget.nHICData!.lastNameEn!.toUpperCase()), - controller: null, + labelText: authVM!.isUserFromUAE() ? LocaleKeys.fullName.tr() : LocaleKeys.name.tr(), + hintText: authVM!.isUserFromUAE() ? "" : ("${appState.getNHICUserData.firstNameEn!.toUpperCase()} ${appState.getNHICUserData.lastNameEn!.toUpperCase()}"), + controller: authVM!.isUserFromUAE() ? authVM!.nameController : null, isEnable: true, prefix: null, isAllowRadius: false, isBorderAllowed: false, keyboardType: TextInputType.text, isAllowLeadingIcon: true, - isReadOnly: isFromDubai ? false : true, + isReadOnly: authVM!.isUserFromUAE() ? false : true, leadingIcon: AppAssets.user_circle) .paddingSymmetrical(0.h, 16.h), Divider(height: 1, color: AppColors.greyColor), TextInputWidget( labelText: LocaleKeys.nationalIdNumber.tr(), - hintText: isFromDubai ? "widget.payload.nationalID!" : (widget.nHICData!.idNumber ?? ""), + hintText: authVM!.isUserFromUAE() ? appState.getUserRegistrationPayload.patientIdentificationId.toString() : (appState.getNHICUserData.idNumber ?? ""), controller: null, isEnable: true, prefix: null, @@ -98,7 +91,7 @@ class _RegisterNew extends State { leadingIcon: AppAssets.student_card) .paddingSymmetrical(0.h, 16.h), Divider(height: 1, color: AppColors.greyColor), - isFromDubai + authVM!.isUserFromUAE() ? Selector( selector: (_, authViewModel) => authViewModel.genderType, shouldRebuild: (previous, next) => previous != next, @@ -121,19 +114,19 @@ class _RegisterNew extends State { }) : TextInputWidget( labelText: LocaleKeys.gender.tr(), - hintText: (widget.nHICData!.gender ?? ""), + hintText: (appState.getNHICUserData.gender ?? ""), controller: null, isEnable: true, prefix: null, isAllowRadius: false, isBorderAllowed: false, isAllowLeadingIcon: true, - isReadOnly: isFromDubai ? false : true, + isReadOnly: authVM!.isUserFromUAE() ? false : true, leadingIcon: AppAssets.user_full, onChange: (value) {}) .paddingSymmetrical(0.h, 16.h), Divider(height: 1, color: AppColors.greyColor), - isFromDubai + authVM!.isUserFromUAE() ? Selector( selector: (_, authViewModel) => authViewModel.maritalStatus, shouldRebuild: (previous, next) => previous != next, @@ -158,8 +151,8 @@ class _RegisterNew extends State { : TextInputWidget( labelText: LocaleKeys.maritalStatus.tr(), hintText: appState.isArabic() - ? (MaritalStatusTypeExtension.fromValue(widget.nHICData!.maritalStatusCode)!.typeAr) - : (MaritalStatusTypeExtension.fromValue(widget.nHICData!.maritalStatusCode)!.type), + ? (MaritalStatusTypeExtension.fromValue(appState.getNHICUserData.maritalStatusCode)!.typeAr) + : (MaritalStatusTypeExtension.fromValue(appState.getNHICUserData.maritalStatusCode)!.type), isEnable: true, prefix: null, isAllowRadius: false, @@ -170,7 +163,7 @@ class _RegisterNew extends State { onChange: (value) {}) .paddingSymmetrical(0.h, 16.h), Divider(height: 1, color: AppColors.greyColor), - isFromDubai + authVM!.isUserFromUAE() ? Selector? countriesList, NationalityCountries? selectedCountry, bool isArabic})>( selector: (context, authViewModel) { final appState = getIt.get(); @@ -206,8 +199,8 @@ class _RegisterNew extends State { : TextInputWidget( labelText: LocaleKeys.nationality.tr(), hintText: appState.isArabic() - ? (authVM!.countriesList!.firstWhere((e) => e.id == (widget.nHICData!.nationalityCode ?? ""), orElse: () => NationalityCountries()).nameN ?? "") - : (authVM!.countriesList!.firstWhere((e) => e.id == (widget.nHICData!.nationalityCode ?? ""), orElse: () => NationalityCountries()).name ?? ""), + ? (authVM!.countriesList!.firstWhere((e) => e.id == (appState.getNHICUserData.nationalityCode ?? ""), orElse: () => NationalityCountries()).nameN ?? "") + : (authVM!.countriesList!.firstWhere((e) => e.id == (appState.getNHICUserData.nationalityCode ?? ""), orElse: () => NationalityCountries()).name ?? ""), isEnable: true, prefix: null, isAllowRadius: false, @@ -223,7 +216,7 @@ class _RegisterNew extends State { ), TextInputWidget( labelText: LocaleKeys.mobileNumber.tr(), - hintText: ("widget.payload.mobileNo" ?? ""), + hintText: (appState.getUserRegistrationPayload.patientMobileNumber.toString() ?? ""), controller: authVM!.phoneNumberController, isEnable: true, prefix: null, @@ -240,15 +233,15 @@ class _RegisterNew extends State { ), TextInputWidget( labelText: LocaleKeys.dob.tr(), - hintText: isFromDubai ? "widget.payload.dob!" : (widget.nHICData!.dateOfBirth ?? ""), + hintText: authVM!.isUserFromUAE() ? appState.getUserRegistrationPayload.dob! : (appState.getNHICUserData.dateOfBirth ?? ""), controller: authVM!.dobController, - isEnable: true, + isEnable: authVM!.isUserFromUAE() ? true : false, prefix: null, isBorderAllowed: false, isAllowLeadingIcon: true, isReadOnly: true, leadingIcon: AppAssets.birthday_cake, - selectionType: SelectionTypeEnum.calendar, + selectionType: authVM!.isUserFromUAE() ? SelectionTypeEnum.calendar : null, ).paddingSymmetrical(0.h, 16.h), ], ), @@ -324,7 +317,7 @@ class _RegisterNew extends State { padding: EdgeInsets.only(bottom: MediaQuery.of(bottomSheetContext).viewInsets.bottom), child: SingleChildScrollView( child: GenericBottomSheet( - textController: TextEditingController(), + textController: authVM!.emailController, isForEmail: true, buttons: [ Padding( @@ -332,13 +325,7 @@ class _RegisterNew extends State { child: CustomButton( text: LocaleKeys.submit, onPressed: () { - // if (emailAddress.text.isEmpty) { - // Utils.showErrorToast(TranslationBase.of(context).enterEmailAddress); - // return; - // } else { - // Navigator.of(context).pop(); - // registerNow(); - // } + authVM!.onRegistrationComplete(); }, backgroundColor: AppColors.bgGreenColor, borderColor: AppColors.bgGreenColor, diff --git a/lib/presentation/authentication/saved_login_screen.dart b/lib/presentation/authentication/saved_login_screen.dart index 57f115d..589616f 100644 --- a/lib/presentation/authentication/saved_login_screen.dart +++ b/lib/presentation/authentication/saved_login_screen.dart @@ -34,11 +34,17 @@ class _SavedLogin extends State { void initState() { authVm = context.read(); appState = getIt.get(); + loginType = LoginTypeExtension.fromValue(appState.getSelectDeviceByImeiRespModelElement!.logInType!)!; + authVm.phoneNumberController.text = appState.getSelectDeviceByImeiRespModelElement!.mobile!.startsWith("0") ? appState.getSelectDeviceByImeiRespModelElement!.mobile!.replaceFirst("0", "") : appState.getSelectDeviceByImeiRespModelElement!.mobile!; authVm.nationalIdController.text = appState.getSelectDeviceByImeiRespModelElement!.identificationNo!; + if (loginType == LoginTypeEnum.fingerprint || loginType == LoginTypeEnum.face) { + authVm.loginWithFingerPrintFace(); + } + super.initState(); } @@ -78,16 +84,16 @@ class _SavedLogin extends State { children: [ // Last login info - ("${LocaleKeys.lastloginBy.tr()} ${LoginTypeExtension.fromValue(appState.getSelectDeviceByImeiRespModelElement!.logInType ?? 0)!.displayName}") - .toText14(isBold: true, color: AppColors.greyTextColor), + ("${LocaleKeys.lastloginBy.tr()} ${loginType.displayName}").toText14(isBold: true, color: AppColors.greyTextColor), (appState.getSelectDeviceByImeiRespModelElement!.createdOn != null - ? DateUtil.getFormattedDate(DateUtil.convertStringToDate(appState.getSelectDeviceByImeiRespModelElement!.createdOn!), "d MMMM, y at HH:mm") + ? DateUtil.getFormattedDate(DateUtil.convertStringToDate(appState.getSelectDeviceByImeiRespModelElement!.createdOn!), "d MMMM, y 'at' HH:mm") : '--') .toText16(isBold: true, color: AppColors.textColor), Container( margin: EdgeInsets.all(16.h), - child: Utils.buildSvgWithAssets(icon: getTypeIcons(loginType.toInt), height: 54, width: 54, iconColor: loginType.toInt == 4 ? null : AppColors.primaryRedColor)), + child: Utils.buildSvgWithAssets( + icon: getTypeIcons(appState.getSelectDeviceByImeiRespModelElement!.logInType!), height: 54, width: 54, iconColor: loginType.toInt == 4 ? null : AppColors.primaryRedColor)), // Face ID login button SizedBox( height: 45, @@ -95,7 +101,7 @@ class _SavedLogin extends State { text: "${LocaleKeys.loginBy.tr()} ${loginType.displayName}", onPressed: () { if (loginType == LoginTypeEnum.fingerprint || loginType == LoginTypeEnum.face) { - authVm.loginWithFingerPrintFace(loginType.toInt); + authVm.loginWithFingerPrintFace(); } else { // int? val = loginType.toInt; authVm.checkUserAuthentication(otpTypeEnum: loginType == LoginTypeEnum.sms ? OTPTypeEnum.sms : OTPTypeEnum.whatsapp); @@ -108,7 +114,7 @@ class _SavedLogin extends State { fontWeight: FontWeight.w500, borderRadius: 12, padding: EdgeInsets.fromLTRB(0, 10, 0, 10), - icon: AppAssets.apple_finder, + icon: AppAssets.sms, ), ), ], @@ -165,7 +171,7 @@ class _SavedLogin extends State { backgroundColor: AppColors.primaryRedColor, borderColor: AppColors.primaryRedBorderColor, textColor: AppColors.whiteColor, - icon: AppAssets.message, + icon: AppAssets.sms, ), ), Row( @@ -214,9 +220,11 @@ class _SavedLogin extends State { ) : CustomButton( text: "${LocaleKeys.loginBy.tr()} ${LoginTypeEnum.whatsapp.displayName}", + icon: AppAssets.whatsapp, + iconColor: null, onPressed: () { if (loginType == LoginTypeEnum.fingerprint || loginType == LoginTypeEnum.face) { - authVm.loginWithFingerPrintFace(loginType.toInt); + authVm.loginWithFingerPrintFace(); } else { loginType = LoginTypeEnum.whatsapp; int? val = loginType.toInt; @@ -228,7 +236,6 @@ class _SavedLogin extends State { textColor: Color(0xFF2E3039), borderWidth: 2, padding: EdgeInsets.fromLTRB(0, 14, 0, 14), - icon: AppAssets.whatsapp, ), const Spacer(flex: 2), // OR divider diff --git a/lib/presentation/home/landing_page.dart b/lib/presentation/home/landing_page.dart index d58d0c4..55afffe 100644 --- a/lib/presentation/home/landing_page.dart +++ b/lib/presentation/home/landing_page.dart @@ -39,9 +39,10 @@ class _LandingPageState extends State { @override void initState() { authVM = context.read(); + authVM.savePushTokenToAppState(); if (mounted) { authVM.checkLastLoginStatus(() { - // showQuickLogin(context, false); + showQuickLogin(context, false); }); } super.initState(); @@ -328,12 +329,15 @@ class _LandingPageState extends State { isDone: isDone, onPressed: () { // sharedPref.setBool(HAS_ENABLED_QUICK_LOGIN, true); - // loginWithFingerPrintFace(3, 1, user, deviceToken); + authVM.loginWithFingerPrintFace(); }, ), - height: 400, + height: isDone == false ? ResponsiveExtension.screenHeight * 0.5 : ResponsiveExtension.screenHeight * 0.3, isFullScreen: false, - callBackFunc: (str) {}, + callBackFunc: (str) { + isDone = true; + setState(() {}); + }, ); } } diff --git a/lib/presentation/insurance/widgets/insurance_update_details_card.dart b/lib/presentation/insurance/widgets/insurance_update_details_card.dart index 51afdc4..b6a38b2 100644 --- a/lib/presentation/insurance/widgets/insurance_update_details_card.dart +++ b/lib/presentation/insurance/widgets/insurance_update_details_card.dart @@ -10,6 +10,7 @@ import 'package:hmg_patient_app_new/features/insurance/insurance_view_model.dart import 'package:hmg_patient_app_new/generated/locale_keys.g.dart'; import 'package:hmg_patient_app_new/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/shimmer/movies_shimmer_widget.dart'; import 'package:provider/provider.dart'; @@ -68,42 +69,14 @@ class PatientInsuranceCardUpdateCard extends StatelessWidget { Wrap( direction: Axis.horizontal, spacing: 4.h, - runSpacing: 4.h, + runSpacing: -8.h, children: [ - Row( - children: [ - CustomButton( - icon: AppAssets.doctor_calendar_icon, - iconColor: AppColors.blackColor, - iconSize: 13.h, - text: "${LocaleKeys.expiryOn.tr(context: context)} ${insuranceViewModel.patientInsuranceUpdateResponseModel!.effectiveTo}", - onPressed: () {}, - backgroundColor: AppColors.greyColor, - borderColor: AppColors.greyColor, - textColor: AppColors.blackColor, - fontSize: 10, - fontWeight: FontWeight.w500, - borderRadius: 12, - padding: EdgeInsets.fromLTRB(10, 0, 10, 0), - height: 30.h, - ), - ], + AppCustomChipWidget( + icon: AppAssets.doctor_calendar_icon, + labelText: "${LocaleKeys.expiryOn.tr(context: context)} ${insuranceViewModel.patientInsuranceUpdateResponseModel!.effectiveTo}", ), - Row( - children: [ - CustomButton( - text: "Member ID: ${insuranceViewModel.patientInsuranceUpdateResponseModel!.memberID!}", - onPressed: () {}, - backgroundColor: AppColors.greyColor, - borderColor: AppColors.greyColor, - textColor: AppColors.blackColor, - fontSize: 10, - fontWeight: FontWeight.w500, - borderRadius: 12, - padding: EdgeInsets.fromLTRB(10, 0, 10, 0), - height: 30.h, - ), - ], + AppCustomChipWidget( + labelText: "Member ID: ${insuranceViewModel.patientInsuranceUpdateResponseModel!.memberID!}", ), ], ), diff --git a/lib/presentation/insurance/widgets/patient_insurance_card.dart b/lib/presentation/insurance/widgets/patient_insurance_card.dart index 899016e..720c452 100644 --- a/lib/presentation/insurance/widgets/patient_insurance_card.dart +++ b/lib/presentation/insurance/widgets/patient_insurance_card.dart @@ -13,6 +13,7 @@ import 'package:hmg_patient_app_new/generated/locale_keys.g.dart'; import 'package:hmg_patient_app_new/presentation/insurance/widgets/insurance_update_details_card.dart'; import 'package:hmg_patient_app_new/theme/colors.dart'; import 'package:hmg_patient_app_new/widgets/buttons/custom_button.dart'; +import 'package:hmg_patient_app_new/widgets/chip/app_custom_chip_widget.dart'; import 'package:hmg_patient_app_new/widgets/common_bottom_sheet.dart'; import 'package:provider/provider.dart'; @@ -73,44 +74,13 @@ class PatientInsuranceCard extends StatelessWidget { SizedBox(height: 8.h), Wrap( direction: Axis.horizontal, - spacing: 6.h, - runSpacing: 6.h, + spacing: 4.h, + runSpacing: -8.h, children: [ - Row( - children: [ - CustomButton( - icon: AppAssets.doctor_calendar_icon, - iconColor: AppColors.blackColor, - iconSize: 13.h, - text: "${LocaleKeys.expiryDate.tr(context: context)} ${DateUtil.formatDateToDate(DateUtil.convertStringToDate(insuranceCardDetailsModel.cardValidTo), false)}", - onPressed: () {}, - backgroundColor: AppColors.greyColor, - borderColor: AppColors.greyColor, - textColor: AppColors.blackColor, - fontSize: 10, - fontWeight: FontWeight.w500, - borderRadius: 12, - padding: EdgeInsets.fromLTRB(10, 0, 10, 0), - height: 30.h, - ), - ], - ), - Row( - children: [ - CustomButton( - text: "Patient Card ID: ${insuranceCardDetailsModel.patientCardID}", - onPressed: () {}, - backgroundColor: AppColors.greyColor, - borderColor: AppColors.greyColor, - textColor: AppColors.blackColor, - fontSize: 10, - fontWeight: FontWeight.w500, - borderRadius: 12, - padding: EdgeInsets.fromLTRB(10, 0, 10, 0), - height: 30.h, - ), - ], - ), + AppCustomChipWidget( + icon: AppAssets.doctor_calendar_icon, + labelText: "${LocaleKeys.expiryDate.tr(context: context)} ${DateUtil.formatDateToDate(DateUtil.convertStringToDate(insuranceCardDetailsModel.cardValidTo), false)}"), + AppCustomChipWidget(labelText: "Patient Card ID: ${insuranceCardDetailsModel.patientCardID}"), ], ), SizedBox(height: 10.h), diff --git a/lib/presentation/medical_file/medical_file_page.dart b/lib/presentation/medical_file/medical_file_page.dart index d6cbd14..47951c4 100644 --- a/lib/presentation/medical_file/medical_file_page.dart +++ b/lib/presentation/medical_file/medical_file_page.dart @@ -167,22 +167,22 @@ class _MedicalFilePageState extends State { padding: EdgeInsets.fromLTRB(10, 0, 10, 0), height: 30.h, ), - SizedBox(width: 4.h), - CustomButton( - icon: AppAssets.insurance_active_icon, - iconColor: AppColors.successColor, - iconSize: 13.h, - text: "Insurance Active", - onPressed: () {}, - backgroundColor: AppColors.bgGreenColor.withOpacity(0.20), - borderColor: AppColors.bgGreenColor.withOpacity(0.0), - textColor: AppColors.blackColor, - fontSize: 10, - fontWeight: FontWeight.normal, - borderRadius: 12, - padding: EdgeInsets.fromLTRB(10, 0, 10, 0), - height: 30.h, - ), + // SizedBox(width: 4.h), + // CustomButton( + // icon: AppAssets.insurance_active_icon, + // iconColor: AppColors.successColor, + // iconSize: 13.h, + // text: "Insurance Active", + // onPressed: () {}, + // backgroundColor: AppColors.bgGreenColor.withOpacity(0.20), + // borderColor: AppColors.bgGreenColor.withOpacity(0.0), + // textColor: AppColors.blackColor, + // fontSize: 10, + // fontWeight: FontWeight.normal, + // borderRadius: 12, + // padding: EdgeInsets.fromLTRB(10, 0, 10, 0), + // height: 30.h, + // ), ], ), SizedBox(height: 8.h), @@ -255,7 +255,7 @@ class _MedicalFilePageState extends State { ? const MoviesShimmerWidget().paddingSymmetrical(24.h, 0.0) : PatientInsuranceCard( insuranceCardDetailsModel: insuranceVM.patientInsuranceList.first, - isInsuranceExpired: DateTime.now().isBefore(DateUtil.convertStringToDate(insuranceVM.patientInsuranceList.first.cardValidTo))); + isInsuranceExpired: DateTime.now().isAfter(DateUtil.convertStringToDate(insuranceVM.patientInsuranceList.first.cardValidTo))); }), SizedBox(height: 10.h), GridView( @@ -264,10 +264,10 @@ class _MedicalFilePageState extends State { padding: EdgeInsets.only(top: 12), shrinkWrap: true, children: [ - MedicalFileCard(label: "Update Insurance", textColor: AppColors.blackColor, backgroundColor: AppColors.whiteColor, svgIcon: AppAssets.eye_result_icon), - MedicalFileCard(label: "Insurance Approvals", textColor: AppColors.blackColor, backgroundColor: AppColors.whiteColor, svgIcon: AppAssets.eye_result_icon), - MedicalFileCard(label: "My Invoices List", textColor: AppColors.blackColor, backgroundColor: AppColors.whiteColor, svgIcon: AppAssets.eye_result_icon), - MedicalFileCard(label: "Ancillary Orders List", textColor: AppColors.blackColor, backgroundColor: AppColors.whiteColor, svgIcon: AppAssets.eye_result_icon), + MedicalFileCard(label: "Update Insurance".needTranslation, textColor: AppColors.blackColor, backgroundColor: AppColors.whiteColor, svgIcon: AppAssets.eye_result_icon), + MedicalFileCard(label: "Insurance Approvals".needTranslation, textColor: AppColors.blackColor, backgroundColor: AppColors.whiteColor, svgIcon: AppAssets.eye_result_icon), + MedicalFileCard(label: "My Invoices List".needTranslation, textColor: AppColors.blackColor, backgroundColor: AppColors.whiteColor, svgIcon: AppAssets.eye_result_icon), + MedicalFileCard(label: "Ancillary Orders List".needTranslation, textColor: AppColors.blackColor, backgroundColor: AppColors.whiteColor, svgIcon: AppAssets.eye_result_icon), ], ).paddingSymmetrical(24.h, 0.0), SizedBox(height: 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 92bb4ee..82f38ec 100644 --- a/lib/presentation/medical_file/widgets/medical_file_card.dart +++ b/lib/presentation/medical_file/widgets/medical_file_card.dart @@ -6,18 +6,19 @@ import 'package:hmg_patient_app_new/extensions/widget_extensions.dart'; class MedicalFileCard extends StatelessWidget { final String label; - - // final Color svgColor; final Color textColor; final Color backgroundColor; final String svgIcon; + final num iconSize; + bool isLargeText; MedicalFileCard({ required this.label, - // required this.svgColor, required this.textColor, required this.backgroundColor, this.svgIcon = "", + this.iconSize = 30, + this.isLargeText = false }); @override @@ -33,9 +34,9 @@ class MedicalFileCard extends StatelessWidget { crossAxisAlignment: CrossAxisAlignment.start, mainAxisAlignment: MainAxisAlignment.center, children: [ - Utils.buildSvgWithAssets(icon: svgIcon, width: 30.h, height: 30.h, fit: BoxFit.contain), + Utils.buildSvgWithAssets(icon: svgIcon, width: iconSize.h, height: iconSize.h, fit: BoxFit.contain), SizedBox(height: 12.h), - label.toText11(color: textColor, isBold: true), + isLargeText ? label.toText14(color: textColor, isBold: true) : label.toText11(color: textColor, isBold: true), ], ), ), diff --git a/lib/presentation/prescriptions/prescription_detail_page.dart b/lib/presentation/prescriptions/prescription_detail_page.dart index 77fc273..8f17c54 100644 --- a/lib/presentation/prescriptions/prescription_detail_page.dart +++ b/lib/presentation/prescriptions/prescription_detail_page.dart @@ -17,6 +17,7 @@ import 'package:hmg_patient_app_new/presentation/prescriptions/prescription_item import 'package:hmg_patient_app_new/presentation/prescriptions/prescription_reminder_view.dart'; import 'package:hmg_patient_app_new/theme/colors.dart'; import 'package:hmg_patient_app_new/widgets/buttons/custom_button.dart'; +import 'package:hmg_patient_app_new/widgets/chip/app_custom_chip_widget.dart'; import 'package:hmg_patient_app_new/widgets/common_bottom_sheet.dart'; import 'package:hmg_patient_app_new/widgets/shimmer/movies_shimmer_widget.dart'; import 'package:provider/provider.dart'; @@ -41,6 +42,7 @@ class _PrescriptionDetailPageState extends State { // locationUtils = new LocationUtils(isShowConfirmDialog: true, context: context); // WidgetsBinding.instance.addPostFrameCallback((_) => locationUtils.getCurrentLocation()); scheduleMicrotask(() { + prescriptionsViewModel.setPrescriptionsDetailsLoading(); prescriptionsViewModel.getPrescriptionDetails(widget.prescriptionsResponseModel); }); super.initState(); @@ -92,82 +94,23 @@ class _PrescriptionDetailPageState extends State { SizedBox(height: 16.h), Wrap( direction: Axis.horizontal, - spacing: 6.h, - runSpacing: 6.h, + spacing: 4.h, + runSpacing: -8.h, children: [ - Row( - mainAxisSize: MainAxisSize.min, - children: [ - CustomButton( - icon: AppAssets.doctor_calendar_icon, - iconColor: AppColors.textColor, - iconSize: 13.h, - text: DateUtil.formatDateToDate(DateUtil.convertStringToDate(widget.prescriptionsResponseModel.appointmentDate), false), - onPressed: () {}, - backgroundColor: AppColors.greyColor, - borderColor: AppColors.greyColor, - textColor: AppColors.blackColor, - fontSize: 10, - fontWeight: FontWeight.w500, - borderRadius: 8, - padding: EdgeInsets.fromLTRB(10, 0, 10, 0), - height: 30.h, - ), - ], + AppCustomChipWidget( + icon: AppAssets.doctor_calendar_icon, + labelText: DateUtil.formatDateToDate(DateUtil.convertStringToDate(widget.prescriptionsResponseModel.appointmentDate), false), ), - Row( - mainAxisSize: MainAxisSize.min, - children: [ - CustomButton( - text: widget.prescriptionsResponseModel.clinicDescription!, - onPressed: () {}, - backgroundColor: AppColors.greyColor, - borderColor: AppColors.greyColor, - textColor: AppColors.blackColor, - fontSize: 10, - fontWeight: FontWeight.w500, - borderRadius: 8, - padding: EdgeInsets.fromLTRB(10, 0, 10, 0), - height: 30.h, - ), - ], + AppCustomChipWidget( + labelText: widget.prescriptionsResponseModel.clinicDescription!, ), - Row( - mainAxisSize: MainAxisSize.min, - children: [ - CustomButton( - icon: AppAssets.rating_icon, - iconColor: AppColors.ratingColorYellow, - iconSize: 13.h, - text: "Rating: ${widget.prescriptionsResponseModel.decimalDoctorRate}", - onPressed: () {}, - backgroundColor: AppColors.greyColor, - borderColor: AppColors.greyColor, - textColor: AppColors.blackColor, - fontSize: 10, - fontWeight: FontWeight.w500, - borderRadius: 8, - padding: EdgeInsets.fromLTRB(10, 0, 10, 0), - height: 30.h, - ), - ], + AppCustomChipWidget( + icon: AppAssets.rating_icon, + iconColor: AppColors.ratingColorYellow, + labelText: "Rating: ${widget.prescriptionsResponseModel.decimalDoctorRate}".needTranslation, ), - Row( - mainAxisSize: MainAxisSize.min, - children: [ - CustomButton( - text: widget.prescriptionsResponseModel.name!, - onPressed: () {}, - backgroundColor: AppColors.greyColor, - borderColor: AppColors.greyColor, - textColor: AppColors.blackColor, - fontSize: 10, - fontWeight: FontWeight.w500, - borderRadius: 8, - padding: EdgeInsets.fromLTRB(10, 0, 10, 0), - height: 30.h, - ), - ], + AppCustomChipWidget( + labelText: widget.prescriptionsResponseModel.name!, ), ], ), diff --git a/lib/routes/app_routes.dart b/lib/routes/app_routes.dart index 93146d6..67bf208 100644 --- a/lib/routes/app_routes.dart +++ b/lib/routes/app_routes.dart @@ -1,5 +1,7 @@ import 'package:flutter/material.dart'; import 'package:hmg_patient_app_new/presentation/authentication/login.dart'; +import 'package:hmg_patient_app_new/presentation/authentication/register.dart'; +import 'package:hmg_patient_app_new/presentation/authentication/register_step2.dart'; import 'package:hmg_patient_app_new/presentation/home/landing_page.dart'; import 'package:hmg_patient_app_new/presentation/home/navigation_screen.dart'; import 'package:hmg_patient_app_new/splashPage.dart'; @@ -7,12 +9,15 @@ import 'package:hmg_patient_app_new/splashPage.dart'; class AppRoutes { static const String initialRoute = '/initialRoute'; static const String loginScreen = '/loginScreen'; - static const String registerNewScreen = '/registerNewScreen'; + static const String register = '/register'; + static const String registerStepTwo = '/registerStepTwo'; static const String landingScreen = '/landingScreen'; static Map get routes => { initialRoute: (context) => SplashPage(), loginScreen: (context) => LoginScreen(), landingScreen: (context) => LandingNavigation(), + register: (context) => RegisterNew(), + registerStepTwo: (context) => RegisterNewStep2(), }; } diff --git a/lib/services/localauth_service.dart b/lib/services/localauth_service.dart index a01d1c1..2875a7f 100644 --- a/lib/services/localauth_service.dart +++ b/lib/services/localauth_service.dart @@ -6,22 +6,31 @@ class LocalAuthService { final LocalAuthentication localAuth; final LoggerService loggerService; LocalAuthService({required this.localAuth, required this.loggerService}); - Future authenticate() async { try { - bool isAuthenticated = await localAuth.authenticate( + final canCheck = await localAuth.canCheckBiometrics; + final isDeviceSupported = await localAuth.isDeviceSupported(); + + if (!canCheck || !isDeviceSupported) { + return false; + } + + final isAuthenticated = await localAuth.authenticate( localizedReason: 'Please authenticate to proceed', options: const AuthenticationOptions( biometricOnly: true, stickyAuth: true, ), ); + return isAuthenticated; } catch (e) { + print(e); return false; } } + Future canCheckBiometrics() async { try { return await localAuth.canCheckBiometrics; diff --git a/lib/splashPage.dart b/lib/splashPage.dart index b852cd4..f85f01e 100644 --- a/lib/splashPage.dart +++ b/lib/splashPage.dart @@ -9,6 +9,8 @@ import 'package:flutter_zoom_videosdk/native/zoom_videosdk.dart'; import 'package:hmg_patient_app_new/core/api_consts.dart'; import 'package:hmg_patient_app_new/core/app_assets.dart'; import 'package:hmg_patient_app_new/core/utils/utils.dart'; +import 'package:hmg_patient_app_new/features/authentication/authentication_view_model.dart'; + // import 'package:hmg_patient_app_new/presentation/authantication/login.dart'; import 'package:hmg_patient_app_new/presentation/home/landing_page.dart'; import 'package:hmg_patient_app_new/presentation/home/navigation_screen.dart'; @@ -26,6 +28,8 @@ class SplashPage extends StatefulWidget { } class _SplashScreenState extends State { + late AuthenticationViewModel authVm; + Future initializeStuff() async { Timer( Duration(milliseconds: 500), @@ -34,7 +38,8 @@ class _SplashScreenState extends State { PushNotificationHandler().init(context); // Asyncronously }, ); - Timer(Duration(seconds: 3, milliseconds: 500), () async { + await authVm.getServicePrivilege(); + Timer(Duration(seconds: 2, milliseconds: 500), () async { LocalNotification.init(onNotificationClick: (payload) {}); Navigator.of(context).pushReplacement( FadePage( @@ -51,8 +56,6 @@ class _SplashScreenState extends State { zoom.initSdk(initConfig); } - - /// load the Privilege from service Future loadPrivilege() async { // ProjectViewModel projectProvider = Provider.of(context, listen: false); @@ -71,9 +74,9 @@ class _SplashScreenState extends State { PushNotificationHandler().init(context); // Asyncronously } - @override void initState() { + authVm = context.read(); super.initState(); initializeStuff(); } @@ -87,8 +90,7 @@ class _SplashScreenState extends State { children: [ Padding( padding: EdgeInsets.symmetric(horizontal: 53), - child: Image.asset(AppAssets.hmg_logo, - fit: BoxFit.fitWidth, width: MediaQuery.of(context).size.width), + child: Image.asset(AppAssets.hmg_logo, fit: BoxFit.fitWidth, width: MediaQuery.of(context).size.width), ), Align( alignment: Alignment.bottomCenter, @@ -97,18 +99,12 @@ class _SplashScreenState extends State { children: [ Text( "Powered by", - style: TextStyle( - fontSize: 14, - fontWeight: FontWeight.w400, - color: AppColors.textColor, - letterSpacing: -0.56, - height: 16 / 14), + style: TextStyle(fontSize: 14, fontWeight: FontWeight.w400, color: AppColors.textColor, letterSpacing: -0.56, height: 16 / 14), ), SizedBox( height: 5, ), - Utils.buildSvgWithAssets( - icon: AppAssets.cloud_logo, width: 40, height: 40), + Utils.buildSvgWithAssets(icon: AppAssets.cloud_logo, width: 40, height: 40), SizedBox(height: 7), // Text( // "Version 1.1.0", diff --git a/lib/widgets/common_bottom_sheet.dart b/lib/widgets/common_bottom_sheet.dart index 9652265..f156fe4 100644 --- a/lib/widgets/common_bottom_sheet.dart +++ b/lib/widgets/common_bottom_sheet.dart @@ -54,7 +54,7 @@ class ButtonSheetContent extends StatelessWidget { @override Widget build(BuildContext context) { return Column( - crossAxisAlignment: CrossAxisAlignment.start, + crossAxisAlignment: CrossAxisAlignment.center, children: [ // SizedBox( // height: 20.h, diff --git a/lib/widgets/dropdown/country_dropdown_widget.dart b/lib/widgets/dropdown/country_dropdown_widget.dart index 3d28e23..4e41c31 100644 --- a/lib/widgets/dropdown/country_dropdown_widget.dart +++ b/lib/widgets/dropdown/country_dropdown_widget.dart @@ -105,7 +105,7 @@ class _CustomCountryDropdownState extends State { SizedBox(width: 4.h), if (widget.isEnableTextField) SizedBox( - height: 20, + height: 18, width: 200, child: TextField( focusNode: textFocusNode, diff --git a/lib/widgets/nfc/nfc_reader_sheet.dart b/lib/widgets/nfc/nfc_reader_sheet.dart new file mode 100644 index 0000000..b76b36f --- /dev/null +++ b/lib/widgets/nfc/nfc_reader_sheet.dart @@ -0,0 +1,212 @@ +import 'dart:io'; + +import 'package:easy_localization/easy_localization.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter_nfc_kit/flutter_nfc_kit.dart'; +import 'package:flutter_svg/flutter_svg.dart'; +import 'package:hmg_patient_app_new/core/app_assets.dart'; +import 'package:hmg_patient_app_new/core/utils/size_utils.dart'; +import 'package:hmg_patient_app_new/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'; + +void showNfcReader(BuildContext context, {required Function onNcfScan, required VoidCallback onCancel}) { + showModalBottomSheet( + context: context, + enableDrag: false, + isDismissible: true, + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.only(topLeft: Radius.circular(12), topRight: Radius.circular(12)), + ), + backgroundColor: Colors.white, + builder: (context) { + return NfcLayout( + onNcfScan: onNcfScan, + onCancel: onCancel, + ); + }); +} + +class NfcLayout extends StatefulWidget { + Function? onNcfScan; + VoidCallback? onCancel; + + NfcLayout({this.onNcfScan, this.onCancel}); + + @override + _NfcLayoutState createState() => _NfcLayoutState(); +} + +class _NfcLayoutState extends State { + bool _reading = false; + Widget? mainWidget; + late String nfcId; + + @override + void initState() { + super.initState(); + readNFC(); + } + + void readNFC() async { + FlutterNfcKit.finish(); + FlutterNfcKit.poll(timeout: Duration(seconds: 10), androidPlatformSound: true, androidCheckNDEF: false, iosMultipleTagMessage: "Multiple tags found!").then((value) async { + setState(() { + _reading = true; + mainWidget = doneNfc(); + }); + Future.delayed(const Duration(milliseconds: 500), () async { + await FlutterNfcKit.finish(); + widget.onNcfScan!(nfcId); + Navigator.pop(context); + }); + nfcId = value.id; + }).catchError((err) { + print(err); + Navigator.of(context).pop(); + }); + } + + @override + Widget build(BuildContext context) { + // return SizedBox(); + (mainWidget == null && !_reading) ? mainWidget = scanNfc() : mainWidget = doneNfc(); + return Platform.isAndroid ? AnimatedSwitcher(duration: Duration(milliseconds: 500), child: mainWidget) : SizedBox.shrink(); + } + + Widget scanNfc() { + return Container( + key: ValueKey(1), + child: Column( + mainAxisSize: MainAxisSize.min, + children: [ + SizedBox( + height: 30, + ), + Text( + "Ready To Scan", + style: TextStyle( + fontWeight: FontWeight.bold, + fontSize: 24, + ), + ), + SizedBox( + height: 30, + ), + SvgPicture.asset( + "assets/images/nfc/contactless.svg", + height: MediaQuery.of(context).size.width / 3, + ), + SizedBox( + height: 30, + ), + Text( + "Approach an NFC Tag", + style: TextStyle( + fontSize: 18, + ), + ), + SizedBox( + height: 30, + ), + ButtonTheme( + minWidth: MediaQuery.of(context).size.width / 1.2, + height: 45.0, + buttonColor: Colors.grey[300], + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(6), + ), + child: CustomButton( + text: LocaleKeys.cancel.tr(), + onPressed: () { + widget.onCancel!(); + Navigator.pop(context); + }, + backgroundColor: AppColors.primaryRedColor, + borderColor: AppColors.primaryRedColor, + textColor: AppColors.whiteColor, + fontSize: 14, + fontWeight: FontWeight.w500, + borderRadius: 12.h, + height: 40.h, + icon: AppAssets.cancel, + iconColor: AppColors.whiteColor, + iconSize: 16.h, + ), + ), + SizedBox( + height: 30, + ), + ], + ), + ); + } + + Widget doneNfc() { + return Container( + key: ValueKey(2), + child: Column( + mainAxisSize: MainAxisSize.min, + children: [ + SizedBox( + height: 30, + ), + Text( + "Successfully Scanned", + style: TextStyle( + fontWeight: FontWeight.bold, + fontSize: 24, + ), + ), + SizedBox( + height: 30, + ), + Image.asset( + "assets/images/nfc/ic_done.png", + height: MediaQuery.of(context).size.width / 3, + ), + SizedBox( + height: 30, + ), + Text( + "Approach an NFC Tag", + style: TextStyle( + fontSize: 18, + ), + ), + SizedBox( + height: 30, + ), + ButtonTheme( + minWidth: MediaQuery.of(context).size.width / 1.2, + height: 45.0, + buttonColor: Colors.grey[300], + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(6), + ), + child: CustomButton( + text: LocaleKeys.done.tr(), + onPressed: () { + widget.onCancel!(); + Navigator.pop(context); + }, + backgroundColor: AppColors.primaryRedColor, + borderColor: AppColors.primaryRedColor, + textColor: AppColors.whiteColor, + fontSize: 14, + fontWeight: FontWeight.w500, + borderRadius: 12.h, + height: 40.h, + icon: AppAssets.cancel, + iconColor: AppColors.whiteColor, + iconSize: 16.h, + ), + ), + SizedBox( + height: 30, + ), + ], + ), + ); + } +} diff --git a/pubspec.yaml b/pubspec.yaml index 7536aaa..afbac11 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -73,6 +73,8 @@ dependencies: maps_launcher: ^3.0.0+1 amazon_payfort: ^1.1.4 network_info_plus: ^6.1.4 + flutter_nfc_kit: ^3.6.0 + barcode_scan2: ^4.5.1 dev_dependencies: flutter_test: