diff --git a/android/app/src/main/AndroidManifest.xml b/android/app/src/main/AndroidManifest.xml index 738ab0c..2ff0127 100644 --- a/android/app/src/main/AndroidManifest.xml +++ b/android/app/src/main/AndroidManifest.xml @@ -1,4 +1,6 @@ + + @@ -24,7 +26,7 @@ android:name="${applicationName}" android:icon="@mipmap/launcher_icon"> + android:value="AIzaSyDI8b-PUgKUgj5rHdtgEHCwWjUXYJrqYhE"/> 0) {\n $bm_rt = nearestKeyIndex = nearestKey(time).index;\n if (key(nearestKeyIndex).time > time) {\n nearestKeyIndex--;\n }\n}\nif (nearestKeyIndex == 0) {\n $bm_rt = currentTime = 0;\n} else {\n $bm_rt = currentTime = sub(time, key(nearestKeyIndex).time);\n}\nif (nearestKeyIndex > 0 && currentTime < 1) {\n calculatedVelocity = velocityAtTime(sub(key(nearestKeyIndex).time, div(thisComp.frameDuration, 10)));\n amplitude = 0.1;\n frequency = 1.8;\n decay = 10;\n $bm_rt = sum(value, div(mul(mul(calculatedVelocity, amplitude), Math.sin(mul(mul(mul(frequency, currentTime), 2), Math.PI))), Math.exp(mul(decay, currentTime))));\n} else {\n $bm_rt = value;\n}"},"a":{"a":0,"k":[-32.5,5,0],"ix":1},"s":{"a":0,"k":[100,100,100],"ix":6}},"ao":0,"shapes":[{"ty":"gr","it":[{"d":1,"ty":"el","s":{"a":0,"k":[15,15],"ix":2},"p":{"a":0,"k":[0,0],"ix":3},"nm":"Ellipse Path 1","mn":"ADBE Vector Shape - Ellipse","hd":false},{"ty":"st","c":{"a":0,"k":[1,1,1,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":0,"ix":5},"lc":1,"lj":1,"ml":4,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"fl","c":{"a":0,"k":[0.870588235294,0.874509803922,0.878431372549,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[-32.618,4.916],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Ellipse 1","np":3,"cix":2,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":3.00000012219251,"st":0,"bm":0},{"ddd":0,"ind":4,"ty":4,"nm":"Shape Layer 2","sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[121.75,53.25,0],"ix":2,"x":"var $bm_rt;\nvar nearestKeyIndex, nearestKeyIndex, currentTime, currentTime, calculatedVelocity, amplitude, frequency, decay;\n$bm_rt = nearestKeyIndex = 0;\nif (numKeys > 0) {\n $bm_rt = nearestKeyIndex = nearestKey(time).index;\n if (key(nearestKeyIndex).time > time) {\n nearestKeyIndex--;\n }\n}\nif (nearestKeyIndex == 0) {\n $bm_rt = currentTime = 0;\n} else {\n $bm_rt = currentTime = sub(time, key(nearestKeyIndex).time);\n}\nif (nearestKeyIndex > 0 && currentTime < 1) {\n calculatedVelocity = velocityAtTime(sub(key(nearestKeyIndex).time, div(thisComp.frameDuration, 10)));\n amplitude = 0.1;\n frequency = 1.8;\n decay = 10;\n $bm_rt = sum(value, div(mul(mul(calculatedVelocity, amplitude), Math.sin(mul(mul(mul(frequency, currentTime), 2), Math.PI))), Math.exp(mul(decay, currentTime))));\n} else {\n $bm_rt = value;\n}"},"a":{"a":0,"k":[-32.5,5,0],"ix":1},"s":{"a":0,"k":[100,100,100],"ix":6}},"ao":0,"shapes":[{"ty":"gr","it":[{"d":1,"ty":"el","s":{"a":0,"k":[15,15],"ix":2},"p":{"a":0,"k":[0,0],"ix":3},"nm":"Ellipse Path 1","mn":"ADBE Vector Shape - Ellipse","hd":false},{"ty":"st","c":{"a":0,"k":[1,1,1,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":0,"ix":5},"lc":1,"lj":1,"ml":4,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"fl","c":{"a":0,"k":[0.870588235294,0.874509803922,0.878431372549,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[-32.618,4.916],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Ellipse 1","np":3,"cix":2,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":6.00000024438501,"st":0,"bm":0},{"ddd":0,"ind":5,"ty":0,"nm":"单个点","refId":"comp_0","sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[99.75,38.5,0],"ix":2},"a":{"a":0,"k":[50,50,0],"ix":1},"s":{"a":0,"k":[100,100,100],"ix":6}},"ao":0,"w":100,"h":100,"ip":4.00000016292334,"op":94.0000038286985,"st":4.00000016292334,"bm":0}],"markers":[]} \ No newline at end of file diff --git a/assets/animations/loading.json b/assets/animations/loading.json new file mode 100644 index 0000000..419f71b --- /dev/null +++ b/assets/animations/loading.json @@ -0,0 +1 @@ +{"v":"4.10.1","fr":24,"ip":0,"op":72,"w":400,"h":400,"nm":"Comp 1","ddd":0,"assets":[{"id":"comp_0","layers":[{"ddd":0,"ind":1,"ty":4,"nm":"Capa de formas 12","sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":1,"k":[{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"n":"0p833_0p833_0p167_0p167","t":24,"s":[81.5,370.25,0],"e":[445.5,199.25,0],"to":[60.6666679382324,-28.5,0],"ti":[-60.6666679382324,28.5,0]},{"t":48}],"ix":2},"a":{"a":0,"k":[0,0,0],"ix":1},"s":{"a":0,"k":[43,43,100],"ix":6}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[28,0],[34.935,-19.483],[31.619,18.821],[33,-14],[57,29],[0,0],[0,0],[0,0]],"o":[[-28,0],[-52,29],[-42,-25],[-28.892,12.257],[-57,-29],[0,0],[0,0],[0,0]],"v":[[367.75,-97],[277,-75],[155,-82],[35,-82],[-94,-82.326],[-200,-74],[-352.07,320.209],[499.162,354.093]],"c":true},"ix":2},"nm":"Trazado 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"fl","c":{"a":0,"k":[0.2980392156862745,0.7333333333333333,0.6313725490196078,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"nm":"Relleno 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transformar"}],"nm":"Forma 1","np":3,"cix":2,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":144,"st":0,"bm":0},{"ddd":0,"ind":2,"ty":4,"nm":"Capa de formas 2","sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":1,"k":[{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"n":"0p833_0p833_0p167_0p167","t":24,"s":[-133,374,0],"e":[231,203,0],"to":[60.6666679382324,-28.5,0],"ti":[-60.6666679382324,28.5,0]},{"t":48}],"ix":2},"a":{"a":0,"k":[0,0,0],"ix":1},"s":{"a":0,"k":[43,43,100],"ix":6}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[28,0],[34.935,-19.483],[31.619,18.821],[33,-14],[57,29],[0,0],[0,0],[0,0]],"o":[[-28,0],[-52,29],[-42,-25],[-28.892,12.257],[-57,-29],[0,0],[0,0],[0,0]],"v":[[367.75,-97],[277,-75],[155,-82],[35,-82],[-94,-82.326],[-200,-74],[-352.07,320.209],[499.162,354.093]],"c":true},"ix":2},"nm":"Trazado 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"fl","c":{"a":0,"k":[0.3137254901960784,0.8901960784313725,0.7607843137254902,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"nm":"Relleno 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transformar"}],"nm":"Forma 1","np":3,"cix":2,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":144,"st":0,"bm":0}]}],"layers":[{"ddd":0,"ind":1,"ty":4,"nm":"Capa de formas 5","sr":1,"ks":{"o":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"n":["0p833_0p833_0p167_0p167"],"t":15,"s":[100],"e":[0]},{"t":16}],"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":1,"k":[{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"n":"0p833_0p833_0p167_0p167","t":0,"s":[199,-14.000000000000002,0],"e":[199,156,0],"to":[0,28.3333339691162,0],"ti":[0,-28.9375,0]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"n":"0p833_0p833_0p167_0p167","t":12,"s":[199,156,0],"e":[199,164.066,0],"to":[0,4.54861259460449,0],"ti":[0,-2.45892143249512,0]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"n":"0p833_0p833_0p167_0p167","t":13,"s":[199,164.066,0],"e":[199,166.125,0],"to":[0,13.1843204498291,0],"ti":[0,-1.72074222564697,0]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"n":"0p833_0p833_0p167_0p167","t":14,"s":[199,166.125,0],"e":[199,168.375,0],"to":[0,2.04166674613953,0],"ti":[0,-0.04166666790843,0]},{"t":15}],"ix":2},"a":{"a":0,"k":[-1,-182.375,0],"ix":1},"s":{"a":1,"k":[{"i":{"x":[0.833,0.833,0.833],"y":[0.833,0.833,0.833]},"o":{"x":[0.167,0.167,0.167],"y":[0.167,0.167,0.167]},"n":["0p833_0p833_0p167_0p167","0p833_0p833_0p167_0p167","0p833_0p833_0p167_0p167"],"t":0,"s":[50,50,100],"e":[50,94,100]},{"i":{"x":[0.833,0.833,0.833],"y":[0.833,0.833,0.833]},"o":{"x":[0.167,0.167,0.167],"y":[0.167,0.167,0.167]},"n":["0p833_0p833_0p167_0p167","0p833_0p833_0p167_0p167","0p833_0p833_0p167_0p167"],"t":12,"s":[50,94,100],"e":[70,43.333,100]},{"i":{"x":[0.833,0.833,0.833],"y":[0.833,0.833,0.833]},"o":{"x":[0.167,0.167,0.167],"y":[0.167,0.167,0.167]},"n":["0p833_0p833_0p167_0p167","0p833_0p833_0p167_0p167","0p833_0p833_0p167_0p167"],"t":13,"s":[70,43.333,100],"e":[104.25800000000001,32,100]},{"i":{"x":[0.833,0.833,0.833],"y":[0.833,0.833,0.833]},"o":{"x":[0.167,0.167,0.167],"y":[0.167,0.167,0.167]},"n":["0p833_0p833_0p167_0p167","0p833_0p833_0p167_0p167","0p833_0p833_0p167_0p167"],"t":14,"s":[104.25800000000001,32,100],"e":[212,18,100]},{"t":15}],"ix":6}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0.938,0],[0,-5.25],[-4.563,0.125],[0.108,4.624]],"o":[[-0.813,0.125],[0,4.813],[4.563,-0.125],[-0.125,-5.375]],"v":[[-1.344,-193.078],[-8.75,-180.5],[-1.063,-172.313],[6.938,-180.188]],"c":true},"ix":2},"nm":"Trazado 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"fl","c":{"a":0,"k":[0.2980392156862745,0.7333333333333333,0.6313725490196078,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"nm":"Relleno 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transformar"}],"nm":"Forma 1","np":3,"cix":2,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":60,"st":0,"bm":0},{"ddd":0,"ind":8,"ty":4,"nm":"Capa de formas 3","sr":1,"ks":{"o":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"n":["0p833_0p833_0p167_0p167"],"t":46,"s":[0],"e":[100]},{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"n":["0p833_0p833_0p167_0p167"],"t":47,"s":[100],"e":[100]},{"t":48}],"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":1,"k":[{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"n":"0p833_0p833_0p167_0p167","t":47,"s":[199.98,168.25,0],"e":[199.98,158.037,0],"to":[0,-0.20375619828701,0],"ti":[0,17.58864402771,0]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"n":"0p833_0p833_0p167_0p167","t":48,"s":[199.98,158.037,0],"e":[199.98,-10,0],"to":[-2.8421709430404e-14,-50.4047393798828,0],"ti":[0,1.17485654354095,0]},{"t":53}],"ix":2},"a":{"a":0,"k":[-32,-31,0],"ix":1},"s":{"a":1,"k":[{"i":{"x":[0.833,0.833,0.833],"y":[0.833,0.833,0.833]},"o":{"x":[0.167,0.167,0.167],"y":[0.167,0.167,0.167]},"n":["0p833_0p833_0p167_0p167","0p833_0p833_0p167_0p167","0p833_0p833_0p167_0p167"],"t":47,"s":[-4,1,100],"e":[1.5,4,100]},{"i":{"x":[0.833,0.833,0.833],"y":[0.833,0.833,0.833]},"o":{"x":[0.167,0.167,0.167],"y":[0.167,0.167,0.167]},"n":["0p833_0p833_0p167_0p167","0p833_0p833_0p167_0p167","0p833_0p833_0p167_0p167"],"t":48,"s":[1.5,4,100],"e":[2,3,100]},{"t":53}],"ix":6}},"ao":0,"shapes":[{"ty":"gr","it":[{"d":1,"ty":"el","s":{"a":0,"k":[308,308],"ix":2},"p":{"a":0,"k":[0,0],"ix":3},"nm":"Trazado elíptico 1","mn":"ADBE Vector Shape - Ellipse","hd":false},{"ty":"fl","c":{"a":0,"k":[0.2980392156862745,0.7333333333333333,0.6313725490196078,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"nm":"Relleno 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[-31,-31],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transformar"}],"nm":"Elipse 1","np":3,"cix":2,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":60,"st":0,"bm":0},{"ddd":0,"ind":9,"ty":4,"nm":"Capa de formas 4","sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[199,252.99999999999997,0],"ix":2},"a":{"a":0,"k":[-32,-31,0],"ix":1},"s":{"a":0,"k":[55.00000000000001,55.00000000000001,100],"ix":6}},"ao":0,"shapes":[{"ty":"gr","it":[{"d":1,"ty":"el","s":{"a":0,"k":[308,308],"ix":2},"p":{"a":0,"k":[0,0],"ix":3},"nm":"Trazado elíptico 1","mn":"ADBE Vector Shape - Ellipse","hd":false},{"ty":"st","c":{"a":0,"k":[0.2980392156862745,0.7333333333333333,0.6313725490196078,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":10,"ix":5},"lc":1,"lj":1,"ml":4,"nm":"Trazo 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"tr","p":{"a":0,"k":[-31,-31],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transformar"}],"nm":"Elipse 1","np":3,"cix":2,"ix":1,"mn":"ADBE Vector Group","hd":false},{"ty":"tm","s":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"n":["0p833_0p833_0p167_0p167"],"t":12,"s":[50],"e":[100]},{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"n":["0p833_0p833_0p167_0p167"],"t":24,"s":[100],"e":[50]},{"t":48}],"ix":1},"e":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"n":["0p833_0p833_0p167_0p167"],"t":12,"s":[50],"e":[0]},{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"n":["0p833_0p833_0p167_0p167"],"t":24,"s":[0],"e":[50]},{"t":48}],"ix":2},"o":{"a":0,"k":180,"ix":3},"m":1,"ix":2,"nm":"Recortar trazados 1","mn":"ADBE Vector Filter - Trim","hd":false}],"ip":0,"op":60,"st":0,"bm":0},{"ddd":0,"ind":10,"ty":4,"nm":"Capa de formas 1","td":1,"sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[199,252.99999999999997,0],"ix":2},"a":{"a":0,"k":[-32,-31,0],"ix":1},"s":{"a":0,"k":[50,50,100],"ix":6}},"ao":0,"shapes":[{"ty":"gr","it":[{"d":1,"ty":"el","s":{"a":0,"k":[308,308],"ix":2},"p":{"a":0,"k":[0,0],"ix":3},"nm":"Trazado elíptico 1","mn":"ADBE Vector Shape - Ellipse","hd":false},{"ty":"fl","c":{"a":0,"k":[0.2980392156862745,0.7333333333333333,0.6313725490196078,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"nm":"Relleno 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[-31,-31],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transformar"}],"nm":"Elipse 1","np":3,"cix":2,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":60,"st":0,"bm":0},{"ddd":0,"ind":11,"ty":0,"nm":"Precomp. 1","tt":1,"refId":"comp_0","sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[200,200,0],"ix":2},"a":{"a":0,"k":[200,200,0],"ix":1},"s":{"a":0,"k":[100,100,100],"ix":6}},"ao":0,"w":400,"h":400,"ip":0,"op":144,"st":0,"bm":0}]} \ No newline at end of file diff --git a/lib/bloc/device_detail_bloc.dart b/lib/bloc/device_detail_bloc.dart index ee247e3..a906dd4 100644 --- a/lib/bloc/device_detail_bloc.dart +++ b/lib/bloc/device_detail_bloc.dart @@ -9,6 +9,7 @@ import 'package:google_maps_flutter/google_maps_flutter.dart'; import 'package:sfm_app/product/services/api_services.dart'; import 'package:sfm_app/product/utils/device_utils.dart'; +import '../product/shared/shared_snack_bar.dart'; import '../product/utils/date_time_utils.dart'; import '../feature/device_log/device_logs_model.dart'; import '../feature/devices/device_model.dart'; @@ -42,14 +43,12 @@ class DetailDeviceBloc extends BlocBase { String thingID, Completer controller, ) async { - String body = await apiServices.getDeviceInfomation(thingID); - if (body != "") { - final data = jsonDecode(body); - Device device = Device.fromJson(data); + try { + Device device = await apiServices.getDeviceInformation(thingID); sinkDeviceInfo.add(device); if (device.areaPath != null) { String fullLocation = await DeviceUtils.instance - .getFullDeviceLocation(context, device.areaPath!); + .getFullDeviceLocation(context, device.areaPath!, ""); log("Location: $fullLocation"); sinkDeviceLocation.add(fullLocation); } @@ -74,40 +73,46 @@ class DetailDeviceBloc extends BlocBase { mapController .animateCamera(CameraUpdate.newCameraPosition(cameraPosition)); } + } catch (e) { + if (!context.mounted) return; + showErrorTopSnackBarCustom(context, e.toString()); } } void findLocation(BuildContext context, String areaPath) async { String fullLocation = - await DeviceUtils.instance.getFullDeviceLocation(context, areaPath); + await DeviceUtils.instance.getFullDeviceLocation(context, areaPath, ""); sinkDeviceLocation.add(fullLocation); } - void getNearerSensorValue(String thingID) async { - List sensorTemps = []; - DateTime twoDaysAgo = DateTime.now().subtract(const Duration(days: 2)); - String from = DateTimeUtils.instance.formatDateTimeToString(twoDaysAgo); - String now = DateTimeUtils.instance.formatDateTimeToString(DateTime.now()); - Map params = { - 'thing_id': thingID, - 'from': from, - 'to': now, - 'limit': '100', - 'n': '7', - }; - final body = await apiServices.getLogsOfDevice(thingID, params); - if (body != "") { - final data = jsonDecode(body); - DeviceLog devicesListLog = DeviceLog.fromJson(data); + void getNearerSensorValue(BuildContext context,String thingID) async { + try { + List sensorTemps = []; + DateTime twoDaysAgo = DateTime.now().subtract(const Duration(days: 2)); + String from = DateTimeUtils.instance.formatDateTimeToString(twoDaysAgo); + String now = DateTimeUtils.instance.formatDateTimeToString(DateTime.now()); + Map params = { + 'thing_id': thingID, + 'from': from, + 'to': now, + 'limit': '100', + 'n': '7', + }; + DeviceLog devicesListLog = await apiServices.getLogsOfDevice(thingID, params); if (devicesListLog.sensors!.isNotEmpty) { for (var sensor in devicesListLog.sensors!) { sensorTemps.add(sensor); } sensorTemps = sensorTemps.reversed.toList(); sinkSensorTemps.add(sensorTemps); - } else{ + } else { sinkSensorTemps.add([]); } + } catch (e) { + if (!context.mounted) return; + showErrorTopSnackBarCustom( + context, e.toString()); + sinkSensorTemps.add([]); } } } diff --git a/lib/bloc/device_logs_bloc.dart b/lib/bloc/device_logs_bloc.dart index 2f586a5..4b32a32 100644 --- a/lib/bloc/device_logs_bloc.dart +++ b/lib/bloc/device_logs_bloc.dart @@ -1,10 +1,13 @@ import 'dart:async'; import 'dart:convert'; +import 'package:flutter/material.dart'; + import '../feature/devices/device_model.dart'; import '../product/base/bloc/base_bloc.dart'; import '../product/constant/app/app_constants.dart'; import '../product/services/api_services.dart'; +import '../product/shared/shared_snack_bar.dart'; import '../product/utils/date_time_utils.dart'; import '../product/utils/device_utils.dart'; @@ -35,40 +38,41 @@ class DeviceLogsBloc extends BlocBase { @override void dispose() {} - void getAllDevices() async { - String body = await apiServices.getOwnerDevices(); - if (body != "") { - final data = jsonDecode(body); - List items = data['items']; - List originalDevices = Device.fromJsonDynamicList(items); + void getAllDevices(BuildContext context) async { + try { + List originalDevices = await apiServices.getOwnerDevices(); List devices = DeviceUtils.instance.sortDeviceByState(originalDevices); sinkAllDevices.add(devices); + } catch (e) { + if (!context.mounted) return; + showErrorTopSnackBarCustom(context, e.toString()); } } void getDeviceLogByThingID( + BuildContext context, int offset, String thingID, DateTime fromDate, List sensors, ) async { - sinkmessage.add(ApplicationConstants.LOADING); - String fromDateString = - DateTimeUtils.instance.formatDateTimeToString(fromDate); - String now = DateTimeUtils.instance.formatDateTimeToString(DateTime.now()); - Map params = { - 'thing_id': thingID, - 'from': fromDateString, - 'to': now, - 'limit': '30', - "offset": offset.toString(), - "asc": "true" - }; - final body = await apiServices.getLogsOfDevice(thingID, params); - if (body != "") { - final data = jsonDecode(body); - DeviceLog devicesListLog = DeviceLog.fromJson(data); + try { + sinkmessage.add(ApplicationConstants.LOADING); + String fromDateString = + DateTimeUtils.instance.formatDateTimeToString(fromDate); + String now = + DateTimeUtils.instance.formatDateTimeToString(DateTime.now()); + Map params = { + 'thing_id': thingID, + 'from': fromDateString, + 'to': now, + 'limit': '30', + "offset": offset.toString(), + "asc": "true" + }; + DeviceLog devicesListLog = + await apiServices.getLogsOfDevice(thingID, params); if (devicesListLog.sensors!.isEmpty) { bool hasMore = false; sinkHasMore.add(hasMore); @@ -81,6 +85,9 @@ class DeviceLogsBloc extends BlocBase { sinkmessage.add(ApplicationConstants.NO_DATA); } sinkSensors.add(sensors); + } catch (e) { + if (!context.mounted) return; + showErrorTopSnackBarCustom(context, e.toString()); } } } diff --git a/lib/bloc/device_update_bloc.dart b/lib/bloc/device_update_bloc.dart index 66aab6a..90441e0 100644 --- a/lib/bloc/device_update_bloc.dart +++ b/lib/bloc/device_update_bloc.dart @@ -9,6 +9,7 @@ import 'package:intl/intl.dart'; import '../product/services/api_services.dart'; import '../product/services/language_services.dart'; import '../product/shared/model/ward_model.dart'; +import '../product/shared/shared_snack_bar.dart'; import '../product/utils/response_status_utils.dart'; import '../product/shared/model/district_model.dart'; @@ -75,151 +76,210 @@ class DeviceUpdateBloc extends BlocBase { // deviceInfo.done; } - Future getAllProvinces() async { + Future getAllProvinces(BuildContext context) async { List> provincesData = []; provincesData.clear(); sinkListProvinces.add(provincesData); - final body = await apiServices.getAllProvinces(); - final data = jsonDecode(body); - List items = data["items"]; - - final provinces = Province.fromJsonDynamicList(items); - for (var province in provinces) { - provincesData.add( - DropdownMenuItem(value: province, child: Text(province.fullName!))); + try { + List provinces = await apiServices.getAllProvinces(); + for (var province in provinces) { + provincesData.add( + DropdownMenuItem(value: province, child: Text(province.fullName!))); + } + sinkListProvinces.add(provincesData); + } catch (e) { + if (!context.mounted) return; + showErrorTopSnackBarCustom( + context, e.toString()); } - sinkListProvinces.add(provincesData); + } - Future getAllDistricts(String provinceID) async { + Future getAllDistricts(BuildContext context, String provinceID) async { List> districtsData = []; districtsData.clear(); sinkListDistricts.add(districtsData); - final body = await apiServices.getAllDistricts(provinceID); - final data = jsonDecode(body); - List items = data["items"]; - final districts = District.fromJsonDynamicList(items); - for (var district in districts) { - districtsData.add( - DropdownMenuItem(value: district, child: Text(district.fullName!))); + try { + final districts = await apiServices.getAllDistricts(provinceID); + for (var district in districts) { + districtsData.add( + DropdownMenuItem(value: district, child: Text(district.fullName!))); + } + sinkListDistricts.add(districtsData); + } catch (e) { + if (!context.mounted) return; + showErrorTopSnackBarCustom( + context, e.toString()); } - sinkListDistricts.add(districtsData); + } - Future getAllWards(String districtID) async { + Future getAllWards(BuildContext context, String districtID) async { List> wardsData = []; wardsData.clear(); sinkListWards.add(wardsData); - final body = await apiServices.getAllWards(districtID); - final data = jsonDecode(body); - List items = data["items"]; - final wards = Ward.fromJsonDynamicList(items); - for (var ward in wards) { - wardsData.add(DropdownMenuItem(value: ward, child: Text(ward.fullName!))); + try { + final wards = await apiServices.getAllWards(districtID); + for (var ward in wards) { + wardsData.add(DropdownMenuItem(value: ward, child: Text(ward.fullName!))); + } + sinkListWards.add(wardsData); + } catch (e) { + if (!context.mounted) return; + showErrorTopSnackBarCustom( + context, e.toString()); } - sinkListWards.add(wardsData); + } - Future getDeviceInfomation( + Future getDeviceInformation( + BuildContext context, String thingID, - List> districtsData, - List> wardsData, TextEditingController deviceNameController, TextEditingController latitudeController, TextEditingController longitudeController) async { - String body = await apiServices.getDeviceInfomation(thingID); - final data = jsonDecode(body); - Device device = Device.fromJson(data); - sinkDeviceInfo.add(device); - deviceNameController.text = device.name ?? ""; - latitudeController.text = device.settings!.latitude ?? ""; - longitudeController.text = device.settings!.longitude ?? ""; - if (device.areaPath != "") { - List areaPath = device.areaPath!.split('_'); - String provinceCode = areaPath[0]; - String districtCode = areaPath[1]; - String wardCode = areaPath[2]; - getAllDistricts(provinceCode); - getAllWards(districtCode); - final provinceResponse = await apiServices.getProvinceByID(provinceCode); - final provincesData = jsonDecode(provinceResponse); - Province province = Province.fromJson(provincesData['data']); - final districtResponse = await apiServices.getDistrictByID(districtCode); - final districtData = jsonDecode(districtResponse); - District district = District.fromJson(districtData['data']); - final wardResponse = await apiServices.getWardByID(wardCode); - final wardData = jsonDecode(wardResponse); - Ward ward = Ward.fromJson(wardData['data']); - Map provinceData = { - "name": province.fullName!, - "code": province.code! - }; - sinkProvinceData.add(provinceData); - Map districData = { - "name": district.fullName!, - "code": district.code!, - }; - sinkDistrictData.add(districData); - Map wardMap = { - "name": ward.fullName!, - "code": ward.code!, - }; - sinkWardData.add(wardMap); + try { + Device device = await apiServices.getDeviceInformation(thingID); + sinkDeviceInfo.add(device); + deviceNameController.text = device.name ?? ""; + latitudeController.text = device.settings!.latitude ?? ""; + longitudeController.text = device.settings!.longitude ?? ""; + + if (device.areaPath != null && device.areaPath!.isNotEmpty) { + List areaPath = device.areaPath!.split('_'); + + // Kiểm tra độ dài của areaPath + if (areaPath.length >= 3) { + String provinceCode = areaPath[0]; + String districtCode = areaPath[1]; + String wardCode = areaPath[2]; + + // Kiểm tra các mã có hợp lệ không (không rỗng) + if (provinceCode.isNotEmpty && districtCode.isNotEmpty && wardCode.isNotEmpty) { + try { + // Lấy danh sách districts và wards + await getAllDistricts(context,provinceCode); + await getAllWards(context,districtCode); + + // Xử lý Province + try { + Province province = await apiServices.getProvinceByID(provinceCode); + Map provinceData = { + "name": province.fullName ?? "Unknown Province", + "code": province.code ?? provinceCode + }; + sinkProvinceData.add(provinceData); + + } catch (e) { + + // Thêm dữ liệu mặc định khi lỗi + Map provinceData = { + "name": "Error Loading Province", + "code": provinceCode + }; + sinkProvinceData.add(provinceData); + if (!context.mounted) return; + showErrorTopSnackBarCustom( + context, e.toString()); + } + + // Xử lý District + try { + District district = await apiServices.getDistrictByID(districtCode); + Map districData = { + "name": district.fullName ?? "Unknown District", + "code": district.code ?? districtCode, + }; + sinkDistrictData.add(districData); + } catch (e) { + print("Lỗi khi lấy thông tin district ($districtCode): $e"); + Map districData = { + "name": "Error Loading District", + "code": districtCode, + }; + sinkDistrictData.add(districData); + } + + // Xử lý Ward + try { + Ward ward = await apiServices.getWardByID(wardCode); + Map wardMap = { + "name": ward.fullName ?? "Unknown Ward", + "code": ward.code ?? wardCode, + }; + sinkWardData.add(wardMap); + } catch (e) { + print("Lỗi khi lấy thông tin ward ($wardCode): $e"); + Map wardMap = { + "name": "Error Loading Ward", + "code": wardCode, + }; + sinkWardData.add(wardMap); + } + + } catch (e) { + print("Lỗi khi gọi getAllDistricts hoặc getAllWards: $e"); + } + } else { + await getAllProvinces(context); + print("Một hoặc nhiều mã địa phương trống: Province: $provinceCode, District: $districtCode, Ward: $wardCode"); + } + } else { + showNoIconTopSnackBar(context,"AreaPath không đủ thông tin: ${device.areaPath}",Colors.orangeAccent, Colors.white); + } + } + } catch (e) { + showNoIconTopSnackBar(context,"Lỗi trong getDeviceInfomation: $e",Colors.orangeAccent, Colors.white); } } - Future getProvinceByName(String name) async { - final response = await apiServices.getProvincesByName(name); - final data = jsonDecode(response); - if (data != null && - data.containsKey('items') && - data['items'] != null && - data['items'].isNotEmpty) { - List items = data['items']; - List provinces = Province.fromJsonDynamicList(items); + Future getProvinceByName(BuildContext context, String name) async { + try { + List provinces = await apiServices.getProvincesByName(name); if (provinces.isNotEmpty) { return provinces[0]; + } else { + return Province(name: "null"); } + } catch (e) { + if (context.mounted) { + showErrorTopSnackBarCustom(context, e.toString()); + } + return Province(name: "null"); } - return Province(name: "null"); } - Future getDistrictByName(String name, String provinceCode) async { - final response = await apiServices.getDistrictsByName(name); - if (response != "") { - final data = jsonDecode(response); - List items = data['items']; - if (items.isNotEmpty) { - List districts = District.fromJsonDynamicList(items); - if (districts.isNotEmpty) { - for (var district in districts) { - if (district.provinceCode == provinceCode) { - return district; - } - } - } + + Future getDistrictByName( + BuildContext context, String name, String provinceCode) async { + try { + final districts = await apiServices.getDistrictsByName(name); + return districts.firstWhere( + (district) => district.provinceCode == provinceCode, + orElse: () => District(name: "null"), + ); + } catch (e) { + if (context.mounted) { + showErrorTopSnackBarCustom(context, e.toString()); } + return District(name: "null"); } - return District(name: "null"); } - Future getWardByName(String name, String districtCode) async { - final response = await apiServices.getWarsdByName(name); - final data = jsonDecode(response); - if (data != null && data['items'] != null) { - List items = data['items']; - if (items.isNotEmpty) { - List wards = Ward.fromJsonDynamicList(items); - if (wards.isNotEmpty) { - for (var ward in wards) { - if (ward.districtCode == districtCode) { - return ward; - } - } - } + Future getWardByName( + BuildContext context, String name, String districtCode) async { + try { + final wards = await apiServices.getWardsByName(name); + return wards.firstWhere( + (ward) => ward.districtCode == districtCode, + orElse: () => Ward(name: "null"), + ); + } catch (e) { + if (context.mounted) { + showErrorTopSnackBarCustom(context, e.toString()); } + return Ward(name: "null"); } - return Ward(name: "null"); } Future updateDevice( @@ -232,24 +292,31 @@ class DeviceUpdateBloc extends BlocBase { String districtCode, String wardCode, ) async { - DateTime dateTime = DateTime.now(); - String formattedDateTime = - DateFormat('yyyy-MM-dd HH:mm:ss').format(dateTime); - Map body = { - "name": name, - "area_province": provinceCode, - "area_district": districtCode, - "area_ward": wardCode, - "latitude": latitude, - "longitude": longitude, - "note": "User updated device infomation at $formattedDateTime", - }; - int statusCode = await apiServices.updateOwnerDevice(thingID, body); - showSnackBarResponseByStatusCodeNoIcon( - context, - statusCode, - appLocalization(context).notification_update_device_success, - appLocalization(context).notification_update_device_failed, - ); + try { + DateTime dateTime = DateTime.now(); + String formattedDateTime = + DateFormat('yyyy-MM-dd HH:mm:ss').format(dateTime); + Map body = { + "name": name, + "area_province": provinceCode, + "area_district": districtCode, + "area_ward": wardCode, + "latitude": latitude, + "longitude": longitude, + "note": "User updated device infomation at $formattedDateTime", + }; + int statusCode = await apiServices.updateOwnerDevice(thingID, body); + showSnackBarResponseByStatusCodeNoIcon( + context, + statusCode, + appLocalization(context).notification_update_device_success, + appLocalization(context).notification_update_device_failed, + ); + } catch (e) { + if (!context.mounted) return; + showErrorTopSnackBarCustom( + context, e.toString()); + } + } } diff --git a/lib/bloc/devices_manager_bloc.dart b/lib/bloc/devices_manager_bloc.dart index 56813c5..4e1f10e 100644 --- a/lib/bloc/devices_manager_bloc.dart +++ b/lib/bloc/devices_manager_bloc.dart @@ -1,11 +1,14 @@ import 'dart:async'; import 'dart:convert'; +import 'package:flutter/cupertino.dart'; + import '../feature/devices/device_model.dart'; import '../product/base/bloc/base_bloc.dart'; import '../product/constant/app/app_constants.dart'; import '../product/services/api_services.dart'; +import '../product/shared/shared_snack_bar.dart'; import '../product/utils/device_utils.dart'; class DevicesManagerBloc extends BlocBase { @@ -73,31 +76,27 @@ class DevicesManagerBloc extends BlocBase { // } // } - void getDeviceByState(int state) async { - sinkTagStates.add([state]); + void getDeviceByState(BuildContext context,int state) async { + try { + sinkTagStates.add([state]); - Map> deviceByState = { - ApplicationConstants.OFFLINE_STATE: [], - ApplicationConstants.NORMAL_STATE: [], - ApplicationConstants.WARNING_STATE: [], - ApplicationConstants.INPROGRESS_STATE: [], - ApplicationConstants.ERROR_STATE: [], - }; + Map> deviceByState = { + ApplicationConstants.OFFLINE_STATE: [], + ApplicationConstants.NORMAL_STATE: [], + ApplicationConstants.WARNING_STATE: [], + ApplicationConstants.INPROGRESS_STATE: [], + ApplicationConstants.ERROR_STATE: [], + }; - List devices = []; - String body; + List devices = []; + List originalDevices = []; + if (state != -2) { + originalDevices = + await apiServices.getOwnerDeviceByState({"state": state.toString()}); + } else { + originalDevices = await apiServices.getOwnerDevices(); + } - if (state != -2) { - body = - await apiServices.getOwnerDeviceByState({"state": state.toString()}); - } else { - body = await apiServices.getOwnerDevices(); - } - - if (body.isNotEmpty) { - final data = jsonDecode(body); - List items = data['items']; - List originalDevices = Device.fromJsonDynamicList(items); List publicDevices = []; for(var device in originalDevices){ @@ -116,9 +115,14 @@ class DevicesManagerBloc extends BlocBase { } sinkDeviceByState.add(deviceByState); } + + sinkAllDevices.add(devices); + } catch (e) { + if (!context.mounted) return; + showErrorTopSnackBarCustom( + context, e.toString()); } - sinkAllDevices.add(devices); } String _getStateKey(int state) { diff --git a/lib/bloc/group_detail_bloc.dart b/lib/bloc/group_detail_bloc.dart index ed22171..a461a12 100644 --- a/lib/bloc/group_detail_bloc.dart +++ b/lib/bloc/group_detail_bloc.dart @@ -8,6 +8,7 @@ import '../feature/devices/device_model.dart'; import '../product/base/bloc/base_bloc.dart'; import '../product/services/api_services.dart'; import '../product/services/language_services.dart'; +import '../product/shared/shared_snack_bar.dart'; import '../product/utils/response_status_utils.dart'; import '../feature/inter_family/group_detail/group_detail_model.dart'; @@ -29,77 +30,114 @@ class DetailGroupBloc extends BlocBase { @override void dispose() {} - Future getGroupDetail(String groupID) async { - final body = await apiServices.getGroupDetail(groupID); - final data = jsonDecode(body); - List warningDevices = []; - if (data != null) { - GroupDetail group = GroupDetail.fromJson(data); - sinkDetailGroup.add(group); - if (group.devices != null) { - for (var device in group.devices!) { - if (device.state == 1) { - warningDevices.add(device); + Future getGroupDetail(BuildContext context,String groupID) async { + try { + List warningDevices = []; + GroupDetail group = await apiServices.getGroupDetail(groupID); + sinkDetailGroup.add(group); + if (group.devices != null) { + for (var device in group.devices!) { + if (device.state == 1) { + warningDevices.add(device); + } } + sinkWarningDevice.add(warningDevices); } - sinkWarningDevice.add(warningDevices); - } + } catch (e) { + if (!context.mounted) return; + showErrorTopSnackBarCustom( + context, e.toString()); } + } Future approveUserToGroup(BuildContext context, String groupID, String userID, String userName) async { - Map body = {"group_id": groupID, "user_id": userID}; - int statusCode = await apiServices.approveGroup(body); - showSnackBarResponseByStatusCode(context, statusCode, - "Đã duyệt $userName vào nhóm!", "Duyệt $userName thất bại!"); + try { + Map body = {"group_id": groupID, "user_id": userID}; + int statusCode = await apiServices.approveGroup(body); + showSnackBarResponseByStatusCode(context, statusCode, + "Đã duyệt $userName vào nhóm!", "Duyệt $userName thất bại!"); + } catch (e) { + if (!context.mounted) return; + showErrorTopSnackBarCustom( + context, e.toString()); + } } Future deleteOrUnapproveUser(BuildContext context, String groupID, String userID, String userName) async { - int statusCode = await apiServices.deleteUserInGroup(groupID, userID); - showSnackBarResponseByStatusCode(context, statusCode, - "Đã xóa người dùng $userName", "Xóa người dùng $userName thất bại"); + try { + int statusCode = await apiServices.deleteUserInGroup(groupID, userID); + showSnackBarResponseByStatusCode(context, statusCode, + "Đã xóa người dùng $userName", "Xóa người dùng $userName thất bại"); + } catch (e) { + if (!context.mounted) return; + showErrorTopSnackBarCustom( + context, e.toString()); + } } Future deleteDevice(BuildContext context, String groupID, String thingID, String deviceName) async { - int statusCode = await apiServices.deleteDeviceInGroup(groupID, thingID); - showSnackBarResponseByStatusCode(context, statusCode, - "Đã xóa thiết bị $deviceName", "Xóa thiết bị $deviceName thất bại"); + try { + int statusCode = await apiServices.deleteDeviceInGroup(groupID, thingID); + showSnackBarResponseByStatusCode(context, statusCode, + "Đã xóa thiết bị $deviceName", "Xóa thiết bị $deviceName thất bại"); + } catch (e) { + if (!context.mounted) return; + showErrorTopSnackBarCustom( + context, e.toString()); + } } Future leaveGroup( BuildContext context, String groupID, String userID) async { - int statusCode = await apiServices.deleteUserInGroup(groupID, userID); - showSnackBarResponseByStatusCode( - context, - statusCode, - appLocalization(context).notification_leave_group_success, - appLocalization(context).notification_leave_group_failed); + try { + int statusCode = await apiServices.deleteUserInGroup(groupID, userID); + showSnackBarResponseByStatusCode( + context, + statusCode, + appLocalization(context).notification_leave_group_success, + appLocalization(context).notification_leave_group_failed); + } catch (e) { + if (!context.mounted) return; + showErrorTopSnackBarCustom( + context, e.toString()); + } } Future updateDeviceNameInGroup( BuildContext context, String thingID, String newAlias) async { - Map body = {"thing_id": thingID, "alias": newAlias}; - int statusCode = await apiServices.updateDeviceAlias(body); - showSnackBarResponseByStatusCode( - context, - statusCode, - appLocalization(context).notification_update_device_success, - appLocalization(context).notification_update_device_failed, - ); + try { + Map body = {"thing_id": thingID, "alias": newAlias}; + int statusCode = await apiServices.updateDeviceAlias(body); + showSnackBarResponseByStatusCode( + context, + statusCode, + appLocalization(context).notification_update_device_success, + appLocalization(context).notification_update_device_failed, + ); + } catch (e) { + if (!context.mounted) return; + showErrorTopSnackBarCustom( + context, e.toString()); + } } - Future> getOwnerDevices() async { + Future> getOwnerDevices(BuildContext context) async { List allDevices = []; - String body = await apiServices.getOwnerDevices(); - if (body != "") { - final data = jsonDecode(body); - List items = data['items']; - allDevices = Device.fromJsonDynamicList(items); + try { + final originalDevices = await apiServices.getOwnerDevices(); + allDevices = originalDevices.where((device) => device.visibility == "PUBLIC").toList(); + + return allDevices; + } catch (e) { + if (context.mounted) { + showErrorTopSnackBarCustom(context, e.toString()); + } + return allDevices; } - return allDevices; } Future addDeviceToGroup( @@ -107,12 +145,18 @@ class DetailGroupBloc extends BlocBase { Map body = { "thing_id": thingID, }; - int statusCode = await apiServices.addDeviceToGroup(groupID, body); - showSnackBarResponseByStatusCode( - context, - statusCode, - appLocalization(context).notification_add_device_success, - appLocalization(context).notification_add_device_failed, - ); + try { + int statusCode = await apiServices.addDeviceToGroup(groupID, body); + showSnackBarResponseByStatusCode( + context, + statusCode, + appLocalization(context).notification_add_device_success, + appLocalization(context).notification_add_device_failed, + ); + } catch (e) { + if (!context.mounted) return; + showErrorTopSnackBarCustom( + context, e.toString()); + } } } diff --git a/lib/bloc/inter_family_bloc.dart b/lib/bloc/inter_family_bloc.dart index 389afe8..bae8114 100644 --- a/lib/bloc/inter_family_bloc.dart +++ b/lib/bloc/inter_family_bloc.dart @@ -10,6 +10,7 @@ import '../product/constant/app/app_constants.dart'; import '../product/services/api_services.dart'; import '../product/base/bloc/base_bloc.dart'; import '../product/services/language_services.dart'; +import '../product/shared/shared_snack_bar.dart'; import '../product/utils/response_status_utils.dart'; import '../feature/inter_family/groups/groups_model.dart'; @@ -38,46 +39,27 @@ class InterFamilyBloc extends BlocBase { void getAllGroup(String role) async { List groups = []; sinkCurrentGroups.add(groups); + try { + groups = await apiServices.getAllGroups(); + groups = sortGroupByName(groups); - final body = await apiServices.getAllGroups(); + List currentGroups = groups.where( + (group) { + bool isPublic = group.visibility == "PUBLIC"; + if (role == ApplicationConstants.OWNER_GROUP) { + return group.isOwner == true && isPublic; + } + if (role == ApplicationConstants.PARTICIPANT_GROUP) { + return group.isOwner == null && isPublic; + } + return false; + }, + ).toList(); - if (body.isNotEmpty) { - try { - final data = jsonDecode(body); - - if (data is Map && data.containsKey("items") && data["items"] is List) { - List items = data["items"]; - groups = Group.fromJsonDynamicList(items); - groups = sortGroupByName(groups); - - List currentGroups = groups.where( - (group) { - bool isPublic = group.visibility == "PUBLIC"; - - if (role == ApplicationConstants.OWNER_GROUP) { - return group.isOwner == true && isPublic; - } - - if (role == ApplicationConstants.PARTICIPANT_GROUP) { - return group.isOwner == null && isPublic; - } - - return false; - }, - ).toList(); - - sinkCurrentGroups.add(currentGroups); - } else { - log("No items found in API response or empty JSON object"); - sinkCurrentGroups.add([]); - } - } catch (e) { - // Xử lý lỗi khi jsonDecode thất bại - log("Error decoding JSON: $e"); - sinkCurrentGroups.add([]); - } - } else { - log("Get groups from API failed: Empty response"); + sinkCurrentGroups.add(currentGroups); + } catch (e) { + // Xử lý lỗi khi jsonDecode thất bại + log("Error decoding JSON: $e"); sinkCurrentGroups.add([]); } log("Inter Family Role: $role"); @@ -85,46 +67,70 @@ class InterFamilyBloc extends BlocBase { Future createGroup( BuildContext context, String name, String description) async { - APIServices apiServices = APIServices(); - Map body = {"name": name, "description": description}; - int? statusCode = await apiServices.createGroup(body); - showSnackBarResponseByStatusCode( - context, - statusCode, - appLocalization(context).notification_add_group_success, - appLocalization(context).notification_add_group_failed); + try { + Map body = {"name": name, "description": description}; + int? statusCode = await apiServices.createGroup(body); + showSnackBarResponseByStatusCode( + context, + statusCode, + appLocalization(context).notification_add_group_success, + appLocalization(context).notification_add_group_failed); + } catch (e) { + if (!context.mounted) return; + showErrorTopSnackBarCustom( + context, e.toString()); + } + } - Future changeGroupInfomation(BuildContext context, String groupID, + Future changeGroupInformation(BuildContext context, String groupID, String name, String description) async { - Map body = {"name": name, "description": description}; - int statusCode = await apiServices.updateGroup(body, groupID); - showSnackBarResponseByStatusCode( - context, - statusCode, - appLocalization(context).notification_update_group_success, - appLocalization(context).notification_update_group_failed); + try { + Map body = {"name": name, "description": description}; + int statusCode = await apiServices.updateGroup(body, groupID); + showSnackBarResponseByStatusCode( + context, + statusCode, + appLocalization(context).notification_update_group_success, + appLocalization(context).notification_update_group_failed); + } catch (e) { + if (!context.mounted) return; + showErrorTopSnackBarCustom( + context, e.toString()); + } } Future joinGroup(BuildContext context, String groupID) async { - Map body = { - "group_id": groupID, - }; - int statusCode = await apiServices.joinGroup(groupID, body); - showSnackBarResponseByStatusCode( - context, - statusCode, - appLocalization(context).notification_join_request_group_success, - appLocalization(context).notification_join_request_group_failed); + try { + Map body = { + "group_id": groupID, + }; + int statusCode = await apiServices.joinGroup(groupID, body); + showSnackBarResponseByStatusCode( + context, + statusCode, + appLocalization(context).notification_join_request_group_success, + appLocalization(context).notification_join_request_group_failed); + } catch (e) { + if (!context.mounted) return; + showErrorTopSnackBarCustom( + context, e.toString()); + } } Future deleteGroup(BuildContext context, String groupID) async { - int statusCode = await apiServices.deleteGroup(groupID); - showSnackBarResponseByStatusCode( - context, - statusCode, - appLocalization(context).notification_delete_group_success, - appLocalization(context).notification_delete_group_failed); + try { + int statusCode = await apiServices.deleteGroup(groupID); + showSnackBarResponseByStatusCode( + context, + statusCode, + appLocalization(context).notification_delete_group_success, + appLocalization(context).notification_delete_group_failed); + } catch (e) { + if (!context.mounted) return; + showErrorTopSnackBarCustom( + context, e.toString()); + } } List sortGroupByName(List groups) { diff --git a/lib/bloc/main_bloc.dart b/lib/bloc/main_bloc.dart index fac8de6..b5a7ebb 100644 --- a/lib/bloc/main_bloc.dart +++ b/lib/bloc/main_bloc.dart @@ -12,6 +12,7 @@ import '../product/base/bloc/base_bloc.dart'; import '../product/services/api_services.dart'; import '../feature/bell/bell_model.dart'; import '../feature/settings/profile/profile_model.dart'; +import '../product/shared/shared_snack_bar.dart'; class MainBloc extends BlocBase { APIServices apiServices = APIServices(); @@ -42,10 +43,16 @@ class MainBloc extends BlocBase { @override void dispose() {} - void getUserProfile() async { - String data = await apiServices.getUserDetail(); - User user = User.fromJson(jsonDecode(data)); - sinkUserProfile.add(user); + void getUserProfile(BuildContext context) async { + try { + User user = await apiServices.getUserDetail(); + sinkUserProfile.add(user); + } catch (e) { + if (!context.mounted) return; + showErrorTopSnackBarCustom( + context, e.toString()); + } + } getFCMTokenAndPresentations() async { diff --git a/lib/bloc/settings_bloc.dart b/lib/bloc/settings_bloc.dart index 31008e1..1f3336a 100644 --- a/lib/bloc/settings_bloc.dart +++ b/lib/bloc/settings_bloc.dart @@ -1,10 +1,15 @@ import 'dart:async'; +import 'package:flutter/cupertino.dart'; +import 'package:sfm_app/product/services/api_services.dart'; + import '../feature/settings/profile/profile_model.dart'; import '../product/base/bloc/base_bloc.dart'; +import '../product/shared/shared_snack_bar.dart'; class SettingsBloc extends BlocBase { // Settings Screen + APIServices apiServices = APIServices(); final userProfile = StreamController.broadcast(); StreamSink get sinkUserProfile => userProfile.sink; Stream get streamUserProfile => userProfile.stream; @@ -18,6 +23,17 @@ class SettingsBloc extends BlocBase { isChangeProfileInfomation.stream; + void getUserProfile(BuildContext context) async { + try { + User user = await apiServices.getUserDetail(); + sinkUserProfile.add(user); + } catch (e) { + if (!context.mounted) return; + showErrorTopSnackBarCustom( + context, e.toString()); + } + } + @override void dispose() { diff --git a/lib/feature/bell/bell_screen.dart b/lib/feature/bell/bell_screen.dart index d55bead..2d95e02 100644 --- a/lib/feature/bell/bell_screen.dart +++ b/lib/feature/bell/bell_screen.dart @@ -1,11 +1,14 @@ import 'dart:async'; import 'package:flutter/material.dart'; +import 'package:sfm_app/product/shared/shared_snack_bar.dart'; import '../../product/extension/context_extension.dart'; import '../../product/services/language_services.dart'; import '../../bloc/bell_bloc.dart'; import '../../product/base/bloc/base_bloc.dart'; import '../../product/services/api_services.dart'; +import '../../product/shared/shared_component_loading_animation.dart'; +import '../../product/shared/shared_loading_animation.dart'; import 'bell_model.dart'; class BellScreen extends StatefulWidget { @@ -56,11 +59,7 @@ class _BellScreenState extends State { initialData: items, builder: (context, bellSnapshot) { return check - ? Center( - child: CircularProgressIndicator( - value: context.highValue, - ), - ) + ? const SharedLoadingAnimation() : bellSnapshot.data?.isEmpty ?? true ? Center( child: Text( @@ -78,16 +77,7 @@ class _BellScreenState extends State { if (index < bellSnapshot.data!.length) { return GestureDetector( onTap: () async { - List read = []; - read.add(bellSnapshot.data![index].id!); - int code = await apiServices - .updateStatusOfNotification(read); - if (code == 200) { - read.clear(); - } else { - read.clear(); - } - refresh(); + readNotification(bellSnapshot.data![index].id!); }, child: Column( children: [ @@ -143,7 +133,7 @@ class _BellScreenState extends State { builder: (context, hasMoreSnapshot) { return Center( child: hasMoreSnapshot.data ?? hasMore - ? const CircularProgressIndicator() + ? const SharedComponentLoadingAnimation() : Text( appLocalization(context) .bell_read_all, @@ -173,20 +163,40 @@ class _BellScreenState extends State { getBellNotification(offset); } - Future getBellNotification(int offset) async { - bell = await apiServices.getBellNotifications( - offset.toString(), (offset + 20).toString()); - - if (bell.items!.isEmpty) { - hasMore = false; - bellBloc.sinkHasMore.add(hasMore); - } else { - for (var item in bell.items!) { - items.add(item); + void readNotification(String id) async{ + try { + List read = []; + read.add(id); + await apiServices + .updateStatusOfNotification(read); + read.clear(); + } catch (e) { + if (mounted){ + showErrorTopSnackBarCustom( + context, e.toString()); } } - bellBloc.bellItems.add(items); - check = false; + refresh(); + } + + Future getBellNotification(int offset) async { + try { + bell = await apiServices.getBellNotifications( + offset.toString(), (offset + 20).toString()); + if (bell.items!.isEmpty) { + hasMore = false; + bellBloc.sinkHasMore.add(hasMore); + } else { + for (var item in bell.items!) { + items.add(item); + } + } + bellBloc.bellItems.add(items); + check = false; + } catch (e) { + if (!mounted) return; + showErrorTopSnackBarCustom(context, e.toString()); + } } String timeAgo(BuildContext context, DateTime dateTime) { diff --git a/lib/feature/device_log/device_logs_screen.dart b/lib/feature/device_log/device_logs_screen.dart index f496817..3b09211 100644 --- a/lib/feature/device_log/device_logs_screen.dart +++ b/lib/feature/device_log/device_logs_screen.dart @@ -2,6 +2,8 @@ import 'package:dropdown_button2/dropdown_button2.dart'; import 'package:flutter/material.dart'; import 'package:intl/intl.dart'; +import '../../product/shared/shared_component_loading_animation.dart'; +import '../../product/shared/shared_loading_animation.dart'; import 'widgets/tag_widget.dart'; import '../devices/device_model.dart'; import '../../bloc/device_logs_bloc.dart'; @@ -42,7 +44,7 @@ class _DeviceLogsScreenState extends State { () { if (controller.position.maxScrollExtent == controller.offset) { offset += 30; - deviceLogsBloc.getDeviceLogByThingID( + deviceLogsBloc.getDeviceLogByThingID(context, offset, thingID, dateTime!, sensors); } }, @@ -64,10 +66,8 @@ class _DeviceLogsScreenState extends State { stream: deviceLogsBloc.streamAllDevices, builder: (context, allDevicesSnapshot) { if (allDevicesSnapshot.data == null) { - deviceLogsBloc.getAllDevices(); - return const Center( - child: CircularProgressIndicator(), - ); + deviceLogsBloc.getAllDevices(context); + return const SharedLoadingAnimation(); } else { return StreamBuilder>( stream: deviceLogsBloc.streamSensors, @@ -188,6 +188,7 @@ class _DeviceLogsScreenState extends State { ); } else { deviceLogsBloc.getDeviceLogByThingID( + context, offset, thingID, dateTime!, @@ -246,7 +247,7 @@ class _DeviceLogsScreenState extends State { return Center( child: hasMoreSnapshot.data ?? hasMore - ? const CircularProgressIndicator() + ? const SharedComponentLoadingAnimation() : Text( appLocalization(context) .main_no_data), @@ -310,7 +311,7 @@ class _DeviceLogsScreenState extends State { deviceLogsBloc.sensors.add(sensors); hasMore = true; deviceLogsBloc.sinkHasMore.add(hasMore); - deviceLogsBloc.getDeviceLogByThingID(offset, thingID, dateTime!, sensors); + deviceLogsBloc.getDeviceLogByThingID(context,offset, thingID, dateTime!, sensors); } Widget leadingList(SensorLogs sensor) { diff --git a/lib/feature/devices/add_new_device_widget.dart b/lib/feature/devices/add_new_device_widget.dart index f1cdc59..f19779e 100644 --- a/lib/feature/devices/add_new_device_widget.dart +++ b/lib/feature/devices/add_new_device_widget.dart @@ -95,23 +95,36 @@ void addDevices( APIServices apiServices = APIServices(); Map body = {}; if (role == RoleEnums.ADMIN.name) { - body = {"ext_id": extID, "name": deviceName}; - int statusCode = await apiServices.createDeviceByAdmin(body); - showSnackBarResponseByStatusCode( - context, - statusCode, - appLocalization(context).notification_create_device_success, - appLocalization(context).notification_create_device_failed); - deviceManagerBloc.getDeviceByState(-2); + try { + body = {"ext_id": extID, "name": deviceName}; + int statusCode = await apiServices.createDeviceByAdmin(body); + showSnackBarResponseByStatusCode( + context, + statusCode, + appLocalization(context).notification_create_device_success, + appLocalization(context).notification_create_device_failed); + deviceManagerBloc.getDeviceByState(context,-2); + } catch (e) { + if (!context.mounted) return; + showErrorTopSnackBarCustom( + context, e.toString()); + } + } else { - body = {"ext_id": extID}; - int statusCode = await apiServices.registerDevice(body); - showSnackBarResponseByStatusCode( - context, - statusCode, - appLocalization(context).notification_add_device_success, - appLocalization(context).notification_device_not_exist); - deviceManagerBloc.getDeviceByState(-2); + try { + body = {"ext_id": extID}; + int statusCode = await apiServices.registerDevice(body); + showSnackBarResponseByStatusCode( + context, + statusCode, + appLocalization(context).notification_add_device_success, + appLocalization(context).notification_device_not_exist); + deviceManagerBloc.getDeviceByState(context,-2); + } catch (e) { + if (!context.mounted) return; + showErrorTopSnackBarCustom( + context, e.toString()); + } } } diff --git a/lib/feature/devices/delete_device_widget.dart b/lib/feature/devices/delete_device_widget.dart index 299c0b7..2b15310 100644 --- a/lib/feature/devices/delete_device_widget.dart +++ b/lib/feature/devices/delete_device_widget.dart @@ -5,6 +5,7 @@ import '../../bloc/devices_manager_bloc.dart'; import '../../product/constant/enums/role_enums.dart'; import '../../product/services/api_services.dart'; import '../../product/services/language_services.dart'; +import '../../product/shared/shared_snack_bar.dart'; import '../../product/utils/response_status_utils.dart'; handleDeleteDevice(BuildContext context, DevicesManagerBloc devicesManagerBloc, @@ -53,14 +54,21 @@ deleteOrUnregisterDevice(BuildContext context, DevicesManagerBloc devicesBloc, statusCode, appLocalization(context).notification_delete_device_success, appLocalization(context).notification_delete_device_failed); - devicesBloc.getDeviceByState(-2); + devicesBloc.getDeviceByState(context,-2); } else { - int statusCode = await apiServices.deleteDeviceByAdmin(extID); - showSnackBarResponseByStatusCode( - context, - statusCode, - appLocalization(context).notification_delete_device_success, - appLocalization(context).notification_delete_device_failed); - devicesBloc.getDeviceByState(-2); + try { + int statusCode = await apiServices.deleteDeviceByAdmin(extID); + showSnackBarResponseByStatusCode( + context, + statusCode, + appLocalization(context).notification_delete_device_success, + appLocalization(context).notification_delete_device_failed); + devicesBloc.getDeviceByState(context,-2); + } catch (e) { + if (!context.mounted) return; + showErrorTopSnackBarCustom( + context, e.toString()); + } + } } diff --git a/lib/feature/devices/device_detail/device_detail_screen.dart b/lib/feature/devices/device_detail/device_detail_screen.dart index f27f3ae..441f5f9 100644 --- a/lib/feature/devices/device_detail/device_detail_screen.dart +++ b/lib/feature/devices/device_detail/device_detail_screen.dart @@ -2,11 +2,13 @@ import 'dart:async'; import 'package:flutter/material.dart'; import 'package:google_maps_flutter/google_maps_flutter.dart'; +import 'package:sfm_app/product/shared/shared_component_loading_animation.dart'; import 'package:simple_ripple_animation/simple_ripple_animation.dart'; import '../../../product/constant/image/image_constants.dart'; import '../../../product/shared/shared_line_chart.dart'; import '../../../product/shared/shared_curve.dart'; +import '../../../product/shared/shared_loading_animation.dart'; import '../../device_log/device_logs_model.dart'; import '../device_model.dart'; import '../../../product/base/bloc/base_bloc.dart'; @@ -92,8 +94,11 @@ class _DetailDeviceScreenState extends State { widget.thingID, controller, ); - return const Center( - child: CircularProgressIndicator(), + return Scaffold( + appBar: AppBar(), + body: Center( + child: Text(appLocalization(context).no_data_message), + ), ); } else { return StreamBuilder>( @@ -125,28 +130,6 @@ class _DetailDeviceScreenState extends State { fit: BoxFit.fill, ), ), - // Center( - // child: Container( - // height: 50, - // width: 400, - // // color: Colors.blueAccent, - // alignment: Alignment.centerRight, - // margin: const EdgeInsets.fromLTRB(0, 0, 0, 50), - // child: Row( - // mainAxisAlignment: MainAxisAlignment.spaceBetween, - // children: [ - // const SizedBox(), - // Text( - // deviceSnapshot.data?.name ?? "", - // style: const TextStyle( - // fontSize: 25, - // fontWeight: FontWeight.w600, - // ), - // ), - // ], - // ), - // ), - // ), ], ), ), @@ -541,12 +524,10 @@ class _DetailDeviceScreenState extends State { builder: (context, sensorTempsSnapshot) { if (sensorTempsSnapshot.data == null) { detailDeviceBloc - .getNearerSensorValue(widget.thingID); + .getNearerSensorValue(context,widget.thingID); return const AspectRatio( aspectRatio: 3, - child: Center( - child: CircularProgressIndicator(), - ), + child: SharedComponentLoadingAnimation(), ); } else if (sensorTempsSnapshot.data!.isEmpty) { return Center( @@ -657,12 +638,7 @@ class _DetailDeviceScreenState extends State { ), ); } else { - return Scaffold( - appBar: AppBar(), - body: const Center( - child: CircularProgressIndicator(), - ), - ); + return const SharedLoadingAnimation(); } }, ); diff --git a/lib/feature/devices/device_update/device_update_screen.dart b/lib/feature/devices/device_update/device_update_screen.dart index a0ea706..e8042c0 100644 --- a/lib/feature/devices/device_update/device_update_screen.dart +++ b/lib/feature/devices/device_update/device_update_screen.dart @@ -1,6 +1,7 @@ import 'package:flutter/material.dart'; import 'package:search_choices/search_choices.dart'; +import '../../../product/shared/shared_loading_animation.dart'; import '../device_model.dart'; import '../../../bloc/device_update_bloc.dart'; import 'map_dialog.dart'; @@ -47,7 +48,7 @@ class _DeviceUpdateScreenState extends State { void initState() { super.initState(); deviceUpdateBloc = BlocProvider.of(context); - deviceUpdateBloc.getAllProvinces(); + deviceUpdateBloc.getAllProvinces(context); } @override @@ -72,16 +73,16 @@ class _DeviceUpdateScreenState extends State { initialData: device, builder: (context, deviceInfoSnapshot) { if (deviceInfoSnapshot.data!.thingId == null) { - deviceUpdateBloc.getDeviceInfomation( + deviceUpdateBloc.getDeviceInformation( + context, widget.thingID, - districtsData, - wardsData, + // provincesData, + // districtsData, + // wardsData, deviceNameController, deviceLatitudeController, deviceLongitudeController); - return const Center( - child: CircularProgressIndicator(), - ); + return const SharedLoadingAnimation(); } else { return StreamBuilder( stream: deviceUpdateBloc.streamIsChanged, @@ -245,7 +246,7 @@ class _DeviceUpdateScreenState extends State { .sinkProvinceData .add(provinceData); deviceUpdateBloc - .getAllDistricts( + .getAllDistricts(context, value.code); selectedDistrict = ""; districtData['name'] = @@ -318,7 +319,7 @@ class _DeviceUpdateScreenState extends State { .sinkDistrictData .add(districtData); deviceUpdateBloc - .getAllWards(value.code); + .getAllWards(context,value.code); selectedWard = ""; wardData['name'] = selectedWard!; diff --git a/lib/feature/devices/device_update/map_dialog.dart b/lib/feature/devices/device_update/map_dialog.dart index 5b4d740..e7d48c8 100644 --- a/lib/feature/devices/device_update/map_dialog.dart +++ b/lib/feature/devices/device_update/map_dialog.dart @@ -64,7 +64,7 @@ showMapDialog( String latitude = mapDialogLatitudeController.text; String longitude = mapDialogLongitudeController.text; log("Finish -- Latitude: $latitude, longitude: $longitude --"); - getDataFromApi(latitude, longitude, deviceUpdateBloc); + getDataFromApi(context,latitude, longitude, deviceUpdateBloc); latitudeController.text = mapDialogLatitudeController.text; longitudeController.text = @@ -184,7 +184,7 @@ addMarker( updateCameraPosition(position, 14, mapController); } -void getDataFromApi(String latitude, String longitude, +void getDataFromApi(BuildContext context,String latitude, String longitude, DeviceUpdateBloc deviceUpdateBloc) async { String path = "maps/api/geocode/json?latlng=$latitude,$longitude&language=vi&result_type=political&key=${ApplicationConstants.MAP_KEY}"; @@ -215,7 +215,7 @@ void getDataFromApi(String latitude, String longitude, log("$key: $value"); }); - await _processLocations(locations, deviceUpdateBloc); + await _processLocations(context,locations, deviceUpdateBloc); } Map _extractLocationComponents( @@ -241,31 +241,31 @@ Map _extractLocationComponents( return locations; } -Future _processLocations( +Future _processLocations(BuildContext context, Map locations, DeviceUpdateBloc deviceUpdateBloc) async { String provinceNameFromAPI = locations['provincekey'] ?? ""; String districtNameFromAPI = locations['districtkey'] ?? ""; String wardNameFromAPI = locations['wardkey'] ?? ""; final province = - await deviceUpdateBloc.getProvinceByName(provinceNameFromAPI); + await deviceUpdateBloc.getProvinceByName(context,provinceNameFromAPI); if (province.name != "null") { log("Province: ${province.fullName}, ProvinceCode: ${province.code}"); deviceUpdateBloc.sinkProvinceData .add({"code": province.code!, "name": province.fullName!}); - deviceUpdateBloc.getAllProvinces(); + deviceUpdateBloc.getAllProvinces(context); - final district = await deviceUpdateBloc.getDistrictByName( + final district = await deviceUpdateBloc.getDistrictByName(context, districtNameFromAPI, province.code!); log("Districtname: ${district.fullName}, districtCode: ${district.code}"); - deviceUpdateBloc.getAllDistricts(province.code!); + deviceUpdateBloc.getAllDistricts(context,province.code!); if (district.name != "null") { deviceUpdateBloc.sinkDistrictData .add({"code": district.code!, "name": district.fullName!}); final ward = - await deviceUpdateBloc.getWardByName(wardNameFromAPI, district.code!); + await deviceUpdateBloc.getWardByName(context,wardNameFromAPI, district.code!); log("Wardname: ${ward.fullName}, WardCode: ${ward.code}"); - deviceUpdateBloc.getAllWards(district.code!); + deviceUpdateBloc.getAllWards(context,district.code!); if (ward.name != "null") { log("Xac dinh duoc het thong tin tu toa do"); deviceUpdateBloc.sinkWardData diff --git a/lib/feature/devices/devices_manager_screen.dart b/lib/feature/devices/devices_manager_screen.dart index 6af1bc5..5bf8866 100644 --- a/lib/feature/devices/devices_manager_screen.dart +++ b/lib/feature/devices/devices_manager_screen.dart @@ -4,6 +4,8 @@ import 'package:data_table_2/data_table_2.dart'; import 'package:flutter/gestures.dart'; import 'package:flutter/material.dart'; import 'package:go_router/go_router.dart'; +import '../../product/shared/shared_component_loading_animation.dart'; +import '../../product/shared/shared_loading_animation.dart'; import 'add_new_device_widget.dart'; import 'delete_device_widget.dart'; import 'device_model.dart'; @@ -41,7 +43,7 @@ class _DevicesManagerScreenState extends State { const duration = Duration(seconds: 10); getAllDevicesTimer = Timer.periodic( duration, - (Timer t) => devicesManagerBloc.getDeviceByState(tagIndex), + (Timer t) => devicesManagerBloc.getDeviceByState(context,tagIndex), ); } @@ -68,8 +70,8 @@ class _DevicesManagerScreenState extends State { builder: (context, allDeviceSnapshot) { if(allDeviceSnapshot.data == null){ devicesManagerBloc - .getDeviceByState(tagSnapshot.data?[0] ?? -2); - return const Center(child: CircularProgressIndicator()); + .getDeviceByState(context,tagSnapshot.data?[0] ?? -2); + return const SharedLoadingAnimation(); } if (allDeviceSnapshot.data!.isEmpty) { return Center( @@ -219,10 +221,9 @@ class _DevicesManagerScreenState extends State { stream: devicesManagerBloc.streamDeviceByState, builder: (context, devicesByStateSnapshot) { if (devicesByStateSnapshot.data == null) { - devicesManagerBloc.getDeviceByState( + devicesManagerBloc.getDeviceByState(context, tagSnapshot.data?[0] ?? -2); - return const Center( - child: CircularProgressIndicator()); + return const SharedComponentLoadingAnimation(); } else { return SharedPieChart( deviceByState: @@ -392,7 +393,7 @@ class TagState extends StatelessWidget { ), GestureDetector( onTap: () { - devicesManagerBloc.getDeviceByState(-2); + devicesManagerBloc.getDeviceByState(context,-2); }, child: const Icon( Icons.close, diff --git a/lib/feature/home/home_screen.dart b/lib/feature/home/home_screen.dart index 4d035be..b645093 100644 --- a/lib/feature/home/home_screen.dart +++ b/lib/feature/home/home_screen.dart @@ -3,6 +3,9 @@ import 'dart:convert'; import 'dart:developer'; import 'package:flutter/material.dart'; +import '../../product/shared/shared_component_loading_animation.dart'; +import '../../product/shared/shared_loading_animation.dart'; +import '../../product/shared/shared_snack_bar.dart'; import 'shared/alert_card.dart'; import 'shared/warning_card.dart'; import '../../product/utils/device_utils.dart'; @@ -206,7 +209,7 @@ class _HomeScreenState extends State { stream: homeBloc.streamAllDevicesAliasJoinedMap, builder: (context, allDevicesAliasJoinedMapSnapshot) { if (hasJoinedDeviceSnapshot.data == null) { - return const CircularProgressIndicator(); + return const SharedComponentLoadingAnimation(); } else { final data = allDevicesAliasMapSnapshot.data!; final dataJoined = @@ -219,54 +222,71 @@ class _HomeScreenState extends State { } return OverviewCard( - isOwner: true, - total: data['all']?.length ?? 0, - active: data['online']?.length ?? 0, - inactive: data['offline']?.length ?? 0, - warning: data['warn']?.length ?? 0, - unused: data['not-use']?.length ?? 0); + isOwner: true, + total: data['all']?.length ?? 0, + active: data['online']?.length ?? 0, + inactive: data['offline']?.length ?? 0, + warning: data['warn']?.length ?? 0, + unused: data['not-use']?.length ?? 0, + showUnused: false, + ); } else { return DefaultTabController( length: 2, child: Column( + // mainAxisSize: MainAxisSize.min, + // crossAxisAlignment: CrossAxisAlignment.start, children: [ TabBar( tabs: [ - Tab(text: appLocalization(context).over_view_owner_devices), - Tab(text: appLocalization(context).over_view_joined_devices), + Tab( + text: appLocalization(context) + .over_view_owner_devices), + Tab( + text: appLocalization(context) + .over_view_joined_devices), ], labelColor: Colors.blue, unselectedLabelColor: Colors.grey, indicatorColor: Colors.blue, ), - SizedBox( - height: context.dynamicHeight(0.6), + ConstrainedBox( + constraints: BoxConstraints( + maxHeight: context.dynamicHeight(0.5), + minHeight: context.dynamicHeight(0.3), + ), child: TabBarView( children: [ OverviewCard( - isOwner: true, - total: data['all']?.length ?? 0, - active: data['online']?.length ?? 0, - inactive: - data['offline']?.length ?? 0, - warning: data['warn']?.length ?? 0, - unused: - data['not-use']?.length ?? 0), + isOwner: true, + total: data['all']?.length ?? 0, + active: data['online']?.length ?? 0, + inactive: + data['offline']?.length ?? 0, + warning: data['warn']?.length ?? 0, + unused: + data['not-use']?.length ?? 0, + showUnused: false, + ), OverviewCard( - isOwner: false, - total: - dataJoined['all']?.length ?? 0, - active: - dataJoined['online']?.length ?? - 0, - inactive: - dataJoined['offline']?.length ?? - 0, - warning: - dataJoined['warn']?.length ?? 0, - unused: - dataJoined['not-use']?.length ?? - 0,), + isOwner: false, + total: + dataJoined['all']?.length ?? 0, + active: + dataJoined['online']?.length ?? + 0, + inactive: + dataJoined['offline']?.length ?? + 0, + warning: + dataJoined['warn']?.length ?? 0, + unused: + dataJoined['not-use']?.length ?? + 0, + showUnused: false, + showActive: false, + showInactive: false, + ), ], ), ), @@ -289,19 +309,21 @@ class _HomeScreenState extends State { } void getOwnerAndJoinedDevices() async { - String response = await apiServices.getDashBoardDevices(); - final data = jsonDecode(response); - List result = data["items"]; - devices = DeviceWithAlias.fromJsonDynamicList(result); - List publicDevices = []; - for (var device in devices) { - if (device.visibility == "PUBLIC") { - publicDevices.add(device); + try { + devices = await apiServices.getDashBoardDevices(); + List publicDevices = []; + for (var device in devices) { + if (device.visibility == "PUBLIC") { + publicDevices.add(device); + } } + getOwnerDeviceState(publicDevices); + checkSettingDevice(publicDevices); + getDeviceStatusAliasMap(publicDevices); + } catch (e) { + if (!mounted) return; + showErrorTopSnackBarCustom(context, e.toString()); } - getOwnerDeviceState(publicDevices); - checkSettingDevice(publicDevices); - getDeviceStatusAliasMap(publicDevices); } void getOwnerDeviceState(List allDevices) async { @@ -391,33 +413,36 @@ class _HomeScreenState extends State { } void checkSettingDevice(List devices) async { - if (isFunctionCall) { - log("Ham check setting da duoc goi"); - } else { - String? response = - await apiServices.getAllSettingsNotificationOfDevices(); - if (response != "") { - final data = jsonDecode(response); - final result = data['data']; - // log("Data ${DeviceNotificationSettings.mapFromJson(jsonDecode(data)).values.toList()}"); + try { + if (isFunctionCall) { + log("Ham check setting da duoc goi"); + } else { List list = - DeviceNotificationSettings.mapFromJson(result).values.toList(); + await apiServices.getAllSettingsNotificationOfDevices(); // log("List: $list"); Set thingIdsInList = list.map((device) => device.thingId!).toSet(); for (var device in devices) { if (!thingIdsInList.contains(device.thingId)) { log("Device with Thing ID ${device.thingId} is not in the notification settings list."); - await apiServices.setupDeviceNotification( - device.thingId!, device.name!); + try { + await apiServices.setupDeviceNotification( + device.thingId!, device.name!); + } catch (e) { + if (!mounted) return; + showErrorTopSnackBarCustom( + context, e.toString()); + } + } else { log("All devices are in the notification settings list."); } } - } else { - log("apiServices: getAllSettingsNotificationofDevices error!"); } + isFunctionCall = true; + } catch (e) { + if (!mounted) return; + showErrorTopSnackBarCustom(context, e.toString()); } - isFunctionCall = true; } } diff --git a/lib/feature/home/shared/alert_card.dart b/lib/feature/home/shared/alert_card.dart index 12858b0..c3901c2 100644 --- a/lib/feature/home/shared/alert_card.dart +++ b/lib/feature/home/shared/alert_card.dart @@ -17,7 +17,7 @@ Future notificationCard(BuildContext context, String notiticationType, String location = ""; if (device.areaPath != "") { location = await DeviceUtils.instance - .getFullDeviceLocation(context, device.areaPath!); + .getFullDeviceLocation(context, device.areaPath!,""); } String path = ""; // DateTime time = DateTime.now(); diff --git a/lib/feature/home/shared/overview_card.dart b/lib/feature/home/shared/overview_card.dart index 46d3e12..9b95680 100644 --- a/lib/feature/home/shared/overview_card.dart +++ b/lib/feature/home/shared/overview_card.dart @@ -3,69 +3,97 @@ import 'status_card.dart'; import '../../../product/extension/context_extension.dart'; import '../../../product/services/language_services.dart'; -class OverviewCard extends StatelessWidget { +class OverviewCard extends StatefulWidget { final bool isOwner; final int total; final int active; final int inactive; final int warning; final int unused; + final bool showTotal; + final bool showActive; + final bool showInactive; + final bool showWarning; + final bool showUnused; - const OverviewCard( - {super.key, - required this.isOwner, - required this.total, - required this.active, - required this.inactive, - required this.warning, - required this.unused}); + const OverviewCard({ + super.key, + required this.isOwner, + required this.total, + required this.active, + required this.inactive, + required this.warning, + required this.unused, + this.showTotal = true, + this.showActive = true, + this.showInactive = true, + this.showWarning = true, + this.showUnused = true, + }); + @override + State createState() => _OverviewCardState(); +} + +class _OverviewCardState extends State { @override Widget build(BuildContext context) { - return Card( - elevation: 8, - shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(15)), - child: Padding( - padding: context.paddingNormal, - child: Column( - children: [ - Text( - isOwner - ? appLocalization(context).overview_message - : appLocalization(context).interfamily_page_name, - style: context.h2, - ), - SizedBox(height: context.normalValue), - Column( + return FittedBox( + alignment: Alignment.topCenter, + child: SizedBox( + width: context.width, + child: Card( + // elevation: 8, + shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(15)), + child: Padding( + padding: context.paddingNormal, + child: Column( + mainAxisSize: MainAxisSize.min, children: [ - StatusCard( - label: appLocalization(context).total_nof_devices_message, - count: total, - color: Colors.blue, + Text( + widget.isOwner + ? appLocalization(context).overview_message + : appLocalization(context).interfamily_page_name, + style: context.h2, ), - StatusCard( - label: appLocalization(context).active_devices_message, - count: active, - color: Colors.green, - ), - StatusCard( - label: appLocalization(context).inactive_devices_message, - count: inactive, - color: Colors.grey, - ), - StatusCard( - label: appLocalization(context).warning_devices_message, - count: warning, - color: Colors.orange, - ), - StatusCard( - label: appLocalization(context).unused_devices_message, - count: unused, - color: Colors.yellow, + SizedBox(height: context.normalValue), + Column( + children: [ + if (widget.showTotal) + StatusCard( + label: appLocalization(context).total_nof_devices_message, + count: widget.total, + color: Colors.blue, + ), + if (widget.showActive) + StatusCard( + label: appLocalization(context).active_devices_message, + count: widget.active, + color: Colors.green, + ), + if (widget.showInactive) + StatusCard( + label: appLocalization(context).inactive_devices_message, + count: widget.inactive, + color: Colors.grey, + ), + if (widget.showWarning) + StatusCard( + label: appLocalization(context).warning_devices_message, + count: widget.warning, + color: Colors.orange, + ), + if (widget.showUnused) + StatusCard( + label: appLocalization(context).unused_devices_message, + count: widget.unused, + color: Colors.yellow, + ), + ], ), ], ), - ], + ), ), ), ); diff --git a/lib/feature/home/shared/warning_card.dart b/lib/feature/home/shared/warning_card.dart index a3c3390..2fcaec6 100644 --- a/lib/feature/home/shared/warning_card.dart +++ b/lib/feature/home/shared/warning_card.dart @@ -21,7 +21,7 @@ Future warningCard(BuildContext context, APIServices apiServices, String fullLocation = ""; if (device.areaPath != "") { fullLocation = await DeviceUtils.instance - .getFullDeviceLocation(context, device.areaPath!); + .getFullDeviceLocation(context, device.areaPath!,""); } String time = ""; for (var sensor in device.status!.sensors!) { @@ -209,22 +209,28 @@ Future warningCard(BuildContext context, APIServices apiServices, actions: [ TextButton( onPressed: () async { - int statusCode = await apiServices - .confirmFakeFireByUser(device.thingId!); - if (statusCode == 200) { - showNoIconTopSnackBar( - context, - appLocalization(context) - .notification_confirm_fake_fire_success, - Colors.green, - Colors.white); - } else { - showNoIconTopSnackBar( - context, - appLocalization(context) - .notification_confirm_fake_fire_failed, - Colors.red, - Colors.red); + try { + int statusCode = await apiServices + .confirmFakeFireByUser(device.thingId!); + if (statusCode == 200) { + showNoIconTopSnackBar( + context, + appLocalization(context) + .notification_confirm_fake_fire_success, + Colors.green, + Colors.white); + } else { + showNoIconTopSnackBar( + context, + appLocalization(context) + .notification_confirm_fake_fire_failed, + Colors.red, + Colors.red); + } + } catch (e) { + if (!context.mounted) return; + showErrorTopSnackBarCustom( + context, e.toString()); } Navigator.of(context).pop(); }, diff --git a/lib/feature/inter_family/group_detail/add_device_to_group_dialog.dart b/lib/feature/inter_family/group_detail/add_device_to_group_dialog.dart index 16e07bf..73b7666 100644 --- a/lib/feature/inter_family/group_detail/add_device_to_group_dialog.dart +++ b/lib/feature/inter_family/group_detail/add_device_to_group_dialog.dart @@ -12,7 +12,7 @@ import 'group_detail_model.dart'; addDeviceDialog(BuildContext context, DetailGroupBloc detailGroupBloc, String groupID, List devices) async { - List ownerDevices = await detailGroupBloc.getOwnerDevices(); + List ownerDevices = await detailGroupBloc.getOwnerDevices(context); List selectedItems = []; List selectedDevices = []; if (devices.isNotEmpty) { @@ -131,7 +131,7 @@ addDeviceDialog(BuildContext context, DetailGroupBloc detailGroupBloc, for (var device in selectedItems) { await detailGroupBloc.addDeviceToGroup( context, groupID, device); - await detailGroupBloc.getGroupDetail(groupID); + await detailGroupBloc.getGroupDetail(context,groupID); } Navigator.of(dialogContext).pop(); diff --git a/lib/feature/inter_family/group_detail/group_detail_screen.dart b/lib/feature/inter_family/group_detail/group_detail_screen.dart index 109f8c7..a94ca06 100644 --- a/lib/feature/inter_family/group_detail/group_detail_screen.dart +++ b/lib/feature/inter_family/group_detail/group_detail_screen.dart @@ -4,6 +4,8 @@ import 'dart:async'; import 'package:flutter/material.dart'; import '../../../bloc/group_detail_bloc.dart'; +import '../../../product/shared/shared_loading_animation.dart'; +import '../../../product/shared/shared_snack_bar.dart'; import 'group_detail_model.dart'; import '../../../product/base/bloc/base_bloc.dart'; import '../../../product/constant/app/app_constants.dart'; @@ -35,15 +37,13 @@ class _DetailGroupScreenState extends State { const duration = Duration(seconds: 10); getGroupDetailTimer = Timer.periodic( duration, - (Timer t) => detailGroupBloc.getGroupDetail(widget.group), + (Timer t) => detailGroupBloc.getGroupDetail(context, widget.group), ); } - @override void dispose() { getGroupDetailTimer?.cancel(); - // Thieeus hamf super.dispose(); } @@ -53,10 +53,8 @@ class _DetailGroupScreenState extends State { stream: detailGroupBloc.streamDetailGroup, builder: (context, detailGroupSnapshot) { if (detailGroupSnapshot.data?.id == null) { - detailGroupBloc.getGroupDetail(widget.group); - return const Center( - child: CircularProgressIndicator(), - ); + detailGroupBloc.getGroupDetail(context, widget.group); + return const SharedLoadingAnimation(); } else { return Scaffold( key: scaffoldKey, @@ -142,8 +140,8 @@ class _DetailGroupScreenState extends State { widget.group, user.id!, user.name!); - detailGroupBloc - .getGroupDetail(widget.group); + detailGroupBloc.getGroupDetail( + context, widget.group); }, icon: const Icon( Icons.check, @@ -160,8 +158,8 @@ class _DetailGroupScreenState extends State { widget.group, user.id!, user.name!); - await detailGroupBloc - .getGroupDetail(widget.group); + await detailGroupBloc.getGroupDetail( + context, widget.group); }, icon: const Icon( Icons.close, @@ -207,8 +205,8 @@ class _DetailGroupScreenState extends State { widget.group, user.id!, user.name!); - await detailGroupBloc - .getGroupDetail(widget.group); + await detailGroupBloc.getGroupDetail( + context, widget.group); }, value: 2, child: Text(appLocalization(context) @@ -241,7 +239,7 @@ class _DetailGroupScreenState extends State { ? PopupMenuButton( icon: IconConstants.instance .getMaterialIcon(Icons.more_horiz), - itemBuilder: (contex) => [ + itemBuilder: (context) => [ PopupMenuItem( onTap: () { Navigator.pop(context); @@ -327,15 +325,21 @@ class _DetailGroupScreenState extends State { Future.delayed(context.lowDuration).then( (value) => Navigator.pop(context), ); - int statusCode = await apiServices - .deleteGroup(widget.group); - showSnackBarResponseByStatusCode( - context, - statusCode, - appLocalization(context) - .notification_delete_group_success, - appLocalization(context) - .notification_delete_group_failed); + try { + int statusCode = await apiServices + .deleteGroup(widget.group); + showSnackBarResponseByStatusCode( + context, + statusCode, + appLocalization(context) + .notification_delete_group_success, + appLocalization(context) + .notification_delete_group_failed); + } catch (e) { + if (!context.mounted) return; + showErrorTopSnackBarCustom( + context, e.toString()); + } }, child: Text( appLocalization(context) @@ -463,7 +467,8 @@ class _DetailGroupScreenState extends State { DeviceUtils.instance.checkStateDevice( context, devices[index].state!), style: TextStyle( - color: DeviceUtils.instance.getTableRowColor(context, + color: DeviceUtils.instance.getTableRowColor( + context, devices[index].state!, ), ), @@ -525,7 +530,7 @@ class _DetailGroupScreenState extends State { String alias = aliasController.text; await detailGroupBloc.updateDeviceNameInGroup( context, device.thingId!, alias); - await detailGroupBloc.getGroupDetail(widget.group); + await detailGroupBloc.getGroupDetail(context, widget.group); }, child: Text(appLocalization(context).confirm_button_content)), ) diff --git a/lib/feature/inter_family/groups/groups_screen.dart b/lib/feature/inter_family/groups/groups_screen.dart index c4078d1..57082a6 100644 --- a/lib/feature/inter_family/groups/groups_screen.dart +++ b/lib/feature/inter_family/groups/groups_screen.dart @@ -6,6 +6,8 @@ import 'package:flutter/material.dart'; import 'package:go_router/go_router.dart'; import '../../../product/constant/enums/app_route_enums.dart'; +import '../../../product/shared/shared_component_loading_animation.dart'; +import '../../../product/shared/shared_loading_animation.dart'; import 'groups_model.dart'; import '../../../bloc/inter_family_bloc.dart'; import '../inter_family_widget.dart'; @@ -54,64 +56,76 @@ class _GroupsScreenState extends State { return StreamBuilder>( stream: interFamilyBloc.streamCurrentGroups, builder: (context, groupsSnapshot) { - if(groupsSnapshot.data == null){ + if (groupsSnapshot.data == null) { interFamilyBloc.getAllGroup(widget.role); - return const Center(child: CircularProgressIndicator(),); - }else if(groupsSnapshot.data!.isEmpty){ - return Center(child: Text(appLocalization(context).dont_have_group),); - }else { + return const SharedLoadingAnimation(); + } else if (groupsSnapshot.data!.isEmpty) { + return Center( + child: Text(appLocalization(context).dont_have_group), + ); + } else { return Scaffold( body: groupsSnapshot.data?.isEmpty ?? true - ? const Center( - child: CircularProgressIndicator(), - ) + ? const SharedComponentLoadingAnimation() : ListView.builder( - itemCount: groupsSnapshot.data!.length, - itemBuilder: (context, index) { - return ListTile( - onTap: () { - context.pushNamed(AppRoutes.GROUP_DETAIL.name, - pathParameters: {"groupId": groupsSnapshot.data![index].id!}, - extra: widget.role); - }, - leading: IconConstants.instance.getMaterialIcon(Icons.diversity_2), - title: Text( - groupsSnapshot.data![index].name ?? '', - style: const TextStyle(fontWeight: FontWeight.bold), + itemCount: groupsSnapshot.data!.length, + itemBuilder: (context, index) { + return ListTile( + onTap: () { + context.pushNamed(AppRoutes.GROUP_DETAIL.name, + pathParameters: { + "groupId": groupsSnapshot.data![index].id! + }, + extra: widget.role); + }, + leading: IconConstants.instance + .getMaterialIcon(Icons.diversity_2), + title: Text( + groupsSnapshot.data![index].name ?? '', + style: const TextStyle(fontWeight: FontWeight.bold), + ), + subtitle: Text( + groupsSnapshot.data![index].description ?? ""), + trailing: + widget.role == ApplicationConstants.OWNER_GROUP + ? PopupMenuButton( + shape: const RoundedRectangleBorder( + borderRadius: BorderRadius.only( + bottomLeft: Radius.circular(8.0), + bottomRight: Radius.circular(8.0), + topLeft: Radius.circular(8.0), + topRight: Radius.circular(8.0), + ), + ), + itemBuilder: (ctx) => [ + _buildPopupMenuItem( + groupsSnapshot.data![index], + context, + appLocalization(context) + .share_group_title, + Icons.share, + 4), + _buildPopupMenuItem( + groupsSnapshot.data![index], + context, + appLocalization(context) + .change_group_infomation_title, + Icons.settings_backup_restore, + 2), + _buildPopupMenuItem( + groupsSnapshot.data![index], + context, + appLocalization(context) + .delete_group_title, + Icons.delete_forever_rounded, + 3), + ], + icon: const Icon(Icons.more_horiz), + ) + : const SizedBox.shrink(), + ); + }, ), - subtitle: Text(groupsSnapshot.data![index].description ?? ""), - trailing: widget.role == ApplicationConstants.OWNER_GROUP - ? PopupMenuButton( - shape: const RoundedRectangleBorder( - borderRadius: BorderRadius.only( - bottomLeft: Radius.circular(8.0), - bottomRight: Radius.circular(8.0), - topLeft: Radius.circular(8.0), - topRight: Radius.circular(8.0), - ), - ), - itemBuilder: (ctx) => [ - _buildPopupMenuItem(groupsSnapshot.data![index], context, - appLocalization(context).share_group_title, Icons.share, 4), - _buildPopupMenuItem( - groupsSnapshot.data![index], - context, - appLocalization(context).change_group_infomation_title, - Icons.settings_backup_restore, - 2), - _buildPopupMenuItem( - groupsSnapshot.data![index], - context, - appLocalization(context).delete_group_title, - Icons.delete_forever_rounded, - 3), - ], - icon: const Icon(Icons.more_horiz), - ) - : const SizedBox.shrink(), - ); - }, - ), ); } }, @@ -121,15 +135,16 @@ class _GroupsScreenState extends State { } } - PopupMenuItem _buildPopupMenuItem( - Group group, BuildContext context, String title, IconData iconData, int position) { + PopupMenuItem _buildPopupMenuItem(Group group, BuildContext context, + String title, IconData iconData, int position) { return PopupMenuItem( onTap: () { if (title == appLocalization(context).share_group_title) { Future.delayed(context.lowDuration, () { shareGroup(context, group); }); - } else if (title == appLocalization(context).change_group_infomation_title) { + } else if (title == + appLocalization(context).change_group_infomation_title) { Future.delayed(context.lowDuration, () { createOrJoinGroupDialog( context, diff --git a/lib/feature/inter_family/inter_family_widget.dart b/lib/feature/inter_family/inter_family_widget.dart index 9c65c97..ad4fe1e 100644 --- a/lib/feature/inter_family/inter_family_widget.dart +++ b/lib/feature/inter_family/inter_family_widget.dart @@ -131,7 +131,7 @@ createOrJoinGroupDialog( appLocalization(context) .change_group_infomation_content) { try { - await interFamilyBloc.changeGroupInfomation( + await interFamilyBloc.changeGroupInformation( context, groupID, groupName, description); interFamilyBloc.getAllGroup(role); Navigator.of(dialogContext).pop(); diff --git a/lib/feature/main/main_screen.dart b/lib/feature/main/main_screen.dart index 665d153..9c641d9 100644 --- a/lib/feature/main/main_screen.dart +++ b/lib/feature/main/main_screen.dart @@ -10,6 +10,7 @@ import 'package:flutter_local_notifications/flutter_local_notifications.dart'; import 'package:go_router/go_router.dart'; import 'package:badges/badges.dart' as badges; import 'package:persistent_bottom_nav_bar/persistent_bottom_nav_bar.dart'; +import 'package:sfm_app/product/shared/shared_snack_bar.dart'; import 'package:sfm_app/product/utils/permission_handler.dart'; import '../../product/permission/notification_permission.dart'; import '../../product/services/notification_services.dart'; @@ -100,7 +101,7 @@ class _MainScreenState extends State with WidgetsBindingObserver { WidgetsBinding.instance.addObserver(this); initialCheck(); getBellNotification(); - mainBloc.getUserProfile(); + mainBloc.getUserProfile(context); FirebaseMessaging.instance.onTokenRefresh.listen((newToken) { log("New FCM Token: $newToken"); @@ -412,8 +413,12 @@ class _MainScreenState extends State with WidgetsBindingObserver { } Future getBellNotification() async { - bell = await apiServices.getBellNotifications("0", "20"); - mainBloc.bellBloc.add(bell); + try{ + bell = await apiServices.getBellNotifications("0", "20"); + mainBloc.bellBloc.add(bell); + }catch(e){ + showErrorTopSnackBarCustom(context, e.toString()); + } } bool checkStatus(List bells) { diff --git a/lib/feature/map/map_screen.dart b/lib/feature/map/map_screen.dart index 90a00e9..6a798fe 100644 --- a/lib/feature/map/map_screen.dart +++ b/lib/feature/map/map_screen.dart @@ -17,6 +17,7 @@ import 'package:sfm_app/product/permission/location_permission.dart'; import 'package:sfm_app/product/services/api_services.dart'; import 'package:sfm_app/product/utils/permission_handler.dart'; import '../../product/constant/enums/app_theme_enums.dart'; +import '../../product/shared/shared_snack_bar.dart'; class MapScreen extends StatefulWidget { const MapScreen({super.key}); @@ -73,11 +74,11 @@ class _MapScreenState extends State with WidgetsBindingObserver { @override void dispose() { - super.dispose(); checkThemeTimer?.cancel(); getMarker?.cancel(); _controller = Completer(); streamController.close(); + super.dispose(); } void onMapCreated(GoogleMapController controller) { @@ -273,18 +274,18 @@ class _MapScreenState extends State with WidgetsBindingObserver { } void getAllMarkers() async { - String response = await apiServices.getOwnerDevices(); - if (response != "") { - final data = jsonDecode(response); - List result = data['items']; - if (result.isNotEmpty) { - devices.clear(); - final devicesList = Device.fromJsonDynamicList(result); - for (var device in devicesList) { - devices.add(device); - } - } else {} + try { + devices.clear(); + final devicesList = await apiServices.getOwnerDevices(); + for (var device in devicesList) { + devices.add(device); + } + } catch (e) { + if (!mounted) return; + showErrorTopSnackBarCustom( + context, e.toString()); } + } // Future checkLocationPermission(context) async { diff --git a/lib/feature/map/widget/on_tap_marker_widget.dart b/lib/feature/map/widget/on_tap_marker_widget.dart index d7dcbb0..1532c34 100644 --- a/lib/feature/map/widget/on_tap_marker_widget.dart +++ b/lib/feature/map/widget/on_tap_marker_widget.dart @@ -79,7 +79,7 @@ onTapMarker( destination, ); String deviceLocations = await DeviceUtils.instance - .getFullDeviceLocation(context, device.areaPath!); + .getFullDeviceLocation(context, device.areaPath!,device.name); String yourLocation = appLocalization(context).map_your_location; showDirections( @@ -88,7 +88,6 @@ onTapMarker( otherMarkers, mapBloc, yourLocation, - deviceLocations, double.parse(device.settings!.latitude!), double.parse(device.settings!.longitude!), diff --git a/lib/feature/settings/device_notification_settings/device_notification_settings_screen.dart b/lib/feature/settings/device_notification_settings/device_notification_settings_screen.dart index 9db6be3..867c369 100644 --- a/lib/feature/settings/device_notification_settings/device_notification_settings_screen.dart +++ b/lib/feature/settings/device_notification_settings/device_notification_settings_screen.dart @@ -111,14 +111,15 @@ class _DeviceNotificationSettingsScreenState } void getNotificationSetting() async { - String? response = await apiServices.getAllSettingsNotificationOfDevices(); - final data = jsonDecode(response); - final result = data['data']; - // log("Data ${DeviceNotificationSettings.mapFromJson(jsonDecode(data)).values.toList()}"); - deviceNotifications = - DeviceNotificationSettings.mapFromJson(result).values.toList(); - deviceNotificationSettingsBloc.sinkListNotifications - .add(deviceNotifications); + try { + deviceNotifications = + await apiServices.getAllSettingsNotificationOfDevices(); + deviceNotificationSettingsBloc.sinkListNotifications + .add(deviceNotifications); + } catch (e) { + if (!mounted) return; + showErrorTopSnackBarCustom(context, e.toString()); + } } Widget listNotificationSetting( @@ -292,22 +293,29 @@ class _DeviceNotificationSettingsScreenState void updateDeviceNotification(String thingID, Map notifiSettings, bool isDataChange) async { - int statusCode = await apiServices.updateDeviceNotificationSettings( - thingID, notifiSettings); - if (statusCode == 200) { - showNoIconTopSnackBar( - context, - appLocalization(context).notification_update_device_settings_success, - Colors.green, - Colors.white); - } else { - showNoIconTopSnackBar( - context, - appLocalization(context).notification_update_device_settings_failed, - Colors.red, - Colors.white); + try { + int statusCode = await apiServices.updateDeviceNotificationSettings( + thingID, notifiSettings); + if (statusCode == 200) { + showNoIconTopSnackBar( + context, + appLocalization(context).notification_update_device_settings_success, + Colors.green, + Colors.white); + } else { + showNoIconTopSnackBar( + context, + appLocalization(context).notification_update_device_settings_failed, + Colors.red, + Colors.white); + } + isDataChange = false; + deviceNotificationSettingsBloc.sinkIsDataChange.add(isDataChange); + } catch (e) { + if (!context.mounted) return; + showErrorTopSnackBarCustom( + context, e.toString()); } - isDataChange = false; - deviceNotificationSettingsBloc.sinkIsDataChange.add(isDataChange); + } } diff --git a/lib/feature/settings/profile/profile_screen.dart b/lib/feature/settings/profile/profile_screen.dart index 12b95dd..24f9f44 100644 --- a/lib/feature/settings/profile/profile_screen.dart +++ b/lib/feature/settings/profile/profile_screen.dart @@ -38,35 +38,42 @@ changeUserInfomation( ? IconButton( onPressed: () async { if (formKey.currentState!.validate()) { - formKey.currentState!.save(); - String latitude = user.latitude ?? ""; - String longitude = user.longitude ?? ""; - Map body = { - "name": username, - "email": email, - "phone": tel, - "address": address, - "latitude": latitude, - "longitude": longitude - }; - int statusCode = - await apiServices.updateUserProfile(body); - if (statusCode == 200) { - showNoIconTopSnackBar( - modalBottomSheetContext, - appLocalization(context) - .notification_update_profile_success, - Colors.green, - Colors.white); - } else { - showNoIconTopSnackBar( - modalBottomSheetContext, - appLocalization(context) - .notification_update_profile_failed, - Colors.redAccent, - Colors.white); + try { + formKey.currentState!.save(); + String latitude = user.latitude ?? ""; + String longitude = user.longitude ?? ""; + Map body = { + "name": username, + "email": email, + "phone": tel, + "address": address, + "latitude": latitude, + "longitude": longitude + }; + int statusCode = + await apiServices.updateUserProfile(body); + if (statusCode == 200) { + showNoIconTopSnackBar( + modalBottomSheetContext, + appLocalization(context) + .notification_update_profile_success, + Colors.green, + Colors.white); + } else { + showNoIconTopSnackBar( + modalBottomSheetContext, + appLocalization(context) + .notification_update_profile_failed, + Colors.redAccent, + Colors.white); + } + settingsBloc.getUserProfile(context); + Navigator.pop(modalBottomSheetContext); + } catch (e) { + if (!context.mounted) return; + showErrorTopSnackBarCustom( + context, e.toString()); } - Navigator.pop(modalBottomSheetContext); } }, icon: @@ -205,35 +212,42 @@ changeUserInfomation( child: TextButton( onPressed: () async { if (formKey.currentState!.validate()) { - formKey.currentState!.save(); - String latitude = user.latitude ?? ""; - String longitude = user.longitude ?? ""; - Map body = { - "name": username, - "email": email, - "phone": tel, - "address": address, - "latitude": latitude, - "longitude": longitude - }; - int statusCode = await apiServices - .updateUserProfile(body); - if (statusCode == 200) { - showNoIconTopSnackBar( - modalBottomSheetContext, - appLocalization(context) - .notification_update_profile_success, - Colors.green, - Colors.white); - } else { - showNoIconTopSnackBar( - modalBottomSheetContext, - appLocalization(context) - .notification_update_profile_failed, - Colors.redAccent, - Colors.white); + try { + formKey.currentState!.save(); + String latitude = user.latitude ?? ""; + String longitude = user.longitude ?? ""; + Map body = { + "name": username, + "email": email, + "phone": tel, + "address": address, + "latitude": latitude, + "longitude": longitude + }; + int statusCode = + await apiServices.updateUserProfile(body); + if (statusCode == 200) { + showNoIconTopSnackBar( + modalBottomSheetContext, + appLocalization(context) + .notification_update_profile_success, + Colors.green, + Colors.white); + } else { + showNoIconTopSnackBar( + modalBottomSheetContext, + appLocalization(context) + .notification_update_profile_failed, + Colors.redAccent, + Colors.white); + } + settingsBloc.getUserProfile(context); + Navigator.pop(modalBottomSheetContext); + } catch (e) { + if (!context.mounted) return; + showErrorTopSnackBarCustom( + context, e.toString()); } - Navigator.pop(modalBottomSheetContext); } }, style: const ButtonStyle( @@ -282,30 +296,36 @@ changeUserPassword(BuildContext context, SettingsBloc settingsBloc) { isChangeSnapshot.data ?? isChange ? IconButton( onPressed: () async { - if (formKey.currentState!.validate()) { - formKey.currentState!.save(); - Map body = { - "password_old": oldPass, - "password_new": newPass, - }; - int statusCode = - await apiServices.updateUserPassword(body); - if (statusCode == 200) { - showNoIconTopSnackBar( - modalBottomSheetContext, - appLocalization(context) - .notification_update_password_success, - Colors.green, - Colors.white); - } else { - showNoIconTopSnackBar( - modalBottomSheetContext, - appLocalization(context) - .notification_update_password_failed, - Colors.redAccent, - Colors.white); + try { + if (formKey.currentState!.validate()) { + formKey.currentState!.save(); + Map body = { + "password_old": oldPass, + "password_new": newPass, + }; + int statusCode = + await apiServices.updateUserPassword(body); + if (statusCode == 200) { + showNoIconTopSnackBar( + modalBottomSheetContext, + appLocalization(context) + .notification_update_password_success, + Colors.green, + Colors.white); + } else { + showNoIconTopSnackBar( + modalBottomSheetContext, + appLocalization(context) + .notification_update_password_failed, + Colors.redAccent, + Colors.white); + } + Navigator.pop(modalBottomSheetContext); } - Navigator.pop(modalBottomSheetContext); + } catch (e) { + if (!context.mounted) return; + showErrorTopSnackBarCustom( + context, e.toString()); } }, icon: @@ -390,30 +410,36 @@ changeUserPassword(BuildContext context, SettingsBloc settingsBloc) { ? Center( child: TextButton( onPressed: () async { - if (formKey.currentState!.validate()) { - formKey.currentState!.save(); - Map body = { - "password_old": oldPass, - "password_new": newPass, - }; - int statusCode = await apiServices - .updateUserPassword(body); - if (statusCode == 200) { - showNoIconTopSnackBar( - modalBottomSheetContext, - appLocalization(context) - .notification_update_password_success, - Colors.green, - Colors.white); - } else { - showNoIconTopSnackBar( - modalBottomSheetContext, - appLocalization(context) - .notification_update_password_failed, - Colors.redAccent, - Colors.white); + try { + if (formKey.currentState!.validate()) { + formKey.currentState!.save(); + Map body = { + "password_old": oldPass, + "password_new": newPass, + }; + int statusCode = + await apiServices.updateUserPassword(body); + if (statusCode == 200) { + showNoIconTopSnackBar( + modalBottomSheetContext, + appLocalization(context) + .notification_update_password_success, + Colors.green, + Colors.white); + } else { + showNoIconTopSnackBar( + modalBottomSheetContext, + appLocalization(context) + .notification_update_password_failed, + Colors.redAccent, + Colors.white); + } + Navigator.pop(modalBottomSheetContext); } - Navigator.pop(modalBottomSheetContext); + } catch (e) { + if (!context.mounted) return; + showErrorTopSnackBarCustom( + context, e.toString()); } }, style: const ButtonStyle( diff --git a/lib/feature/settings/settings_screen.dart b/lib/feature/settings/settings_screen.dart index 24bb0c8..dc6b09c 100644 --- a/lib/feature/settings/settings_screen.dart +++ b/lib/feature/settings/settings_screen.dart @@ -3,6 +3,8 @@ import 'dart:convert'; import 'package:flutter/material.dart'; import 'package:go_router/go_router.dart'; import '../../product/constant/app/app_constants.dart'; +import '../../product/shared/shared_loading_animation.dart'; +import '../../product/shared/shared_snack_bar.dart'; import 'profile/profile_screen.dart'; import '../../product/constant/icon/icon_constants.dart'; import '../../product/extension/context_extension.dart'; @@ -27,7 +29,7 @@ class _SettingsScreenState extends State { void initState() { super.initState(); settingsBloc = BlocProvider.of(context); - getUserProfile(); + // getUserProfile(); } @override @@ -38,76 +40,75 @@ class _SettingsScreenState extends State { centerTitle: true, ), body: StreamBuilder( - stream: settingsBloc.streamUserProfile, - initialData: user, - builder: (context, userSnapshot) { - return userSnapshot.data?.id == "" || user.id == "" - ? Center( - child: CircularProgressIndicator( - value: context.highValue, + stream: settingsBloc.streamUserProfile, + // initialData: user, + builder: (context, userSnapshot) { + if (userSnapshot.data == null) { + settingsBloc.getUserProfile(context); + return const SharedLoadingAnimation(); + } else { + return Column( + mainAxisAlignment: MainAxisAlignment.center, + crossAxisAlignment: CrossAxisAlignment.center, + children: [ + CircleAvatar( + backgroundColor: Theme.of(context).focusColor, + radius: 70, + child: CircleAvatar( + backgroundColor: Theme.of(context).highlightColor, + radius: 60, + child: CircleAvatar( + radius: 50, + child: Text( + getAvatarContent(userSnapshot.data?.username ?? ""), + style: context.dynamicResponsiveSize(36), + ), ), - ) - : Column( - mainAxisAlignment: MainAxisAlignment.center, - crossAxisAlignment: CrossAxisAlignment.center, - children: [ - CircleAvatar( - backgroundColor: Theme.of(context).focusColor, - radius: 70, - child: CircleAvatar( - backgroundColor: Theme.of(context).highlightColor, - radius: 60, - child: CircleAvatar( - radius: 50, - child: Text( - getAvatarContent( - userSnapshot.data?.username ?? ""), - style: context.dynamicResponsiveSize(36), - ), - ), - ), - ), - SizedBox(height: context.lowValue), - Row( - mainAxisAlignment: MainAxisAlignment.center, - children: [ - Text( - userSnapshot.data?.name ?? "User Name", - style: context.h2, - ) - ], - ), - Row( - mainAxisAlignment: MainAxisAlignment.center, - children: [Text(userSnapshot.data?.email ?? "Email")], - ), - SizedBox(height: context.mediumValue), - cardContent( - Icons.account_circle_rounded, - appLocalization(context).profile_change_info, - ), - SizedBox(height: context.lowValue), - cardContent( - Icons.lock_outline, - appLocalization(context).profile_change_pass, - ), - SizedBox(height: context.lowValue), - cardContent( - Icons.settings_outlined, - appLocalization(context).profile_setting, - ), - SizedBox(height: context.lowValue), - cardContent( - Icons.logout_outlined, - appLocalization(context).log_out, - ), - ], - ); - }), + ), + ), + SizedBox(height: context.lowValue), + Row( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + Text( + userSnapshot.data?.name ?? "User Name", + style: context.h2, + ) + ], + ), + Row( + mainAxisAlignment: MainAxisAlignment.center, + children: [Text(userSnapshot.data?.email ?? "Email")], + ), + SizedBox(height: context.mediumValue), + cardContent( + Icons.account_circle_rounded, + appLocalization(context).profile_change_info, + userSnapshot.data ?? user), + SizedBox(height: context.lowValue), + cardContent( + Icons.lock_outline, + appLocalization(context).profile_change_pass, + userSnapshot.data ?? user), + SizedBox(height: context.lowValue), + cardContent( + Icons.settings_outlined, + appLocalization(context).profile_setting, + userSnapshot.data ?? user), + SizedBox(height: context.lowValue), + cardContent( + Icons.logout_outlined, + appLocalization(context).log_out, + userSnapshot.data ?? user), + ], + ); + } + }, + ), ); } - cardContent(IconData icon, String content) { + cardContent(IconData icon, String content, User user) { return GestureDetector( onTap: () async { if (icon == Icons.account_circle_rounded) { @@ -138,11 +139,16 @@ class _SettingsScreenState extends State { ); } - void getUserProfile() async { - String data = await apiServices.getUserDetail(); - user = User.fromJson(jsonDecode(data)); - settingsBloc.sinkUserProfile.add(user); - } + // void getUserProfile() async { + // try { + // user = await apiServices.getUserDetail(); + // settingsBloc.sinkUserProfile.add(user); + // } catch (e) { + // if (!mounted) return; + // showErrorTopSnackBarCustom( + // context, e.toString()); + // } + // } String getAvatarContent(String username) { String name = ""; diff --git a/lib/product/constant/app/app_constants.dart b/lib/product/constant/app/app_constants.dart index cffe097..dc156e8 100644 --- a/lib/product/constant/app/app_constants.dart +++ b/lib/product/constant/app/app_constants.dart @@ -25,4 +25,5 @@ class ApplicationConstants { static const PARTICIPANT_GROUP = "participant"; static const NO_DATA = "no_data"; static const LOADING = "loading"; + static int CALL_API_TIMEOUT = 15; } diff --git a/lib/product/network/network_manager.dart b/lib/product/network/network_manager.dart index ff0c335..919bbcd 100644 --- a/lib/product/network/network_manager.dart +++ b/lib/product/network/network_manager.dart @@ -1,3 +1,4 @@ +import 'dart:async'; import 'dart:convert'; import 'dart:developer'; @@ -12,6 +13,7 @@ class NetworkManager { NetworkManager._init(); static NetworkManager? _instance; static NetworkManager? get instance => _instance ??= NetworkManager._init(); + Future> getHeaders() async { String? token = @@ -35,15 +37,24 @@ class NetworkManager { /// [String] if the request is successful (status code 200), or an empty /// string if the request fails Future getDataFromServer(String path) async { - final url = Uri.https(ApplicationConstants.DOMAIN, path); - log("[${DateTime.now().toLocal().toString().split(' ')[1]}] GET url: $url"); - final headers = await getHeaders(); - final response = await http.get(url, headers: headers); - if (response.statusCode == StatusCodeConstants.OK || - response.statusCode == StatusCodeConstants.CREATED) { - return response.body; - } else { - return ""; + try { + final url = Uri.https(ApplicationConstants.DOMAIN, path); + log("[${DateTime.now().toLocal().toString().split(' ')[1]}] GET url: $url"); + final headers = await getHeaders(); + final response = await http.get(url, headers: headers).timeout( + Duration(seconds: ApplicationConstants.CALL_API_TIMEOUT), + onTimeout: () => + throw TimeoutException('Yêu cầu GET hết thời gian'), + ); + if (response.statusCode == StatusCodeConstants.OK || + response.statusCode == StatusCodeConstants.CREATED) { + return response.body; + } else { + throw Exception('Lỗi server: ${response.statusCode}'); + } + } catch (e, stackTrace) { + log('Lỗi khi lấy dữ liệu: $e, StackTrace: $stackTrace'); + throw Exception('Lỗi khi lấy dữ liệu: $e'); } } @@ -60,6 +71,25 @@ class NetworkManager { /// Returns a [Future] containing the server response body. Future getDataFromServerWithParams( String path, Map params) async { + try { + final url = Uri.https(ApplicationConstants.DOMAIN, path, params); + log("[${DateTime.now().toLocal().toString().split(' ')[1]}] GET Params url: $url"); + final headers = await getHeaders(); + final response = await http.get(url, headers: headers).timeout( + Duration(seconds: ApplicationConstants.CALL_API_TIMEOUT), + onTimeout: () => + throw TimeoutException('Yêu cầu GET+PARAM hết thời gian'), + ); + if (response.statusCode == StatusCodeConstants.OK || + response.statusCode == StatusCodeConstants.CREATED) { + return response.body; + } else { + throw Exception('Lỗi server: ${response.statusCode}'); + } + } catch (e, stackTrace) { + log('Lỗi khi lấy dữ liệu: $e, StackTrace: $stackTrace'); + throw Exception('Lỗi khi lấy dữ liệu: $e'); + } final url = Uri.https(ApplicationConstants.DOMAIN, path, params); log("[${DateTime.now().toLocal().toString().split(' ')[1]}] GET Params url: $url"); final headers = await getHeaders(); @@ -77,12 +107,27 @@ class NetworkManager { /// [path] is the endpoint for the request, and [body] contains the data /// to be sent. Returns the HTTP status code of the response. Future createDataInServer(String path, Map body) async { - final url = Uri.https(ApplicationConstants.DOMAIN, path); - log("[${DateTime.now().toLocal().toString().split(' ')[1]}] POST url: $url"); - final headers = await getHeaders(); - final response = - await http.post(url, headers: headers, body: jsonEncode(body)); - return response.statusCode; + try { + final url = Uri.https(ApplicationConstants.DOMAIN, path); + log("[${DateTime.now().toLocal().toString().split(' ')[1]}] POST url: $url"); + final headers = await getHeaders(); + final response = await http + .post(url, headers: headers, body: jsonEncode(body)) + .timeout( + Duration(seconds: ApplicationConstants.CALL_API_TIMEOUT), + onTimeout: () => + throw TimeoutException('Yêu cầu POST hết thời gian'), + ); + if (response.statusCode == StatusCodeConstants.OK || + response.statusCode == StatusCodeConstants.CREATED) { + return response.statusCode; + } else { + throw Exception('Lỗi server: ${response.statusCode}'); + } + } catch (e, stackTrace) { + log('Lỗi khi lấy dữ liệu: $e, StackTrace: $stackTrace'); + throw Exception('Lỗi khi lấy dữ liệu: $e'); + } } /// Updates existing data on the server using a PUT request. @@ -90,12 +135,26 @@ class NetworkManager { /// [path] is the endpoint for the request, and [body] contains the data /// to be updated. Returns the HTTP status code of the response. Future updateDataInServer(String path, Map body) async { - final url = Uri.https(ApplicationConstants.DOMAIN, path); - log("[${DateTime.now().toLocal().toString().split(' ')[1]}] PUT url: $url"); - final headers = await getHeaders(); - final response = - await http.put(url, headers: headers, body: jsonEncode(body)); - return response.statusCode; + try { + final url = Uri.https(ApplicationConstants.DOMAIN, path); + log("[${DateTime.now().toLocal().toString().split(' ')[1]}] PUT url: $url"); + final headers = await getHeaders(); + final response = + await http.put(url, headers: headers, body: jsonEncode(body)).timeout( + Duration(seconds: ApplicationConstants.CALL_API_TIMEOUT), + onTimeout: () => + throw TimeoutException('Yêu cầu PUT hết thời gian'), + ); + if (response.statusCode == StatusCodeConstants.OK || + response.statusCode == StatusCodeConstants.CREATED) { + return response.statusCode; + } else { + throw Exception('Lỗi server: ${response.statusCode}'); + } + } catch (e, stackTrace) { + log('Lỗi khi lấy dữ liệu: $e, StackTrace: $stackTrace'); + throw Exception('Lỗi khi lấy dữ liệu: $e'); + } } /// Deletes data from the server using a DELETE request. @@ -105,10 +164,24 @@ class NetworkManager { /// A status code of 200 indicates success, while other codes indicate /// failure or an error. Future deleteDataInServer(String path) async { - final url = Uri.https(ApplicationConstants.DOMAIN, path); - log("[${DateTime.now().toLocal().toString().split(' ')[1]}] DELETE url: $url"); - final headers = await getHeaders(); - final response = await http.delete(url, headers: headers); - return response.statusCode; + try { + final url = Uri.https(ApplicationConstants.DOMAIN, path); + log("[${DateTime.now().toLocal().toString().split(' ')[1]}] DELETE url: $url"); + final headers = await getHeaders(); + final response = await http.delete(url, headers: headers).timeout( + Duration(seconds: ApplicationConstants.CALL_API_TIMEOUT), + onTimeout: () => + throw TimeoutException('Yêu cầu DELETE hết thời gian'), + ); + if (response.statusCode == StatusCodeConstants.OK || + response.statusCode == StatusCodeConstants.CREATED) { + return response.statusCode; + } else { + throw Exception('Lỗi server: ${response.statusCode}'); + } + } catch (e, stackTrace) { + log('Lỗi khi lấy dữ liệu: $e, StackTrace: $stackTrace'); + throw Exception('Lỗi khi lấy dữ liệu: $e'); + } } } diff --git a/lib/product/services/api_services.dart b/lib/product/services/api_services.dart index 2152f49..52fef83 100644 --- a/lib/product/services/api_services.dart +++ b/lib/product/services/api_services.dart @@ -1,10 +1,21 @@ // ignore_for_file: use_build_context_synchronously +import 'dart:async'; import 'dart:convert'; import 'package:flutter/material.dart'; import 'package:go_router/go_router.dart'; import 'package:http/http.dart' as http; +import 'package:sfm_app/product/shared/model/province_model.dart'; +import '../../feature/device_log/device_logs_model.dart'; +import '../../feature/devices/device_model.dart'; +import '../../feature/home/device_alias_model.dart'; +import '../../feature/inter_family/group_detail/group_detail_model.dart'; +import '../../feature/inter_family/groups/groups_model.dart'; +import '../../feature/settings/device_notification_settings/device_notification_settings_model.dart'; +import '../../feature/settings/profile/profile_model.dart'; import '../constant/app/api_path_constant.dart'; +import '../shared/model/district_model.dart'; +import '../shared/model/ward_model.dart'; import '../shared/shared_snack_bar.dart'; import '../constant/enums/app_route_enums.dart'; import 'language_services.dart'; @@ -41,6 +52,40 @@ class APIServices { return headers; } + Future executeApiCall( + Future Function() apiCall, { + T Function(dynamic)? parser, + String errorMessage = 'Lỗi khi gọi API', + T Function(int)? statusCodeHandler, // Thêm handler cho statusCode + }) async { + try { + final response = await apiCall().timeout( + Duration(seconds: ApplicationConstants.CALL_API_TIMEOUT), + onTimeout: () => throw TimeoutException('Yêu cầu hết thời gian'), + ); + + if (statusCodeHandler != null && response is int) { + return statusCodeHandler(response); + } + + if (response is String && response != "") { + if (parser != null) { + try { + return parser(jsonDecode(response)); + } catch (e) { + throw Exception('Lỗi parsing dữ liệu: $e'); + } + } + return response as T; + } else { + throw Exception('Dữ liệu trả về rỗng'); + } + } catch (e, stackTrace) { + // log('Lỗi API: $e, StackTrace: $stackTrace'); + throw Exception('$errorMessage: $e'); + } + } + Future login(String path, Map loginRequest) async { final url = Uri.https(ApplicationConstants.DOMAIN, path); final headers = await getHeaders(); @@ -49,14 +94,11 @@ class APIServices { return response.body; } - Future sendNotificationToken(String token) async{ + Future sendNotificationToken(String token) async { String uid = await getUID(); - Map body = { - "user_id": uid, - "app_token": token - }; - int statusCode = await NetworkManager.instance!.updateDataInServer( - APIPathConstants.NOTIFICATION_TOKEN_PATH, body); + Map body = {"user_id": uid, "app_token": token}; + int statusCode = await NetworkManager.instance! + .updateDataInServer(APIPathConstants.NOTIFICATION_TOKEN_PATH, body); return statusCode; } @@ -122,67 +164,88 @@ class APIServices { return language; } - Future getBellNotifications(String offset, String pagesize) async { - Bell bell = Bell(); - final params = {"offset": offset, "page_size": pagesize}; - final data = await NetworkManager.instance!.getDataFromServerWithParams( - APIPathConstants.BELL_NOTIFICATIONS_PATH, params); - if (data != "") { - bell = Bell.fromJson(jsonDecode(data)); - return bell; - } else { - return bell; - } + Future getBellNotifications(String offset, String pageSize) async { + final params = {"offset": offset, "page_size": pageSize}; + return executeApiCall( + () => NetworkManager.instance!.getDataFromServerWithParams( + APIPathConstants.BELL_NOTIFICATIONS_PATH, params), + parser: (json) => Bell.fromJson(json), + errorMessage: 'Lỗi khi GET /${APIPathConstants.BELL_NOTIFICATIONS_PATH}', + ); } Future updateStatusOfNotification(List notificationID) async { Map body = { "event_ids": notificationID, }; - int statusCode = await NetworkManager.instance!.updateDataInServer( - APIPathConstants.BELL_UPDATE_READ_NOTIFICATIONS_PATH, body); - return statusCode; + return executeApiCall( + () => NetworkManager.instance!.updateDataInServer( + APIPathConstants.BELL_UPDATE_READ_NOTIFICATIONS_PATH, body), + statusCodeHandler: (statusCode) => statusCode, + errorMessage: 'Lỗi khi PUT /${APIPathConstants.BELL_NOTIFICATIONS_PATH}', + ); } - Future getUserDetail() async { + Future getUserDetail() async { String uid = await getUID(); - String? response = await NetworkManager.instance! - .getDataFromServer('${APIPathConstants.USER_PATH}/$uid'); - return response; + return executeApiCall( + () => NetworkManager.instance! + .getDataFromServer('${APIPathConstants.USER_PATH}/$uid'), + parser: (json) => User.fromJson(json), + errorMessage: 'Lỗi khi GET /${APIPathConstants.USER_PATH}', + ); } Future updateUserProfile(Map body) async { String uid = await getUID(); - int statusCode = await NetworkManager.instance! - .updateDataInServer("${APIPathConstants.USER_PROFILE_PATH}/$uid", body); - return statusCode; + return executeApiCall( + () => NetworkManager.instance!.updateDataInServer( + "${APIPathConstants.USER_PROFILE_PATH}/$uid", body), + statusCodeHandler: (statusCode) => statusCode, + errorMessage: 'Lỗi khi PUT /${APIPathConstants.USER_PROFILE_PATH}', + ); } Future updateUserPassword(Map body) async { String uid = await getUID(); - int statusCode = await NetworkManager.instance!.updateDataInServer( - "${APIPathConstants.USER_PATH}/$uid/password", body); - return statusCode; + // int statusCode = await NetworkManager.instance!.updateDataInServer( + // "${APIPathConstants.USER_PATH}/$uid/password", body); + return executeApiCall( + () => NetworkManager.instance!.updateDataInServer( + "${APIPathConstants.USER_PATH}/$uid/password", body), + statusCodeHandler: (statusCode) => statusCode, + errorMessage: 'Lỗi khi PUT /${APIPathConstants.USER_PATH}'); } - Future getAllSettingsNotificationOfDevices() async { - String? data = await NetworkManager.instance! - .getDataFromServer(APIPathConstants.DEVICE_NOTIFICATION_SETTINGS); - return data; + Future> + getAllSettingsNotificationOfDevices() async { + return executeApiCall( + () => NetworkManager.instance! + .getDataFromServer(APIPathConstants.DEVICE_NOTIFICATION_SETTINGS), + parser: (json) => + DeviceNotificationSettings.mapFromJson(json['data']).values.toList(), + errorMessage: + 'Lỗi khi GET /${APIPathConstants.DEVICE_NOTIFICATION_SETTINGS}', + ); } - Future updateDeviceNotificationSettings( - String thingID, Map data) async { + Future updateDeviceNotificationSettings(String thingID, Map data) async { Map body = {"thing_id": thingID, "notifi_settings": data}; - int statusCode = await NetworkManager.instance!.updateDataInServer( - APIPathConstants.DEVICE_NOTIFICATION_SETTINGS, body); - return statusCode; + return executeApiCall( + () => NetworkManager.instance!.updateDataInServer( + APIPathConstants.DEVICE_NOTIFICATION_SETTINGS, body), + statusCodeHandler: (statusCode) => statusCode, + errorMessage: + 'Lỗi khi PUT /${APIPathConstants.DEVICE_NOTIFICATION_SETTINGS}'); } - Future getDashBoardDevices() async { - String? data = await NetworkManager.instance! - .getDataFromServer(APIPathConstants.DASHBOARD_DEVICES); - return data; + Future> getDashBoardDevices() async { + return executeApiCall( + () => NetworkManager.instance! + .getDataFromServer(APIPathConstants.DASHBOARD_DEVICES), + parser: (json) => DeviceWithAlias.fromJsonDynamicList(json['items']), + errorMessage: 'Lỗi khi GET /${APIPathConstants.DASHBOARD_DEVICES}', + ); } Future setupDeviceNotification(String thingID, String deviceName) async { @@ -202,68 +265,105 @@ class APIServices { "104": 1, } }; - int statusCode = await NetworkManager.instance!.updateDataInServer( - APIPathConstants.DEVICE_NOTIFICATION_SETTINGS, body); - return statusCode; + return executeApiCall( + () => NetworkManager.instance!.updateDataInServer( + APIPathConstants.DEVICE_NOTIFICATION_SETTINGS, body), + statusCodeHandler: (statusCode) => statusCode, + errorMessage: + 'Lỗi khi PUT /${APIPathConstants.DEVICE_NOTIFICATION_SETTINGS}'); } - Future getAllProvinces() async { - String? data = await NetworkManager.instance! - .getDataFromServer(APIPathConstants.PROVINCES_PATH); - return data; + Future> getAllProvinces() async { + return executeApiCall( + () => NetworkManager.instance! + .getDataFromServer(APIPathConstants.PROVINCES_PATH), + parser: (json) => Province.fromJsonDynamicList(json['items']), + errorMessage: 'Lỗi khi GET /${APIPathConstants.PROVINCES_PATH}'); + ; } - Future getProvincesByName(String name) async { + Future> getProvincesByName(String name) async { final params = {'name': name}; - String? data = await NetworkManager.instance! - .getDataFromServerWithParams(APIPathConstants.PROVINCES_PATH, params); - return data; + return executeApiCall( + () => NetworkManager.instance! + .getDataFromServerWithParams(APIPathConstants.PROVINCES_PATH, params), + parser: (json) => Province.fromJsonDynamicList(json['items']), + errorMessage: 'Lỗi khi GET /${APIPathConstants.PROVINCES_PATH}/$name', + ); + ; } - Future getProvinceByID(String provinceID) async { - String data = await NetworkManager.instance! - .getDataFromServer("${APIPathConstants.PROVINCES_PATH}/$provinceID"); - return data; + Future getProvinceByID(String provinceID) async { + return executeApiCall( + () => NetworkManager.instance! + .getDataFromServer("${APIPathConstants.PROVINCES_PATH}/$provinceID"), + parser: (json) => Province.fromJson(json['data']), + errorMessage: + 'Lỗi khi GET /${APIPathConstants.PROVINCES_PATH}/$provinceID}', + ); } - Future getAllDistricts(String provinceID) async { + Future> getAllDistricts(String provinceID) async { final params = {"parent": provinceID}; - String? data = await NetworkManager.instance! - .getDataFromServerWithParams(APIPathConstants.DISTRICTS_PATH, params); - return data; + return executeApiCall( + () => NetworkManager.instance! + .getDataFromServerWithParams(APIPathConstants.DISTRICTS_PATH, params), + parser: (json) => District.fromJsonDynamicList(json['items']), + errorMessage: + 'Lỗi khi GET /${APIPathConstants.DISTRICTS_PATH} by parentCode $provinceID', + ); } - Future getDistrictsByName(String districtName) async { + Future> getDistrictsByName(String districtName) async { final params = {"name": districtName}; - String? data = await NetworkManager.instance! - .getDataFromServerWithParams(APIPathConstants.DISTRICTS_PATH, params); - return data; + return executeApiCall( + () => NetworkManager.instance! + .getDataFromServerWithParams(APIPathConstants.DISTRICTS_PATH, params), + parser: (json) => District.fromJsonDynamicList(json['items']), + errorMessage: + 'Lỗi khi GET /${APIPathConstants.DISTRICTS_PATH} by name $districtName', + ); } - Future getDistrictByID(String districtID) async { - String? data = await NetworkManager.instance! - .getDataFromServer("${APIPathConstants.DISTRICTS_PATH}/$districtID"); - return data; + Future getDistrictByID(String districtID) async { + return executeApiCall( + () => NetworkManager.instance! + .getDataFromServer("${APIPathConstants.DISTRICTS_PATH}/$districtID"), + parser: (json) => District.fromJson(json['data']), + errorMessage: + 'Lỗi khi GET /${APIPathConstants.DISTRICTS_PATH}/$districtID', + ); } - Future getAllWards(String districtID) async { + Future> getAllWards(String districtID) async { final params = {'parent': districtID}; - String? data = await NetworkManager.instance! - .getDataFromServerWithParams(APIPathConstants.WARDS_PATH, params); - return data; + return executeApiCall( + () => NetworkManager.instance! + .getDataFromServerWithParams(APIPathConstants.WARDS_PATH, params), + parser: (json) => Ward.fromJsonDynamicList(json['items']), + errorMessage: + 'Lỗi khi GET /${APIPathConstants.WARDS_PATH} by parent $districtID', + ); } - Future getWarsdByName(String wardName) async { + Future> getWardsByName(String wardName) async { final params = {"name": wardName}; - String? data = await NetworkManager.instance! - .getDataFromServerWithParams(APIPathConstants.WARDS_PATH, params); - return data; + return executeApiCall( + () => NetworkManager.instance! + .getDataFromServerWithParams(APIPathConstants.WARDS_PATH, params), + parser: (json) => Ward.fromJsonDynamicList(json['items']), + errorMessage: + 'Lỗi khi GET /${APIPathConstants.WARDS_PATH} by name $wardName', + ); } - Future getWardByID(String wardID) async { - String? data = await NetworkManager.instance! - .getDataFromServer("${APIPathConstants.WARDS_PATH}/$wardID"); - return data; + Future getWardByID(String wardID) async { + return executeApiCall( + () => NetworkManager.instance! + .getDataFromServer("${APIPathConstants.WARDS_PATH}/$wardID"), + parser: (json) => Ward.fromJson(json['data']), + errorMessage: 'Lỗi khi GET /${APIPathConstants.WARDS_PATH}/$wardID', + ); } Future confirmFakeFireByUser(String thingID) async { @@ -271,131 +371,187 @@ class APIServices { "state": 3, "note": "Người dùng xác nhận cháy giả!" }; - int statusCode = await NetworkManager.instance! - .updateDataInServer("${APIPathConstants.DEVICE_PATH}/$thingID", body); - return statusCode; + return executeApiCall( + () => NetworkManager.instance!.updateDataInServer( + "${APIPathConstants.DEVICE_PATH}/$thingID", body), + statusCodeHandler: (statusCode) => statusCode, + errorMessage: 'Lỗi khi PUT /${APIPathConstants.DEVICE_PATH}/$thingID'); } - Future getOwnerDevices() async { - String? data = await NetworkManager.instance! - .getDataFromServer(APIPathConstants.DEVICE_PATH); - return data; + Future> getOwnerDevices() async { + return executeApiCall( + () => NetworkManager.instance! + .getDataFromServer(APIPathConstants.DEVICE_PATH), + parser: (json) => Device.fromJsonDynamicList(json['items']), + errorMessage: 'Lỗi khi GET /${APIPathConstants.DEVICE_PATH}', + ); } - Future getOwnerDeviceByState(Map params) async { - String? data = await NetworkManager.instance! - .getDataFromServerWithParams(APIPathConstants.DEVICE_PATH, params); - return data; + Future> getOwnerDeviceByState( + Map params) async { + return executeApiCall( + () => NetworkManager.instance! + .getDataFromServerWithParams(APIPathConstants.DEVICE_PATH, params), + parser: (json) => Device.fromJsonDynamicList(json['items']), + errorMessage: 'Lỗi khi GET /${APIPathConstants.DEVICE_PATH}', + ); } Future createDeviceByAdmin(Map body) async { - int? statusCode = await NetworkManager.instance! - .createDataInServer(APIPathConstants.DEVICE_PATH, body); - return statusCode; + return executeApiCall( + () => NetworkManager.instance! + .createDataInServer(APIPathConstants.DEVICE_PATH, body), + statusCodeHandler: (statusCode) => statusCode, + errorMessage: 'Lỗi khi POST /${APIPathConstants.DEVICE_PATH}'); } Future registerDevice(Map body) async { - int? statusCode = await NetworkManager.instance! - .createDataInServer(APIPathConstants.DEVICE_REGISTER_PATH, body); - return statusCode; + return executeApiCall( + () => NetworkManager.instance! + .createDataInServer(APIPathConstants.DEVICE_REGISTER_PATH, body), + statusCodeHandler: (statusCode) => statusCode, + errorMessage: 'Lỗi khi POST /${APIPathConstants.DEVICE_REGISTER_PATH}'); } Future deleteDeviceByAdmin(String thingID) async { - int statusCode = await NetworkManager.instance! - .deleteDataInServer("${APIPathConstants.DEVICE_PATH}/$thingID"); - return statusCode; + return executeApiCall( + () => NetworkManager.instance! + .deleteDataInServer("${APIPathConstants.DEVICE_PATH}/$thingID"), + statusCodeHandler: (statusCode) => statusCode, + errorMessage: + 'Lỗi khi DELETE /${APIPathConstants.DEVICE_PATH}/$thingID'); } Future unregisterDevice(Map body) async { - int statusCode = await NetworkManager.instance! - .createDataInServer(APIPathConstants.DEVICE_UNREGISTER_PATH, body); - return statusCode; + return executeApiCall( + () => NetworkManager.instance! + .createDataInServer(APIPathConstants.DEVICE_UNREGISTER_PATH, body), + statusCodeHandler: (statusCode) => statusCode, + errorMessage: + 'Lỗi khi DELETE /${APIPathConstants.DEVICE_UNREGISTER_PATH} by USER'); } - Future getDeviceInfomation(String thingID) async { - String? response = await NetworkManager.instance! - .getDataFromServer("${APIPathConstants.DEVICE_PATH}/$thingID"); - return response; + Future getDeviceInformation(String thingID) async { + return executeApiCall( + () => NetworkManager.instance! + .getDataFromServer("${APIPathConstants.DEVICE_PATH}/$thingID"), + parser: (json) => Device.fromJson(json), + errorMessage: 'Lỗi khi GET /${APIPathConstants.DEVICE_PATH}/$thingID', + ); } - Future getAllGroups() async { - String? body = await NetworkManager.instance! - .getDataFromServer(APIPathConstants.ALL_GROUPS_PATH); - return body; + Future> getAllGroups() async { + return executeApiCall( + () => NetworkManager.instance! + .getDataFromServer(APIPathConstants.ALL_GROUPS_PATH), + parser: (json) => Group.fromJsonDynamicList(json['items']), + errorMessage: 'Lỗi khi GET /${APIPathConstants.USER_PATH}', + ); } Future createGroup(Map body) async { - int? statusCode = await NetworkManager.instance! - .createDataInServer(APIPathConstants.GROUPS_PATH, body); - return statusCode; + return executeApiCall( + () => NetworkManager.instance! + .createDataInServer(APIPathConstants.GROUPS_PATH, body), + statusCodeHandler: (statusCode) => statusCode, + errorMessage: 'Lỗi khi POST /${APIPathConstants.GROUPS_PATH}'); } Future updateGroup(Map body, String groupID) async { - int? statusCode = await NetworkManager.instance! - .updateDataInServer("${APIPathConstants.GROUPS_PATH}/$groupID", body); - return statusCode; + return executeApiCall( + () => NetworkManager.instance!.updateDataInServer( + "${APIPathConstants.GROUPS_PATH}/$groupID", body), + statusCodeHandler: (statusCode) => statusCode, + errorMessage: 'Lỗi khi PUT /${APIPathConstants.GROUPS_PATH}/$groupID'); } Future joinGroup(String groupID, Map body) async { - int? statusCode = await NetworkManager.instance! - .createDataInServer(APIPathConstants.JOIN_GROUP_PATH, body); - return statusCode; + return executeApiCall( + () => NetworkManager.instance! + .createDataInServer(APIPathConstants.JOIN_GROUP_PATH, body), + statusCodeHandler: (statusCode) => statusCode, + errorMessage: 'Lỗi khi POST /${APIPathConstants.JOIN_GROUP_PATH}'); } Future deleteGroup(String groupID) async { - int? statusCode = await NetworkManager.instance! - .deleteDataInServer("${APIPathConstants.GROUPS_PATH}/$groupID"); - return statusCode; + return executeApiCall( + () => NetworkManager.instance! + .deleteDataInServer("${APIPathConstants.GROUPS_PATH}/$groupID"), + statusCodeHandler: (statusCode) => statusCode, + errorMessage: + 'Lỗi khi DELETE /${APIPathConstants.GROUPS_PATH}/$groupID'); } - Future getGroupDetail(String groupID) async { - String? body = await NetworkManager.instance! - .getDataFromServer("${APIPathConstants.GROUPS_PATH}/$groupID"); - return body; + Future getGroupDetail(String groupID) async { + return executeApiCall( + () => NetworkManager.instance! + .getDataFromServer("${APIPathConstants.GROUPS_PATH}/$groupID"), + parser: (json) => GroupDetail.fromJson(json), + errorMessage: 'Lỗi khi GET /${APIPathConstants.GROUPS_PATH}/$groupID', + ); } Future approveGroup(Map body) async { - int statusCode = await NetworkManager.instance! - .createDataInServer(APIPathConstants.APPROVE_GROUP_PATH, body); - return statusCode; + return executeApiCall( + () => NetworkManager.instance! + .createDataInServer(APIPathConstants.APPROVE_GROUP_PATH, body), + statusCodeHandler: (statusCode) => statusCode, + errorMessage: 'Lỗi khi POST /${APIPathConstants.APPROVE_GROUP_PATH}'); } Future deleteUserInGroup(String groupID, String userID) async { - int? statusCode = await NetworkManager.instance!.deleteDataInServer( - "${APIPathConstants.GROUPS_PATH}/$groupID/users/$userID"); - return statusCode; + return executeApiCall( + () => NetworkManager.instance!.deleteDataInServer( + "${APIPathConstants.GROUPS_PATH}/$groupID/users/$userID"), + statusCodeHandler: (statusCode) => statusCode, + errorMessage: + 'Lỗi khi DELETE /${APIPathConstants.GROUPS_PATH}/$groupID/users/$userID'); } Future deleteDeviceInGroup(String groupID, String thingID) async { - int? statusCode = await NetworkManager.instance!.deleteDataInServer( - "${APIPathConstants.GROUPS_PATH}/$groupID/devices/$thingID"); - return statusCode; + return executeApiCall( + () => NetworkManager.instance!.deleteDataInServer( + "${APIPathConstants.GROUPS_PATH}/$groupID/devices/$thingID"), + statusCodeHandler: (statusCode) => statusCode, + errorMessage: + 'Lỗi khi DELETE /${APIPathConstants.GROUPS_PATH}/$groupID/devices/$thingID'); } Future updateDeviceAlias(Map body) async { - int? statusCode = await NetworkManager.instance!.updateDataInServer( - APIPathConstants.DEVICE_NOTIFICATION_SETTINGS, body); - return statusCode; + return executeApiCall( + () => NetworkManager.instance!.updateDataInServer( + APIPathConstants.DEVICE_NOTIFICATION_SETTINGS, body), + statusCodeHandler: (statusCode) => statusCode, + errorMessage: + 'Lỗi khi PUT /${APIPathConstants.DEVICE_NOTIFICATION_SETTINGS}'); } Future addDeviceToGroup( String groupID, Map body) async { - int? statusCode = await NetworkManager.instance!.createDataInServer( - "${APIPathConstants.GROUPS_PATH}/$groupID/things", body); - return statusCode; + return executeApiCall( + () => NetworkManager.instance!.createDataInServer( + "${APIPathConstants.GROUPS_PATH}/$groupID/things", body), + statusCodeHandler: (statusCode) => statusCode, + errorMessage: + 'Lỗi khi PUT /${APIPathConstants.GROUPS_PATH}/$groupID/things'); } Future updateOwnerDevice( String thingID, Map body) async { - int? statusCode = await NetworkManager.instance! - .updateDataInServer("${APIPathConstants.DEVICE_PATH}/$thingID", body); - return statusCode; + return executeApiCall( + () => NetworkManager.instance!.updateDataInServer( + "${APIPathConstants.DEVICE_PATH}/$thingID", body), + statusCodeHandler: (statusCode) => statusCode, + errorMessage: 'Lỗi khi PUT /${APIPathConstants.DEVICE_PATH}/$thingID'); } - Future getLogsOfDevice( + Future getLogsOfDevice( String thingID, Map params) async { - String? body = await NetworkManager.instance! - .getDataFromServerWithParams(APIPathConstants.DEVICE_LOGS_PATH, params); - return body; + return executeApiCall( + () => NetworkManager.instance!.getDataFromServerWithParams( + APIPathConstants.DEVICE_LOGS_PATH, params), + parser: (json) => DeviceLog.fromJson(json), + errorMessage: 'Lỗi khi GET /${APIPathConstants.DEVICE_LOGS_PATH}', + ); } } diff --git a/lib/product/shared/shared_component_loading_animation.dart b/lib/product/shared/shared_component_loading_animation.dart new file mode 100644 index 0000000..a6dc696 --- /dev/null +++ b/lib/product/shared/shared_component_loading_animation.dart @@ -0,0 +1,23 @@ +import 'package:flutter/material.dart'; +import 'package:lottie/lottie.dart'; + +class SharedComponentLoadingAnimation extends StatefulWidget { + const SharedComponentLoadingAnimation({super.key}); + + @override + State createState() => _SharedComponentLoadingAnimationState(); +} + +class _SharedComponentLoadingAnimationState extends State { + @override + Widget build(BuildContext context) { + return Center( + child: LottieBuilder.asset( + 'assets/animations/component_loading.json', + width: 80, + height: 80, + fit: BoxFit.fill, + ), + ); + } +} diff --git a/lib/product/shared/shared_loading_animation.dart b/lib/product/shared/shared_loading_animation.dart new file mode 100644 index 0000000..882b41b --- /dev/null +++ b/lib/product/shared/shared_loading_animation.dart @@ -0,0 +1,23 @@ +import 'package:flutter/material.dart'; +import 'package:lottie/lottie.dart'; + +class SharedLoadingAnimation extends StatefulWidget { + const SharedLoadingAnimation({super.key}); + + @override + State createState() => _SharedLoadingAnimationState(); +} + +class _SharedLoadingAnimationState extends State { + @override + Widget build(BuildContext context) { + return Center( + child: LottieBuilder.asset( + 'assets/animations/loading.json', + width: 100, + height: 100, + fit: BoxFit.fill, + ), + ); + } +} diff --git a/lib/product/shared/shared_pie_chart.dart b/lib/product/shared/shared_pie_chart.dart index 4396d6c..5c3f245 100644 --- a/lib/product/shared/shared_pie_chart.dart +++ b/lib/product/shared/shared_pie_chart.dart @@ -182,23 +182,23 @@ class _SharedPieChartState extends State { switch (originalIndex) { case 0: // OFFLINE_STATE log("Touched Index device state = -1"); - widget.devicesManagerBloc.getDeviceByState(-1); + widget.devicesManagerBloc.getDeviceByState(context,-1); break; case 1: // NORMAL_STATE log("Touched Index Get device state = 0"); - widget.devicesManagerBloc.getDeviceByState(0); + widget.devicesManagerBloc.getDeviceByState(context,0); break; case 2: // WARNING_STATE log("Touched Index Get device state = 1"); - widget.devicesManagerBloc.getDeviceByState(1); + widget.devicesManagerBloc.getDeviceByState(context,1); break; case 3: // INPROGRESS_STATE log("Touched Index Get device state = 2"); - widget.devicesManagerBloc.getDeviceByState(2); + widget.devicesManagerBloc.getDeviceByState(context,2); break; case 4: // ERROR_STATE log("Touched Index Get device state = 3"); - widget.devicesManagerBloc.getDeviceByState(3); + widget.devicesManagerBloc.getDeviceByState(context,3); break; } } diff --git a/lib/product/utils/device_utils.dart b/lib/product/utils/device_utils.dart index bc2373f..f19e5ff 100644 --- a/lib/product/utils/device_utils.dart +++ b/lib/product/utils/device_utils.dart @@ -93,30 +93,34 @@ class DeviceUtils { return map; } + Future getFullDeviceLocation( - BuildContext context, String areaPath) async { - if (areaPath != "") { + BuildContext context, String areaPath, String? deviceName) async { + if (areaPath.isNotEmpty) { List parts = areaPath.split('_'); + if (parts.length < 3 || parts[0].isEmpty || parts[1].isEmpty || parts[2].isEmpty) { + if (deviceName != null && deviceName.isNotEmpty) { + return deviceName; + } else { + return appLocalization(context).no_data_message; + } + } + String provinceID = parts[0]; String districtID = parts[1]; String wardID = parts[2]; - String provinceBody = await apiServices.getProvinceByID(provinceID); - final provinceItem = jsonDecode(provinceBody); - Province province = Province.fromJson(provinceItem['data']); - String districtBody = await apiServices.getDistrictByID(districtID); - final districtItem = jsonDecode(districtBody); - District district = District.fromJson(districtItem['data']); - String wardBody = await apiServices.getWardByID(wardID); - final wardItem = jsonDecode(wardBody); - Ward ward = Ward.fromJson(wardItem['data']); - + Province province = await apiServices.getProvinceByID(provinceID); + District district = await apiServices.getDistrictByID(districtID); + Ward ward = await apiServices.getWardByID(wardID); return "${ward.fullName}, ${district.fullName}, ${province.fullName}"; } + return appLocalization(context).no_data_message; } + String checkStateDevice(BuildContext context, int state) { String message = appLocalization(context).no_data_message; if (state == 1) { @@ -157,11 +161,11 @@ class DeviceUtils { } else if (state == 0) { return const Color(0xFF9EF16D); } else if (state == 2) { - return const Color(0xFFF5EF44);; + return const Color(0xFFF5EF44); } else if (state == -1) { - return const Color(0xFFBBBAC2);; + return const Color(0xFFBBBAC2); } else { - return const Color(0xFFF5EF44);; + return const Color(0xFFF5EF44); } } diff --git a/pubspec.lock b/pubspec.lock index 2aba1e0..ad5a9b4 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -441,10 +441,10 @@ packages: dependency: "direct main" description: name: google_maps_flutter - sha256: "830d8f7b51b4a950bf0d7daa675324fed6c9beb57a7ecca2a59018270c96b4e0" + sha256: d7e4704e6b9f3452c7cd9eb6efc226e1f9e8273c28da47b0a1e7451916d71005 url: "https://pub.dev" source: hosted - version: "2.12.1" + version: "2.12.2" google_maps_flutter_android: dependency: transitive description: @@ -565,6 +565,14 @@ packages: url: "https://pub.dev" source: hosted version: "1.3.0" + lottie: + dependency: "direct main" + description: + name: lottie + sha256: c5fa04a80a620066c15cf19cc44773e19e9b38e989ff23ea32e5903ef1015950 + url: "https://pub.dev" + source: hosted + version: "3.3.1" maps_launcher: dependency: "direct main" description: diff --git a/pubspec.yaml b/pubspec.yaml index d42d8e3..ec3167d 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -43,10 +43,11 @@ dependencies: # persistent_bottom_nav_bar_v2: ^4.2.8 persistent_bottom_nav_bar: ^6.2.1 win32: ^5.10.0 - google_maps_flutter: ^2.12.1 + google_maps_flutter: ^2.12.2 data_table_2: ^2.5.18 url_launcher: ^6.3.1 app_settings: ^5.1.1 + lottie: ^3.3.1 dev_dependencies: flutter_test: @@ -64,6 +65,7 @@ flutter: - assets/icons/ - assets/map_themes/ - assets/sounds/ + - assets/animations/ flutter_launcher_icons: android: "launcher_icon" diff --git a/windows/flutter/CMakeLists.txt b/windows/flutter/CMakeLists.txt index 930d207..903f489 100644 --- a/windows/flutter/CMakeLists.txt +++ b/windows/flutter/CMakeLists.txt @@ -10,6 +10,11 @@ include(${EPHEMERAL_DIR}/generated_config.cmake) # https://github.com/flutter/flutter/issues/57146. set(WRAPPER_ROOT "${EPHEMERAL_DIR}/cpp_client_wrapper") +# Set fallback configurations for older versions of the flutter tool. +if (NOT DEFINED FLUTTER_TARGET_PLATFORM) + set(FLUTTER_TARGET_PLATFORM "windows-x64") +endif() + # === Flutter Library === set(FLUTTER_LIBRARY "${EPHEMERAL_DIR}/flutter_windows.dll") @@ -92,7 +97,7 @@ add_custom_command( COMMAND ${CMAKE_COMMAND} -E env ${FLUTTER_TOOL_ENVIRONMENT} "${FLUTTER_ROOT}/packages/flutter_tools/bin/tool_backend.bat" - windows-x64 $ + ${FLUTTER_TARGET_PLATFORM} $ VERBATIM ) add_custom_target(flutter_assemble DEPENDS diff --git a/windows/runner/flutter_window.cpp b/windows/runner/flutter_window.cpp index b25e363..955ee30 100644 --- a/windows/runner/flutter_window.cpp +++ b/windows/runner/flutter_window.cpp @@ -31,6 +31,11 @@ bool FlutterWindow::OnCreate() { this->Show(); }); + // Flutter can complete the first frame before the "show window" callback is + // registered. The following call ensures a frame is pending to ensure the + // window is shown. It is a no-op if the first frame hasn't completed yet. + flutter_controller_->ForceRedraw(); + return true; }