diff --git a/android/app/src/main/AndroidManifest.xml b/android/app/src/main/AndroidManifest.xml
index 6b0b61b..24b7370 100644
--- a/android/app/src/main/AndroidManifest.xml
+++ b/android/app/src/main/AndroidManifest.xml
@@ -33,13 +33,15 @@
-
+
+ android:label="太和e护"
+ android:enableOnBackInvokedCallback="true"
+ >
资源 249
\ No newline at end of file
diff --git a/assets/img/icon/bed_status.svg b/assets/img/icon/bed_status.svg
new file mode 100644
index 0000000..3042041
--- /dev/null
+++ b/assets/img/icon/bed_status.svg
@@ -0,0 +1,21 @@
+
diff --git a/assets/img/icon/bodymotion.svg b/assets/img/icon/bodymotion.svg
new file mode 100644
index 0000000..202f5ce
--- /dev/null
+++ b/assets/img/icon/bodymotion.svg
@@ -0,0 +1,11 @@
+
diff --git a/assets/img/icon/breathe.svg b/assets/img/icon/breathe.svg
new file mode 100644
index 0000000..1f0ec92
--- /dev/null
+++ b/assets/img/icon/breathe.svg
@@ -0,0 +1,13 @@
+
diff --git a/assets/img/icon/breathe_pause.svg b/assets/img/icon/breathe_pause.svg
new file mode 100644
index 0000000..2a9df0f
--- /dev/null
+++ b/assets/img/icon/breathe_pause.svg
@@ -0,0 +1,15 @@
+
diff --git a/assets/img/icon/device_issue.svg b/assets/img/icon/device_issue.svg
new file mode 100644
index 0000000..d19c4d9
--- /dev/null
+++ b/assets/img/icon/device_issue.svg
@@ -0,0 +1,8 @@
+
diff --git a/assets/img/icon/group.svg b/assets/img/icon/group.svg
new file mode 100644
index 0000000..a198a9e
--- /dev/null
+++ b/assets/img/icon/group.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/assets/img/icon/heart.svg b/assets/img/icon/heart.svg
new file mode 100644
index 0000000..69e3f26
--- /dev/null
+++ b/assets/img/icon/heart.svg
@@ -0,0 +1,15 @@
+
diff --git a/assets/img/icon/in_bed.svg b/assets/img/icon/in_bed.svg
new file mode 100644
index 0000000..d5f67a6
--- /dev/null
+++ b/assets/img/icon/in_bed.svg
@@ -0,0 +1,8 @@
+
diff --git a/assets/img/icon/loading.json b/assets/img/icon/loading.json
new file mode 100644
index 0000000..8eb7d21
--- /dev/null
+++ b/assets/img/icon/loading.json
@@ -0,0 +1 @@
+{"v":"5.9.4","fr":25,"ip":0,"op":21,"w":720,"h":720,"nm":"合成 6","ddd":0,"assets":[],"layers":[{"ddd":0,"ind":1,"ty":4,"nm":"loading2","sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[360,264.721,0],"ix":2,"l":2},"a":{"a":0,"k":[-368.2,-431.779,0],"ix":1,"l":2},"s":{"a":1,"k":[{"i":{"x":[0.667,0.667,0.667],"y":[1,1,1]},"o":{"x":[0.333,0.333,0.333],"y":[0,0,0]},"t":0,"s":[115,115,100]},{"i":{"x":[0.667,0.667,0.667],"y":[1,1,1]},"o":{"x":[0.333,0.333,0.333],"y":[0,0,0]},"t":5,"s":[0,0,100]},{"t":10,"s":[115,115,100]}],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0,-7.439],[7.44,0],[0,7.44],[-7.439,0]],"o":[[0,7.44],[-7.439,0],[0,-7.439],[7.44,0]],"v":[[-354.729,-431.779],[-368.2,-418.309],[-381.67,-431.779],[-368.2,-445.25]],"c":true},"ix":2},"nm":"路径 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"fl","c":{"a":0,"k":[0.019607843831,0.019607843831,0.019607843831,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"填充 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":"变换"}],"nm":"组 1","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":75,"st":0,"ct":1,"bm":0},{"ddd":0,"ind":2,"ty":4,"nm":"loading12","sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[411.342,279.796,0],"ix":2,"l":2},"a":{"a":0,"k":[-316.857,-416.704,0],"ix":1,"l":2},"s":{"a":1,"k":[{"i":{"x":[0.667,0.667,0.667],"y":[1,1,1]},"o":{"x":[0.333,0.333,0.333],"y":[0,0,0]},"t":1,"s":[115,115,100]},{"i":{"x":[0.667,0.667,0.667],"y":[1,1,1]},"o":{"x":[0.333,0.333,0.333],"y":[0,0,0]},"t":6,"s":[0,0,100]},{"t":11,"s":[115,115,100]}],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[4.022,-6.258],[6.258,4.022],[-4.022,6.258],[-6.258,-4.022]],"o":[[-4.022,6.258],[-6.259,-4.022],[4.022,-6.258],[6.258,4.022]],"v":[[-305.525,-409.421],[-324.14,-405.372],[-328.189,-423.986],[-309.575,-428.036]],"c":true},"ix":2},"nm":"路径 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"fl","c":{"a":0,"k":[0.019607843831,0.019607843831,0.019607843831,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"填充 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":"变换"}],"nm":"组 1","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":75,"st":0,"ct":1,"bm":0},{"ddd":0,"ind":3,"ty":4,"nm":"loading11","sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[446.384,320.236,0],"ix":2,"l":2},"a":{"a":0,"k":[-281.816,-376.264,0],"ix":1,"l":2},"s":{"a":1,"k":[{"i":{"x":[0.667,0.667,0.667],"y":[1,1,1]},"o":{"x":[0.333,0.333,0.333],"y":[0,0,0]},"t":2,"s":[115,115,100]},{"i":{"x":[0.667,0.667,0.667],"y":[1,1,1]},"o":{"x":[0.333,0.333,0.333],"y":[0,0,0]},"t":7,"s":[0,0,100]},{"t":12,"s":[115,115,100]}],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[6.767,-3.091],[3.09,6.767],[-6.767,3.091],[-3.09,-6.767]],"o":[[-6.767,3.09],[-3.091,-6.767],[6.767,-3.09],[3.09,6.767]],"v":[[-276.22,-364.011],[-294.069,-370.668],[-287.411,-388.517],[-269.563,-381.859]],"c":true},"ix":2},"nm":"路径 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"fl","c":{"a":0,"k":[0.019607843831,0.019607843831,0.019607843831,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"填充 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":"变换"}],"nm":"组 1","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":75,"st":0,"ct":1,"bm":0},{"ddd":0,"ind":4,"ty":4,"nm":"loading10","sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[453.999,373.201,0],"ix":2,"l":2},"a":{"a":0,"k":[-274.201,-323.299,0],"ix":1,"l":2},"s":{"a":1,"k":[{"i":{"x":[0.667,0.667,0.667],"y":[1,1,1]},"o":{"x":[0.333,0.333,0.333],"y":[0,0,0]},"t":3,"s":[115,115,100]},{"i":{"x":[0.667,0.667,0.667],"y":[1,1,1]},"o":{"x":[0.333,0.333,0.333],"y":[0,0,0]},"t":8,"s":[0,0,100]},{"t":13,"s":[115,115,100]}],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[7.364,1.059],[-1.059,7.364],[-7.364,-1.059],[1.059,-7.364]],"o":[[-7.364,-1.059],[1.059,-7.364],[7.364,1.059],[-1.059,7.364]],"v":[[-276.117,-309.965],[-287.534,-325.216],[-272.283,-336.632],[-260.867,-321.382]],"c":true},"ix":2},"nm":"路径 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"fl","c":{"a":0,"k":[0.019607843831,0.019607843831,0.019607843831,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"填充 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":"变换"}],"nm":"组 1","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":75,"st":0,"ct":1,"bm":0},{"ddd":0,"ind":5,"ty":4,"nm":"loading9","sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[431.77,421.876,0],"ix":2,"l":2},"a":{"a":0,"k":[-296.429,-274.624,0],"ix":1,"l":2},"s":{"a":1,"k":[{"i":{"x":[0.667,0.667,0.667],"y":[1,1,1]},"o":{"x":[0.333,0.333,0.333],"y":[0,0,0]},"t":4,"s":[115,115,100]},{"i":{"x":[0.667,0.667,0.667],"y":[1,1,1]},"o":{"x":[0.333,0.333,0.333],"y":[0,0,0]},"t":9,"s":[0,0,100]},{"t":14,"s":[115,115,100]}],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[5.622,4.872],[-4.872,5.622],[-5.622,-4.872],[4.872,-5.622]],"o":[[-5.622,-4.872],[4.872,-5.622],[5.622,4.872],[-4.872,5.622]],"v":[[-305.25,-264.444],[-306.609,-283.445],[-287.608,-284.804],[-286.249,-265.803]],"c":true},"ix":2},"nm":"路径 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"fl","c":{"a":0,"k":[0.019607843831,0.019607843831,0.019607843831,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"填充 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":"变换"}],"nm":"组 1","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":75,"st":0,"ct":1,"bm":0},{"ddd":0,"ind":6,"ty":4,"nm":"loading8","sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[386.755,450.805,0],"ix":2,"l":2},"a":{"a":0,"k":[-341.445,-245.695,0],"ix":1,"l":2},"s":{"a":1,"k":[{"i":{"x":[0.667,0.667,0.667],"y":[1,1,1]},"o":{"x":[0.333,0.333,0.333],"y":[0,0,0]},"t":5,"s":[115,115,100]},{"i":{"x":[0.667,0.667,0.667],"y":[1,1,1]},"o":{"x":[0.333,0.333,0.333],"y":[0,0,0]},"t":10,"s":[0,0,100]},{"t":15,"s":[115,115,100]}],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[2.096,7.138],[-7.138,2.096],[-2.096,-7.138],[7.138,-2.096]],"o":[[-2.096,-7.138],[7.138,-2.096],[2.096,7.138],[-7.138,2.096]],"v":[[-354.369,-241.9],[-345.24,-258.619],[-328.52,-249.49],[-337.65,-232.77]],"c":true},"ix":2},"nm":"路径 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"fl","c":{"a":0,"k":[0.019607843831,0.019607843831,0.019607843831,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"填充 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":"变换"}],"nm":"组 1","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":75,"st":0,"ct":1,"bm":0},{"ddd":0,"ind":7,"ty":4,"nm":"loading7","sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[333.245,450.805,0],"ix":2,"l":2},"a":{"a":0,"k":[-394.954,-245.695,0],"ix":1,"l":2},"s":{"a":1,"k":[{"i":{"x":[0.667,0.667,0.667],"y":[1,1,1]},"o":{"x":[0.333,0.333,0.333],"y":[0,0,0]},"t":6,"s":[115,115,100]},{"i":{"x":[0.667,0.667,0.667],"y":[1,1,1]},"o":{"x":[0.333,0.333,0.333],"y":[0,0,0]},"t":11,"s":[0,0,100]},{"t":16,"s":[115,115,100]}],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[-2.096,7.138],[-7.138,-2.096],[2.096,-7.138],[7.138,2.096]],"o":[[2.096,-7.138],[7.138,2.096],[-2.096,7.138],[-7.138,-2.096]],"v":[[-407.879,-249.49],[-391.159,-258.619],[-382.03,-241.9],[-398.75,-232.77]],"c":true},"ix":2},"nm":"路径 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"fl","c":{"a":0,"k":[0.019607843831,0.019607843831,0.019607843831,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"填充 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":"变换"}],"nm":"组 1","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":75,"st":0,"ct":1,"bm":0},{"ddd":0,"ind":8,"ty":4,"nm":"loading6","sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[288.23,421.876,0],"ix":2,"l":2},"a":{"a":0,"k":[-439.97,-274.624,0],"ix":1,"l":2},"s":{"a":1,"k":[{"i":{"x":[0.667,0.667,0.667],"y":[1,1,1]},"o":{"x":[0.333,0.333,0.333],"y":[0,0,0]},"t":7,"s":[115,115,100]},{"i":{"x":[0.667,0.667,0.667],"y":[1,1,1]},"o":{"x":[0.333,0.333,0.333],"y":[0,0,0]},"t":12,"s":[0,0,100]},{"t":17,"s":[115,115,100]}],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[-5.622,4.872],[-4.872,-5.622],[5.622,-4.872],[4.872,5.622]],"o":[[5.622,-4.872],[4.872,5.622],[-5.622,4.872],[-4.872,-5.622]],"v":[[-448.791,-284.804],[-429.79,-283.445],[-431.149,-264.444],[-450.15,-265.803]],"c":true},"ix":2},"nm":"路径 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"fl","c":{"a":0,"k":[0.019607843831,0.019607843831,0.019607843831,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"填充 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":"变换"}],"nm":"组 1","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":75,"st":0,"ct":1,"bm":0},{"ddd":0,"ind":9,"ty":4,"nm":"loading5","sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[266.001,373.201,0],"ix":2,"l":2},"a":{"a":0,"k":[-462.199,-323.299,0],"ix":1,"l":2},"s":{"a":1,"k":[{"i":{"x":[0.667,0.667,0.667],"y":[1,1,1]},"o":{"x":[0.333,0.333,0.333],"y":[0,0,0]},"t":8,"s":[115,115,100]},{"i":{"x":[0.667,0.667,0.667],"y":[1,1,1]},"o":{"x":[0.333,0.333,0.333],"y":[0,0,0]},"t":13,"s":[0,0,100]},{"t":18,"s":[115,115,100]}],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[-7.364,1.059],[-1.059,-7.364],[7.364,-1.059],[1.059,7.364]],"o":[[7.364,-1.059],[1.059,7.364],[-7.364,1.059],[-1.059,-7.364]],"v":[[-464.116,-336.632],[-448.866,-325.216],[-460.282,-309.965],[-475.532,-321.382]],"c":true},"ix":2},"nm":"路径 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"fl","c":{"a":0,"k":[0.019607843831,0.019607843831,0.019607843831,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"填充 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":"变换"}],"nm":"组 1","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":75,"st":0,"ct":1,"bm":0},{"ddd":0,"ind":10,"ty":4,"nm":"loading4","sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[273.616,320.236,0],"ix":2,"l":2},"a":{"a":0,"k":[-454.583,-376.264,0],"ix":1,"l":2},"s":{"a":1,"k":[{"i":{"x":[0.667,0.667,0.667],"y":[1,1,1]},"o":{"x":[0.333,0.333,0.333],"y":[0,0,0]},"t":9,"s":[115,115,100]},{"i":{"x":[0.667,0.667,0.667],"y":[1,1,1]},"o":{"x":[0.333,0.333,0.333],"y":[0,0,0]},"t":14,"s":[0,0,100]},{"t":19,"s":[115,115,100]}],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[-6.767,-3.09],[3.091,-6.767],[6.767,3.09],[-3.09,6.767]],"o":[[6.767,3.091],[-3.09,6.767],[-6.767,-3.091],[3.09,-6.767]],"v":[[-448.988,-388.517],[-442.33,-370.668],[-460.179,-364.011],[-466.836,-381.859]],"c":true},"ix":2},"nm":"路径 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"fl","c":{"a":0,"k":[0.019607843831,0.019607843831,0.019607843831,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"填充 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":"变换"}],"nm":"组 1","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":75,"st":0,"ct":1,"bm":0},{"ddd":0,"ind":11,"ty":4,"nm":"loading3","sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[308.658,279.796,0],"ix":2,"l":2},"a":{"a":0,"k":[-419.542,-416.704,0],"ix":1,"l":2},"s":{"a":1,"k":[{"i":{"x":[0.667,0.667,0.667],"y":[1,1,1]},"o":{"x":[0.333,0.333,0.333],"y":[0,0,0]},"t":10,"s":[115,115,100]},{"i":{"x":[0.667,0.667,0.667],"y":[1,1,1]},"o":{"x":[0.333,0.333,0.333],"y":[0,0,0]},"t":15,"s":[0,0,100]},{"t":20,"s":[115,115,100]}],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[-4.022,-6.258],[6.259,-4.022],[4.022,6.258],[-6.258,4.022]],"o":[[4.022,6.258],[-6.259,4.022],[-4.022,-6.258],[6.258,-4.022]],"v":[[-408.21,-423.986],[-412.259,-405.372],[-430.874,-409.421],[-426.825,-428.036]],"c":true},"ix":2},"nm":"路径 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"fl","c":{"a":0,"k":[0.019607843831,0.019607843831,0.019607843831,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"填充 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":"变换"}],"nm":"组 1","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":75,"st":0,"ct":1,"bm":0}],"markers":[]}
\ No newline at end of file
diff --git a/assets/img/icon/not_bed.svg b/assets/img/icon/not_bed.svg
new file mode 100644
index 0000000..07ba46a
--- /dev/null
+++ b/assets/img/icon/not_bed.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/assets/img/icon/nulldata.svg b/assets/img/icon/nulldata.svg
new file mode 100644
index 0000000..862f3ad
--- /dev/null
+++ b/assets/img/icon/nulldata.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/assets/img/icon/refresh.svg b/assets/img/icon/refresh.svg
new file mode 100644
index 0000000..9adcd93
--- /dev/null
+++ b/assets/img/icon/refresh.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/assets/img/icon/share.svg b/assets/img/icon/share.svg
new file mode 100644
index 0000000..60cfee1
--- /dev/null
+++ b/assets/img/icon/share.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/assets/img/icon/signal0.svg b/assets/img/icon/signal0.svg
new file mode 100644
index 0000000..5ea3155
--- /dev/null
+++ b/assets/img/icon/signal0.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/assets/img/icon/signal1.svg b/assets/img/icon/signal1.svg
new file mode 100644
index 0000000..ff980f9
--- /dev/null
+++ b/assets/img/icon/signal1.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/assets/img/icon/signal2.svg b/assets/img/icon/signal2.svg
new file mode 100644
index 0000000..0d5401f
--- /dev/null
+++ b/assets/img/icon/signal2.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/assets/img/icon/signal3.svg b/assets/img/icon/signal3.svg
new file mode 100644
index 0000000..a72c553
--- /dev/null
+++ b/assets/img/icon/signal3.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/assets/img/icon/singal4.svg b/assets/img/icon/singal4.svg
new file mode 100644
index 0000000..0211af0
--- /dev/null
+++ b/assets/img/icon/singal4.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/assets/img/icon/snore.svg b/assets/img/icon/snore.svg
new file mode 100644
index 0000000..dbedebd
--- /dev/null
+++ b/assets/img/icon/snore.svg
@@ -0,0 +1,19 @@
+
diff --git a/assets/img/icon/tips.svg b/assets/img/icon/tips.svg
new file mode 100644
index 0000000..b258c59
--- /dev/null
+++ b/assets/img/icon/tips.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/assets/img/icon/upgrade.svg b/assets/img/icon/upgrade.svg
new file mode 100644
index 0000000..a2972ed
--- /dev/null
+++ b/assets/img/icon/upgrade.svg
@@ -0,0 +1,8 @@
+
diff --git a/assets/img/icon/wifi1.svg b/assets/img/icon/wifi1.svg
new file mode 100644
index 0000000..d9e66d3
--- /dev/null
+++ b/assets/img/icon/wifi1.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/assets/img/icon/wifi2.svg b/assets/img/icon/wifi2.svg
new file mode 100644
index 0000000..ada184b
--- /dev/null
+++ b/assets/img/icon/wifi2.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/assets/img/icon/wifi3.svg b/assets/img/icon/wifi3.svg
new file mode 100644
index 0000000..1336275
--- /dev/null
+++ b/assets/img/icon/wifi3.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/assets/img/icon/wifi4.svg b/assets/img/icon/wifi4.svg
new file mode 100644
index 0000000..c0ca522
--- /dev/null
+++ b/assets/img/icon/wifi4.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/assets/img/signal0.png b/assets/img/signal0.png
new file mode 100644
index 0000000..3cb8640
Binary files /dev/null and b/assets/img/signal0.png differ
diff --git a/assets/img/signal1.png b/assets/img/signal1.png
new file mode 100644
index 0000000..12fbf21
Binary files /dev/null and b/assets/img/signal1.png differ
diff --git a/assets/img/signal2.png b/assets/img/signal2.png
new file mode 100644
index 0000000..44a9f17
Binary files /dev/null and b/assets/img/signal2.png differ
diff --git a/assets/img/signal3.png b/assets/img/signal3.png
new file mode 100644
index 0000000..b614204
Binary files /dev/null and b/assets/img/signal3.png differ
diff --git a/assets/img/signal4.png b/assets/img/signal4.png
new file mode 100644
index 0000000..3ee1f35
Binary files /dev/null and b/assets/img/signal4.png differ
diff --git a/assets/img/wifi1.png b/assets/img/wifi1.png
new file mode 100644
index 0000000..0e8dc50
Binary files /dev/null and b/assets/img/wifi1.png differ
diff --git a/assets/img/wifi2.png b/assets/img/wifi2.png
new file mode 100644
index 0000000..2697397
Binary files /dev/null and b/assets/img/wifi2.png differ
diff --git a/assets/img/wifi3.png b/assets/img/wifi3.png
new file mode 100644
index 0000000..aaec06b
Binary files /dev/null and b/assets/img/wifi3.png differ
diff --git a/assets/img/wifi4.png b/assets/img/wifi4.png
new file mode 100644
index 0000000..bea4433
Binary files /dev/null and b/assets/img/wifi4.png differ
diff --git a/assets/langs/zh_CN.json b/assets/langs/zh_CN.json
index a8cd4f8..87e03fe 100644
--- a/assets/langs/zh_CN.json
+++ b/assets/langs/zh_CN.json
@@ -27,7 +27,17 @@
"设备报修": "设备报修",
"操作说明": "操作说明",
"关注我们": "关注我们",
- "当前版本": "当前版本"
+ "当前版本": "当前版本",
+ "未命名": "未命名",
+ "去登录": "去登录",
+ "头像限制": "头像图片不能超过1MB",
+ "头像上传失败":"头像上传失败",
+ "未选择图片":"未选择图片",
+ "上传成功":"上传成功",
+ "保存失败":"保存失败",
+ "保存成功":"保存成功",
+ "昵称为空":"昵称不能为空",
+ "查询失败":"查询用户资料失败"
},
"设备类型": {
"我的设备": "我的设备",
@@ -75,7 +85,12 @@
"是":"是",
"否":"否",
"确定绑定提示":"确定绑定该设备吗?",
- "连接成功":"连接成功"
+ "连接成功":"连接成功",
+ "连接异常":"连接异常",
+ "连接":"连接",
+ "输入wifi密码":"请输入wifi密码",
+ "显示密码":"显示",
+ "不显示密码":"不显示"
},
"登录页":{
"欢迎使用太和e护":"欢迎使用太和e护",
@@ -122,7 +137,13 @@
"未连接":"未连接",
"已连接":"已连接",
"可用WLAN":"可用WLAN",
- "刷新":"刷新"
+ "刷新":"刷新",
+ "密码为空":"密码不能为空",
+ "配网成功":"配网成功",
+ "配网失败":"配网失败",
+ "配网中":"配网中",
+ "需配网":"请给设备配置网络!"
+
},
"其他手机登录页":{
"输入内容":"输入手机号码/邮箱",
@@ -153,13 +174,118 @@
"用户协议":"用户协议",
"隐私协议":"隐私协议",
"退出登录":"退出登录",
- "注销账号":"注销账号"
+ "注销账号":"注销账号",
+ "退出成功":"退出成功",
+ "退出失败":"退出失败"
+
},
"关于我们":{
"标题":"关于我们"
},
"服务器":{
"失败":"服务器内部错误,请联系管理员"
- }
+ },
+ "体征检测设备":{
+ "标题":"体征监测设备",
+ "输入关键词":"输入关键词",
+ "搜索":"搜索",
+ "我的e护":"我的e护",
+ "云关爱":"云关爱",
+ "设备ID":"设备ID",
+ "更新时间":"更新时间",
+ "设备来源":"设备来源",
+ "设备状态":"设备状态",
+ "人员资料":"人员资料",
+ "实时体征":"实时体征",
+ "消息回看":"消息回看",
+ "健康报告":"健康报告",
+ "首页展示":"首页展示",
+ "设备详情":"设备详情",
+ "重命名":"重命名",
+ "删除":"删除"
+ },
+ "设备详情":{
+ "标题":"设备详情",
+ "MAC":"MAC",
+ "设备来源":"设备来源",
+ "型号":"型号",
+ "设备名称":"设备名称",
+ "网络状态":"网络状态",
+ "版本":"版本",
+ "更新状态":"更新状态",
+ "更新时间":"更新时间",
+ "故障状态":"故障状态"
+ },
+ "实时体征":{
+ "标题":"实时体征",
+ "姓名":"姓名",
+ "年龄":"年龄",
+ "设备ID":"设备ID",
+ "体重":"体重",
+ "提示":"提示:实时体征分析时,人员应躺在指定床位上且设备正常运行,保持身体静止不动状态下10~30秒左右,即可显示实时体征数据。"
+ },
+ "待开发":{
+ "提示":"功能开发中..."
+ },
+ "扫一扫":{
+ "标题":"扫一扫",
+ "提示":"请扫描设备二维码",
+ "相册":"相册",
+ "手电筒":"手电筒"
+ },
+ "设备":{
+ "设备列表请求失败":"设备列表请求失败",
+ "设备列表请求成功":"设备列表请求成功"
+ },
+ "未命名":"未命名",
+ "未知时间":"-",
+ "设备ID":"设备ID",
+ "更新时间":"更新时间",
+ "已分享":"已分享",
+ "设备来源":"设备来源",
+ "云关爱":"云关爱",
+ "是否确认解绑":"是否确认解绑?",
+ "请求失败":"请求失败!",
+ "操作成功":"操作成功!",
+ "操作失败":"操作失败!",
+ "暂无数据":"暂无数据",
+ "请输入姓名":"请输入姓名",
+ "请选择生日":"请选择生日",
+ "请输入体重":"请输入体重",
+ "必须登录提示":"请先登录!",
+ "待开发功能":"功能开发中...",
+ "未知数据":"-",
+ "在离床":"在离床",
+ "体动":"体动",
+ "心率":"心率",
+ "打鼾":"打鼾",
+ "呼吸":"呼吸",
+ "呼吸暂停":"呼吸暂停",
+ "请保持静止":"请保持身体静止",
+ "健康报告":"健康报告",
+ "修改人员名称":"修改人员名称",
+ "在线":"在线",
+ "离线":"离线",
+ "有更新":"有更新",
+ "无更新":"无更新",
+ "有故障":"有故障",
+ "无故障":"无故障",
+ "人":"人",
+ "WIFI配置":"WIFI配置",
+ "分享设备":"分享设备",
+ "消息设置":"消息设置",
+ "设备分享":"设备分享",
+ "请输入对方手机号或邮箱":"请输入对方手机号或邮箱",
+ "微信好友一键分享":"微信好友一键分享",
+ "发送邀请":"发送邀请",
+ "要分享的设备":"要分享的设备",
+ "主设备":"主设备:",
+ "从设备":"从设备:",
+ "邀请成功":"邀请成功!",
+ "邀请失败":"邀请失败!",
+ "请输入手机号或者邮箱":"请输入手机号或者邮箱",
+ "请输入正确的手机号或者邮箱":"请输入正确的手机号或者邮箱",
+ "体征消息":"体征消息",
+ "系统消息":"系统消息"
}
\ No newline at end of file
diff --git a/ios/Runner/Info.plist b/ios/Runner/Info.plist
index 9835cb5..58892c1 100644
--- a/ios/Runner/Info.plist
+++ b/ios/Runner/Info.plist
@@ -28,6 +28,8 @@
LaunchScreen
UIMainStoryboardFile
Main
+ NSCameraUsageDescription
+ 需要使用相机扫码
UISupportedInterfaceOrientations
UIInterfaceOrientationPortrait
diff --git a/lib/common/color/ServiceConstant.dart b/lib/common/color/ServiceConstant.dart
new file mode 100644
index 0000000..bafd189
--- /dev/null
+++ b/lib/common/color/ServiceConstant.dart
@@ -0,0 +1,29 @@
+class ServiceConstant {
+ static const String baseHost = "vsbs-test.he-info.cn";//服务地址
+ static const String service_address = "http://$baseHost";
+
+ static String server_service = "/vsbs_app_server";//服务名称
+
+ static String send_code = "/api/verifycode/send";//发送验证码
+ static String login = "/api/user/login";//登录
+ static String get_bluetooth_device_status = "/api/device/status/info";//设备绑定状态
+ static String device_bind = "/api/device/bind";//设备绑定
+ static String device_type = "/api/device/type/list";//设备类型
+ static String upload_file = "/api/file/info";//上传文件
+ static String user_info = "/api/user/info";//更新用户资料,查询用户信息
+ static String device_list = "/api/device/list";//绑定设备列表
+ static String person_info = "/api/personnel/info";//用户资料
+ static String sleep_report = "/api/device/sleep/data";//睡眠报告
+ static String device_share = "/api/device/share";//分享设备
+ static String message_list = "/api/mesasge/list";//消息列表
+ static String device_show = "/api/device/bind";//更新设备绑定
+ static String disease_list = "/api/personnel/disease/list";//获取疾病类型
+
+
+
+ static String logService = "$service_address/vsbs_log";
+ static const String webSocketService = "wss://$baseHost/vsbs_ws_gateway/ws";
+ static const String sleep_token = "HdAMjzqiYQKsmHRyEFKhfRGQ";
+ static const String sleep_report_url = "https://alltoone.he-info.cn/h5/#/mattress/sleep/sleep";
+
+}
diff --git a/lib/common/util/Ble.dart b/lib/common/util/Ble.dart
deleted file mode 100644
index ccb8e20..0000000
--- a/lib/common/util/Ble.dart
+++ /dev/null
@@ -1,1103 +0,0 @@
-import 'dart:async';
-import 'dart:convert';
-import 'dart:io';
-import 'dart:typed_data';
-
-import 'package:ef/ef.dart';
-import 'package:flutter/foundation.dart';
-import 'package:flutter/material.dart';
-import 'package:flutter_blue_plus/flutter_blue_plus.dart';
-import 'package:permission_handler/permission_handler.dart';
-import 'package:vbvs_app/common/util/MyUtils.dart';
-import 'package:vbvs_app/pages/common/selectDialog.dart';
-
-String findInput = "";
-double rssichange = -90;
-Function? findCall;
-
-int mcuMax = 500;
-
-String myuuid = "00000001-0000-1000-8000-00805F9B34FB";
-
-int closeTime = 20000;
-
-Map devices = Map();
-
-StreamSubscription? subscription_adapterState;
-
-Map connectList =
- Map();
-
-bool isBleStart = false;
-
-StreamSubscription>? onScanResultsListen;
-
-StreamSubscription?
- streamSubscription_onConnectionStateChangedEvent;
-
-Timer? showToastTimer;
-
-List permissionInfo = [
- ["位置权限说明", "获得位置信息,连接附近的蓝牙设备与推荐附近门店"],
- ["蓝牙权限说明", "搜索链接附近的蓝牙设备"],
- ["附近设备权限说明", "搜索链接附近的蓝牙设备"]
-];
-
-bool isQuanShiDevice(name) {
- return "$name".contains("S4-ZM-M94-4") || "$name".contains("S4-ZM-N94-4") || "$name".contains("MHT-SWES-D");
-}
-
-bool isMHTSWES(name) {
- return "$name".contains("MHT-SWES-H") || "$name".contains("MHT-SWES-M") || "$name".contains("MHT-SWES-S");
-}
-
-bleParse() {
- findCall = null;
- print("bleParse 执行了");
- showToastTimer?.cancel();
- if (FlutterBluePlus.isScanningNow) {
- FlutterBluePlus.stopScan();
- }
-}
-
-start(Function fun, {Function? bleOnCall}) async {
- if (isBleStart) {
- var isOk = await requestBluetoothPermission();
- if (isOk == true) {
- findCall = fun;
- bleOnCall?.call();
- bleOnCall = null;
- }
- print("ble start again");
- return;
- }
- print("ble start");
- FlutterBluePlus.setLogLevel(LogLevel.info, color: false);
- streamSubscription_onConnectionStateChangedEvent?.cancel();
- streamSubscription_onConnectionStateChangedEvent =
- FlutterBluePlus.events.onConnectionStateChanged.listen((event) {
- print('${event.device} ${event.connectionState}');
- });
- if (await FlutterBluePlus.isSupported == false) {
- print("Bluetooth not supported by this device");
- return;
- }
-
- // handle bluetooth on & off
- // note: for iOS the initial state is typically BluetoothAdapterState.unknown
- // note: if you have permissions issues you will get stuck at BluetoothAdapterState.unauthorized
- subscription_adapterState?.cancel();
- int callIndex = 0;
- subscription_adapterState =
- FlutterBluePlus.adapterState.listen((BluetoothAdapterState state) async {
- print(state);
- print("蓝牙状态 $state");
- if (state == BluetoothAdapterState.on) {
- showToastTimer?.cancel();
- // usually start scanning, connecting, etc
- findCall = fun;
- var isOk = await requestBluetoothPermission();
- if (isOk == true) {
- bleOnCall?.call();
- bleOnCall = null;
- }
- } else {
- // show an error to the user, etc
- if (Platform.isIOS &&
- callIndex == 0 &&
- state == BluetoothAdapterState.unknown) {
- callIndex++;
- return;
- }
- if (Platform.isAndroid) {
- showToast("请打开蓝牙开关");
- }
- if (Platform.isIOS) {
- showToast("请打开蓝牙开关且开启蓝牙权限");
- await showCustomConfirmAndCancelDialog(
- Get.context!, "请在“设置-蓝牙”中打开蓝牙开关或者在“设置-APP”中找到对应APP开启蓝牙权限",
- confirmName: "去设置")
- .then((msg) async {
- if (msg == "confirm") {
- openAppSettings();
- }
- });
- }
- isBleStart = false;
- showToastTimer?.cancel();
- showToastTimer = Timer.periodic(const Duration(seconds: 5), (t) {
- if (t.tick > 3) {
- t.cancel();
- }
- if (Platform.isAndroid) {
- showToast("请打开蓝牙开关");
- }
- if (Platform.isIOS) {
- showToast("请打开蓝牙开关且开启蓝牙权限");
- }
- });
- }
- callIndex++;
- });
-
- // turn on bluetooth ourself if we can
- // for iOS, the user controls bluetooth enable/disable
- if (Platform.isAndroid &&
- FlutterBluePlus.adapterStateNow != BluetoothAdapterState.on) {
- showPermissionInfoDialog(Get.context!, permissionInfo);
- FlutterBluePlus.turnOn().then((e) {
- Get.back();
- }).catchError((e) {
- Get.back();
- });
- }
-
- var timer = null;
- onScanResultsListen?.cancel();
- onScanResultsListen = FlutterBluePlus.onScanResults.listen(
- (List results) {
- // print(results.length);
- for (ScanResult result in results) {
- // if (result.device.id.toString().contains("A3:76")) {
- // print("$result");
- // }
- Map d = {
- "updateTime": DateTime.now().millisecondsSinceEpoch,
- "name": result.device.advName,
- "id": result.device.remoteId.str,
- "rssi": result.rssi,
- "device": result.device,
- "connectable": result.advertisementData.connectable
- };
- Map> m_d = result.advertisementData.manufacturerData;
- m_d.keys.toList().forEach((v) {
- if (v == 65517 && m_d[65517]?.length != 0) {
- List a = [0, 0, ...?m_d[65517]];
- advertisDataFormatter(a, d);
- } else if (v == 11125 && m_d[11125]?.length == 8) {
- List a = [...?m_d[11125]];
- d['adData'] = {'deviceId': ab2str(a.sublist(2, 8)).toUpperCase()};
- } else if (m_d[v]?.length == 8 && isQuanShiDevice(d["name"])) {
- List a = [...?m_d[v]];
- d['adData'] = {'deviceId': ab2str(a.sublist(2, 8)).toUpperCase()};
- } else if (m_d[v]?.length == 4 && isMHTSWES(d["name"])) {
- ByteData bd = ByteData(2);
- bd.setUint16(0, v, Endian.little);
- List a = [bd.getUint8(0), bd.getUint8(1), ...?m_d[v]];
- d['adData'] = {'deviceId': ab2str(a).toUpperCase()};
- } else if (m_d[v]?.length == 6 && isMHTSWES(d["name"])) {
- List a = [...?m_d[v]];
- d['adData'] = {'deviceId': ab2str(a).toUpperCase()};
- }
- });
- devices[d['id']] = d;
- // print('Device found: ${result.device.name}, ${result.device.id}');
- if (timer == null) {
- timer = 1;
- timer = Future.delayed(const Duration(microseconds: 300), () {
- timer = null;
- find();
- });
- }
- }
- },
- onError: (e) => print(e),
- );
-}
-
-// Future locationCheck({bool isGetLocation = true}) async {
-// // 先查看定位服务是否开启
-// bool b = await Geolocator.isLocationServiceEnabled();
-// if (b == false) {
-// if (Platform.isAndroid) {
-// showToast("请开启系统位置开关", closeTime: 5);
-// await showCustomConfirmAndCancelDialog(Get.context!, "请开启系统位置开关",
-// confirmName: "去设置")
-// .then((msg) async {
-// if (msg == "confirm") {
-// // await Geolocator.openLocationSettings();
-// await openGeolocatorLocationSettingsAndWait();
-// b = await Geolocator.isLocationServiceEnabled();
-// }
-// });
-// }
-// if (Platform.isIOS) {
-// showToast("请开启系统定位服务与定位权限", closeTime: 5);
-// await showCustomConfirmAndCancelDialog(Get.context!,
-// "请在“设置-隐私与安全性-定位服务”中开启定位服务开关或者在“设置-APP”中找到对应APP开启定位服务权限",
-// confirmName: "去设置")
-// .then((msg) async {
-// if (msg == "confirm") {
-// // await Geolocator.openAppSettings();
-// await openGeolocatorAppSettingsAndWait();
-// b = await Geolocator.isLocationServiceEnabled();
-// }
-// });
-// }
-// }
-
-// var permission = await Geolocator.checkPermission();
-// if (permission == LocationPermission.denied ||
-// permission == LocationPermission.deniedForever) {
-// showPermissionInfoDialog(Get.context!, [permissionInfo[0]]);
-// permission = await Geolocator.requestPermission().catchError((e) {
-// Get.back();
-// });
-// Get.back();
-// if (permission == LocationPermission.deniedForever) {
-// if (Platform.isAndroid) {
-// await showCustomConfirmAndCancelDialog(Get.context!, "请开启位置权限(打开精确位置)",
-// confirmName: "去设置")
-// .then((msg) async {
-// if (msg == "confirm") {
-// // await Geolocator.openAppSettings();
-// await openGeolocatorAppSettingsAndWait();
-// }
-// });
-// }
-// if (Platform.isIOS) {
-// await showCustomConfirmAndCancelDialog(Get.context!,
-// "请在“设置-隐私与安全性-定位服务”中开启定位服务开关或者在“设置-APP”中找到对应APP开启定位服务权限",
-// confirmName: "去设置")
-// .then((msg) async {
-// if (msg == "confirm") {
-// // await Geolocator.openAppSettings();
-// await openGeolocatorAppSettingsAndWait();
-// }
-// });
-// }
-// }
-// }
-
-// Position? position;
-// if (isGetLocation) {
-// if (b &&
-// permission != LocationPermission.denied &&
-// permission != LocationPermission.deniedForever) {
-// try {
-// position = await Geolocator.getCurrentPosition(
-// locationSettings:
-// const LocationSettings(timeLimit: Duration(seconds: 5)));
-// print("$position");
-// } catch (e) {
-// print("error $e");
-// }
-// }
-// }
-// return position;
-// }
-
-/// 使用 Completer 来等待用户返回应用,自定义的 LifecycleEventHandler 类将监视应用程序的生命周期状态,
-/// 并在应用程序恢复时完成,有效地等待用户从设置屏幕返回
-/// ------开始-----
-Future openGeolocatorAppSettingsAndWait() async {
- final geolocatorAppSettingsLifecycleState =
- GeolocatorAppSettingsLifecycleEventHandler();
-
- WidgetsBinding.instance.addObserver(geolocatorAppSettingsLifecycleState);
-
- // await Geolocator.openAppSettings();
-
- await geolocatorAppSettingsLifecycleState.waitForResume();
-
- WidgetsBinding.instance.removeObserver(geolocatorAppSettingsLifecycleState);
-
- print('AppSettings have been opened and user has returned');
-}
-
-class GeolocatorAppSettingsLifecycleEventHandler
- extends WidgetsBindingObserver {
- final Completer _completer = Completer();
-
- @override
- void didChangeAppLifecycleState(AppLifecycleState state) {
- if (state == AppLifecycleState.resumed && !_completer.isCompleted) {
- _completer.complete();
- }
- }
-
- Future waitForResume() => _completer.future;
-}
-
-/// 使用 Completer 来等待用户返回应用,自定义的 LifecycleEventHandler 类将监视应用程序的生命周期状态,
-/// 并在应用程序恢复时完成,有效地等待用户从设置屏幕返回
-/// ------结束-----
-
-/// 使用 Completer 来等待用户返回应用,自定义的 LifecycleEventHandler 类将监视应用程序的生命周期状态,
-/// 并在应用程序恢复时完成,有效地等待用户从设置屏幕返回
-/// ------开始-----
-Future openGeolocatorLocationSettingsAndWait() async {
- final geolocatorLocationSettingsLifecycleState =
- GeolocatorLocationSettingsLifecycleEventHandler();
-
- WidgetsBinding.instance.addObserver(geolocatorLocationSettingsLifecycleState);
-
- // await Geolocator.openLocationSettings();
-
- await geolocatorLocationSettingsLifecycleState.waitForResume();
-
- WidgetsBinding.instance
- .removeObserver(geolocatorLocationSettingsLifecycleState);
-
- print('LocationSettings have been opened and user has returned');
-}
-
-class GeolocatorLocationSettingsLifecycleEventHandler
- extends WidgetsBindingObserver {
- final Completer _completer = Completer();
-
- @override
- void didChangeAppLifecycleState(AppLifecycleState state) {
- if (state == AppLifecycleState.resumed && !_completer.isCompleted) {
- _completer.complete();
- }
- }
-
- Future waitForResume() => _completer.future;
-}
-
-/// 使用 Completer 来等待用户返回应用,自定义的 LifecycleEventHandler 类将监视应用程序的生命周期状态,
-/// 并在应用程序恢复时完成,有效地等待用户从设置屏幕返回
-/// ------结束-----
-
-Future requestBluetoothPermission() async {
- if (Platform.isIOS) {
- PermissionStatus isBleGranted = await Permission.bluetooth.request();
- print('checkBlePermissions-ios, isBleGranted=$isBleGranted');
- if (isBleGranted.isGranted) {
- startBluetoothScanning();
- return true;
- } else {
- showToast("蓝牙开关或蓝牙权限未开启,请开启蓝牙开关与蓝牙权限", closeTime: 7);
- await showCustomConfirmAndCancelDialog(
- Get.context!, "请“设置-蓝牙”中打开蓝牙开关或者在“设置-APP”中找到对应APP开启蓝牙权限",
- confirmName: "去设置")
- .then((msg) async {
- if (msg == "confirm") {
- openAppSettings();
- }
- });
- return false;
- }
- } else if (Platform.isAndroid) {
- // 检查蓝牙扫描权限
- String error = "";
- bool isShowDialog = false;
- if (!await Permission.bluetoothScan.isGranted) {
- if (!isShowDialog) {
- isShowDialog = true;
- showPermissionInfoDialog(Get.context!, permissionInfo);
- }
- PermissionStatus status = await Permission.bluetoothScan.request();
- if (!status.isGranted) {
- error += "蓝牙扫描权限未开启,请开启附近设备权限";
- }
- print("蓝牙扫描 $status");
- }
-
- // 检查蓝牙连接权限
- if (!await Permission.bluetoothConnect.isGranted) {
- if (!isShowDialog) {
- isShowDialog = true;
- showPermissionInfoDialog(Get.context!, permissionInfo);
- }
- PermissionStatus status = await Permission.bluetoothConnect.request();
- if (!status.isGranted) {
- if (error.isNotEmpty) {
- error += "\n";
- }
- error += "蓝牙连接权限未开启,请开启蓝牙权限";
- }
- print("蓝牙连接 $status");
- }
-
- // 检查位置权限
- if (!await Permission.location.isGranted) {
- if (!isShowDialog) {
- isShowDialog = true;
- showPermissionInfoDialog(Get.context!, permissionInfo);
- }
- //检查
- PermissionStatus status = await Permission.location.request();
- print("位置权限 $status");
- if (!status.isGranted) {
- await showCustomConfirmAndCancelDialog(Get.context!, "请开启位置权限(打开精确位置)",
- confirmName: "去设置")
- .then((msg) async {
- if (msg == "confirm") {
- await openGeolocatorAppSettingsAndWait();
- // await Future.delayed(const Duration(seconds: 2));
- print('Proceeding with other operations');
- status = await Permission.location.request();
- }
- });
- }
- if (!status.isGranted) {
- if (error.isNotEmpty) {
- error += "\n";
- }
- error += "位置权限未开启,请开启位置权限";
- }
- }
-
- if (isShowDialog) {
- Get.back();
- }
-
- if (await Permission.bluetoothScan.isGranted &&
- await Permission.bluetoothConnect.isGranted &&
- await Permission.location.isGranted) {
- // bool b = await Geolocator.isLocationServiceEnabled();
- bool b =false;
- if (b == false) {
- await showCustomConfirmAndCancelDialog(Get.context!, "请开启系统位置开关",
- confirmName: "去设置")
- .then((msg) async {
- if (msg == "confirm") {
- // bool isOpen = await Geolocator.openLocationSettings();
- await openGeolocatorLocationSettingsAndWait();
- // await Future.delayed(const Duration(seconds: 2));
- print('Proceeding with other operations');
- // b = await Geolocator.isLocationServiceEnabled();
- }
- });
- }
- if (b) {
- isBleStart = true;
- startBluetoothScanning();
- return true;
- } else {
- showToast("系统位置开关未开启,请开启系统位置开关", closeTime: 7);
- return false;
- }
- } else {
- Timer(Duration.zero, () async {
- showToast(error, closeTime: 7);
- });
- return false;
- }
- } else {
- showToast("当前系统不支持蓝牙,无法使用此功能", closeTime: 7);
- return false;
- }
-}
-
-void find() {
- int len = devices.length;
- String reg = findInput.toLowerCase().replaceAll(RegExp("[::]"), "");
- List list = devices.values.toList();
- for (int i = 0; i < len; i++) {
- Map d = list[i];
- bool flag = d['rssi'] >= rssichange;
- if (flag) {
- bool a = d['name'].toString().toLowerCase().contains(reg);
- a = a ||
- d['id']
- .toString()
- .toLowerCase()
- .replaceAll(RegExp("[::]"), "")
- .contains(reg);
- // if(d.adData && d.adData.deviceId) {
- // a = a || d.adData.deviceId.toLowerCase().replace(/[::]/g, "").indexOf(reg) > -1
- // }
- // if(d.adData && d.adData.version && reg) {
- // a = a || (d.adData.version + "").indexOf(reg) > -1
- // }
- flag = flag && a;
- }
- if (flag) {
- flag = flag &&
- DateTime.now().millisecondsSinceEpoch - d['updateTime'] < closeTime;
- }
- if (!flag) {
- d['isClose'] = true;
- } else {
- d['isClose'] = false;
- }
- }
- var result = list.where((item) => item['isClose'] == false).toList();
- if (result == null) {
- findCall?.call([]);
- } else {
- // print(result);
- result.sort((a, b) {
- // print("${a['rssi']},${b['rssi']}");
- return b['rssi'] - a['rssi'];
- });
- if (result.length > 0) {
- findCall?.call(result.where((d) => d?['adData'] != null).toList());
- } else {
- findCall?.call(result);
- }
- }
-}
-
-String ab2str(List buffer) {
- return buffer.map((x) => x.toRadixString(16).padLeft(2, '0')).join('');
-}
-
-void advertisDataFormatter(var a, item) {
- Map obj = {};
- try {
- if (a[2] == 1) {
- obj['sn'] = a[3];
- obj['deviceId'] = ab2str(a.sublist(4, 10)).toUpperCase();
- obj['b'] = a[10];
- obj['h'] = a[11];
- obj['t'] = a[12];
- item['adData'] = obj;
- } else if (a[2] == 2) {
- obj['sn'] = a[3];
- obj['deviceId'] = ab2str(a.sublist(4, 10)).toUpperCase();
- obj['b'] = a[10];
- obj['h'] = a[11];
- obj['t'] = a[12];
- obj['net'] = (a[13] & 1) == 1 ? '在线' : '离线';
- obj['flag'] = (a[13] & 2) == 2 ? '异常' : '正常';
- ByteData byteData = ByteData.sublistView(
- Uint8List.fromList(a.sublist(14, 18).reversed.toList()));
- obj['version'] = byteData.getUint32(0);
- item['adData'] = obj;
- } else if (a[2] == 3) {
- List otherstr = [];
- obj['sn'] = a[3];
- obj['deviceId'] = ab2str(a.sublist(4, 10)).toUpperCase();
- obj['b'] = a[10];
- obj['h'] = a[11];
- obj['t'] = a[12];
- obj['net'] = (a[13] & 1) == 1 ? '在线' : '离线';
- obj['flag'] = (a[13] & 2) == 2 ? '异常' : '正常';
-
- if ((a[13] & 4) == 4) {
- otherstr.add('呼吸暂停');
- }
-
- if ((a[13] & 8) == 8 && (a[13] & 1) == 1) {
- obj['isbed'] = '在床';
- } else {
- obj['isbed'] = '离床';
- }
-
- if ((a[13] & 16) == 16) {
- otherstr.add('授权过期');
- }
-
- if ((a[13] & 64) == 64) {
- otherstr.add('设备休眠');
- }
-
- obj['other'] = otherstr.join('、');
-
- ByteData byteData = ByteData.sublistView(
- Uint8List.fromList(a.sublist(14, 18).reversed.toList()));
- obj['version'] = byteData.getUint32(0);
-
- ByteData qsnData =
- ByteData.sublistView(Uint8List.fromList(a.sublist(17, 19)));
- obj['qsn'] = qsnData.getUint16(0) * 256 + obj['sn'];
-
- item['adData'] = obj;
- } else if (a.length > 17) {
- obj['sn'] = a[3];
- obj['deviceId'] = ab2str(a.sublist(4, 10)).toUpperCase();
- obj['b'] = a[10];
- obj['h'] = a[11];
- obj['t'] = a[12];
- obj['net'] = (a[13] & 1) == 1 ? '在线' : '离线';
- obj['flag'] = (a[13] & 2) == 2 ? '异常' : '正常';
-
- ByteData byteData = ByteData.sublistView(
- Uint8List.fromList(a.sublist(14, 18).reversed.toList()));
- obj['version'] = byteData.getUint32(0);
-
- item['adData'] = obj;
- }
- } catch (e) {
- print(e);
- }
-}
-
-void startBluetoothScanning() async {
- // 开始扫描附近的蓝牙设备
- if (FlutterBluePlus.isScanningNow) {
- await FlutterBluePlus.stopScan();
- }
- FlutterBluePlus.startScan(timeout: const Duration(seconds: 15));
-}
-
-getOneConnectedDeviceProp(id) {
- return connectList[id];
-}
-
-void setOther(device, connectedDeviceProp, fun) async {
- try {
- List services = await device.discoverServices();
- print(services);
- bool isNotify = false;
- bool isWrite = false;
- for (var service in services) {
- if (connectedDeviceProp.connectedDevicePropType ==
- ConnectedDevicePropType.JunHe) {
- if (service.uuid.str128.toUpperCase() != myuuid) {
- continue;
- }
- }
- // print("serviece $service");
- for (BluetoothCharacteristic element in service.characteristics) {
- if (isNotify == false && element.properties.notify) {
- await element.setNotifyValue(true);
- print("setNotifyValue 完成");
- connectedDeviceProp.createLisetenReceive(element);
- isNotify = true;
- if (connectedDeviceProp.connectedDevicePropType ==
- ConnectedDevicePropType.JunHe) {
- continue;
- }
- if (connectedDeviceProp.connectedDevicePropType ==
- ConnectedDevicePropType.MHT) {
- continue;
- }
- }
- if (isWrite == false && element.properties.write) {
- connectedDeviceProp?.writeCharacteristic = element;
- isWrite = true;
- print("$element");
- }
- }
- }
- if (!isWrite || !isNotify) {
- if (connectedDeviceProp != null) {
- disconnect(connectedDeviceProp!);
- }
- print("service 订阅失败 isWrite $isWrite isNotify $isNotify");
- fun['fail']?.call("service 订阅失败 isWrite $isWrite isNotify $isNotify");
- return;
- }
- print("service 注册完成");
- connectList[connectedDeviceProp.id] = connectedDeviceProp;
- connectedDeviceProp.createListenState();
- if (connectedDeviceProp.connectedDevicePropType ==
- ConnectedDevicePropType.JunHe) {
- connectedDeviceProp.heartbeat();
- }
- print("回调成功");
- fun['success']?.call(connectedDeviceProp);
- } catch (e) {
- print("连接失败 执行失败回调 错误: $e");
- if (connectedDeviceProp != null) {
- disconnect(connectedDeviceProp!);
- }
- print("连接失败 执行失败回调");
- fun['fail']?.call(e);
- }
-}
-
-// 连接设备
-void connectToDevice(fun) async {
- if (fun != null) {
- return;
- }
- BluetoothDevice device = fun.device;
- ConnectedDeviceProp? connectedDeviceProp =
- getOneConnectedDeviceProp(device.remoteId.str);
- if (connectedDeviceProp != null) {
- disconnect(connectedDeviceProp);
- Future.delayed(const Duration(seconds: 1), () {
- connectToDevice(fun);
- });
- return;
- }
- try {
- print("connecting");
- await device.connect(timeout: const Duration(seconds: 8));
- print("device.connect success");
- ConnectedDevicePropType connectedDevicePropType =
- ConnectedDevicePropType.JunHe;
- connectedDeviceProp = ConnectedDeviceProp(
- connectDevice: device,
- fun: fun,
- connectedDevicePropType: connectedDevicePropType);
- if (Platform.isAndroid) {
- await device.requestMtu(mcuMax);
- }
- Timer(const Duration(milliseconds: 1000), () {
- setOther(device, connectedDeviceProp, fun);
- });
- } catch (e) {
- print("连接失败 执行失败回调 错误: $e");
- if (connectedDeviceProp != null) {
- disconnect(connectedDeviceProp!);
- }
- print("连接失败 执行失败回调");
- fun['fail']?.call(e);
- }
-}
-
-bool jsJunHe(String name) {
- return "$name".contains("AITH-V2") || "$name".contains("AITH-V2") || "$name".contains("AITH-V2");
-}
-
-
-void disconnect(ConnectedDeviceProp connectedDeviceProp) {
- connectedDeviceProp.closeHeartBeat();
- connectList.remove(connectedDeviceProp.id);
- connectedDeviceProp.closeConnectedDeviceProp();
-}
-
-void closeAll() {
- findCall = null;
- connectList.values.toList().forEach((element) {
- disconnect(element);
- });
-}
-
-enum ConnectedDevicePropType { JunHe, QuanShi, MHT }
-
-class ConnectedDeviceProp {
- ConnectedDevicePropType connectedDevicePropType;
- Timer? heartbeatTimer = null;
- int _seq = 0;
- var connectDevice;
- var writeCharacteristic;
- var listenState;
- StreamSubscription>? lisetenReceive;
- Map fun;
- List receiveMethods = [];
- List logList = [];
- Function? logChange;
- ConnectedDeviceProp(
- {required this.connectDevice,
- required Map this.fun,
- this.connectedDevicePropType = ConnectedDevicePropType.JunHe});
- List receiveLogArr = [];
- int deviceType = 2;
- int encodeType = 2;
- List sendArr = [];
- double sendExecAverage = 100;
- bool isClose = false;
-
- String get id {
- return connectDevice.remoteId.str;
- }
-
- int sum_ab(dv) {
- ByteData sum = ByteData(1);
- for (int i = 0; i < dv.buffer.lengthInBytes; i++) {
- sum.setUint8(0, dv.getUint8(i) + sum.getUint8(0));
- }
- return sum.getUint8(0);
- }
-
- void heartbeat() {
- closeHeartBeat();
- heartbeatTimer = Timer.periodic(const Duration(seconds: 8), (timer) {
- ByteData dv = ByteData(4);
- dv.setUint8(0, 4);
- dv.setUint8(2, seq);
- dv.setUint8(3, 5);
- dv.setUint8(1, sum_ab(dv));
- writeBle(dv);
- });
- }
-
- closeHeartBeat() {
- if (heartbeatTimer != null) {
- heartbeatTimer!.cancel();
- heartbeatTimer = null;
- }
- }
-
- ByteData str2ab_oneByte(String str, {int startLength = 0}) {
- Uint8List utf8str = utf8.encode(str);
- int len = utf8str.length + startLength;
- ByteData buf2 = ByteData.sublistView(utf8str);
- ByteData buf = ByteData(len);
- for (int i = startLength; i < len; i++) {
- buf.setUint8(i, buf2.getUint8(i - startLength));
- }
- return buf;
- }
-
- void write3OfString(sendDate, {Function? success, Function? fail}) {
- ByteData dv = str2ab_oneByte(sendDate, startLength: 4);
- int len = dv.buffer.lengthInBytes;
- dv.setUint8(0, len);
- dv.setUint8(2, seq);
- dv.setUint8(3, 8 * 16 + 3);
- dv.setUint8(1, sum_ab(dv));
- writeBle(dv, success: success, fail: fail);
- }
-
- void writeBle(ByteData d, {Function? success, Function? fail}) {
- Uint8List d_ = Uint8List.view(d.buffer);
- if (sendArr.length == 0) {
- write(d_, success, fail);
- }
- sendArr.insert(0, {"d": d_, "success": success, "fail": fail});
- }
-
- void write(Uint8List d, Function? success, Function? fail, {int exec = 100}) {
- if (writeCharacteristic != null) {
- // try {
- // if (d[3] == 8 * 16 + 3) {
- // print(
- // "blewrite s = $sendExecAverage d = ${utf8.decode(d.sublist(4))}");
- // } else {
- // print("ble last write d = ${d[3]}");
- // }
- // } catch (e) {
- // print("write logprint error $e");
- // }
- writeCharacteristic.write(d, withoutResponse: true).then((e) {
- // print("write success $e");
- if (connectedDevicePropType != ConnectedDevicePropType.JunHe) {
- print("发送 $d");
- }
- if (exec > 95) {
- sendExecAverage = sendExecAverage + 0.5;
- }
- if (sendExecAverage > 99) {
- sendExecAverage = 99;
- }
- if (sendArr.length > 0) {
- sendArr.removeLast();
- Map last = sendArr.last;
- write(last["d"], last["success"], last["fail"]);
- }
- success?.call();
- }).catchError((e) {
- // print("exec = $exec , $e");
- if (exec < 0) {
- print("$e");
- fail?.call();
- }
- if (exec > -1 && isClose == false) {
- int time = ((100.0 - sendExecAverage) * 5.0).toInt();
- if (exec < 80) {
- time = (100 - exec) * 5;
- sendExecAverage = exec * 1.0;
- } else {
- sendExecAverage = sendExecAverage - (100 - exec) * 0.1;
- }
- Timer(Duration(milliseconds: time), () {
- write(d, success, fail, exec: exec - 1);
- });
- }
- });
- }
- }
-
- void read6() {
- ByteData dv = ByteData(4);
- dv.setUint8(0, 4);
- dv.setUint8(2, seq);
- dv.setUint8(3, 6);
- dv.setUint8(1, sum_ab(dv));
- writeBle(dv);
- }
-
- addLog(String log) {
- if (logList.length > 500) {
- logList.removeRange(0, 50);
- }
- DateTime date = DateTime.now();
- String h = date.hour > 10 ? "${date.hour}" : "0${date.hour}";
- String m = date.minute > 10 ? "${date.minute}" : "0${date.minute}";
- String s = date.second > 10 ? "${date.second}" : "0${date.second}";
- logList.add({"time": "$h:$m:$s", "value": log});
- print("ble $id log: $log");
- if (logChange != null) {
- logChange?.call(logList, log);
- }
- }
-
- createListenState() {
- listenState = connectDevice.connectionState.listen((state) {
- print('ble Device state $id $state');
- if (state == BluetoothConnectionState.disconnected) {
- print('ble Device state $id disconnected');
- isClose = true;
- disconnect(this);
- fun['stateChange']?.call(state, this);
- }
- });
- }
-
- createLisetenReceive(BluetoothCharacteristic element) {
- lisetenReceive = element.onValueReceived.listen((List value) {
- if (connectedDevicePropType == ConnectedDevicePropType.JunHe) {
- if (value.isEmpty) {
- return;
- }
- bool isOk = sumCheck(value);
- if (isOk) {
- receiveMethods.forEach((m) {
- m?.call();
- });
- yewuSwitch(value[3], value.sublist(4));
- }
- } else {
- print("onValueReceived $value");
- receiveLogArr.forEach((m) {
- m(value);
- });
- }
- });
- }
-
- closeConnectedDeviceProp() {
- isClose = true;
- if (listenState != null) {
- listenState?.cancel();
- }
- if (lisetenReceive != null) {
- lisetenReceive?.cancel();
- }
- connectDevice?.disconnect();
- }
-
- int get seq {
- int r = _seq % 256;
- _seq++;
- return r;
- }
-
- bool sumCheck(List ab) {
- ByteData dv = ByteData.sublistView(Uint8List.fromList(ab));
-
- if (dv.getUint8(0) != ab.length) {
- print("和校验失败:长度不对");
- return false;
- } //和校验失败
-
- if (sumList(ab) != dv.getUint8(1)) {
- print("和校验失败: 校验失败");
- return false;
- }
-
- return true;
- }
-
- int sumList(List ab) {
- ByteData dv = ByteData.sublistView(Uint8List.fromList(ab));
- ByteData sum = ByteData.sublistView(Uint8List(1));
-
- sum.setUint8(0, dv.getUint8(0));
-
- for (int i = 2; i < ab.length; i++) {
- sum.setUint8(0, dv.getUint8(i) + sum.getUint8(0));
- }
-
- return sum.getUint8(0);
- }
-
- List endLogValue = [];
- Timer? endLogTimer;
-
- void yewuSwitch(int yewu, List abData) {
- switch (yewu) {
- case 7:
- String error = ab2StrByType(abData);
- print(error);
- break;
-
- case 131:
- List? logData;
- if (abData.last != 10) {
- int index = abData.lastIndexOf(10);
- if (index == -1) {
- index = abData.length;
- endLogValue = [...endLogValue, ...abData.sublist(0, index)];
- } else {
- logData = [...endLogValue, ...abData.sublist(0, index)];
- endLogValue = abData.sublist(index);
- }
- // if (index == -1) {
- // index = abData.length;
- // }
- // if(endLogValue.isNotEmpty) {
- // logData = [...endLogValue, ...abData.sublist(0, index)];
- // endLogValue = [];
- // } else {
- // logData = [...abData.sublist(0, index)];
- // }
- // if(index != abData.length) {
- // endLogValue = abData.sublist(index);
- // }
- } else {
- int index = abData.length;
-
- if (endLogValue.isNotEmpty) {
- logData = [...endLogValue, ...abData];
- } else {
- logData = abData;
- }
-
- endLogValue = [];
- }
-
- if (endLogTimer != null) {
- endLogTimer!.cancel();
- endLogTimer = null;
- }
-
- if (endLogValue != null && endLogValue!.isNotEmpty) {
- endLogTimer = Timer(Duration(milliseconds: 400), () {
- String log = ab2StrByType(endLogValue!);
- endLogValue = [];
- addLog(log);
- try {
- receiveLogArr.forEach((m) {
- m(log);
- });
- } catch (e) {
- print(e);
- }
- });
- }
-
- if (logData != null && logData.isNotEmpty) {
- if (logData.length != 1 || logData[0] != 13) {
- String log = ab2StrByType(logData!);
- addLog(log);
- try {
- receiveLogArr.forEach((m) {
- m(log);
- });
- } catch (e) {
- print(e);
- }
- }
- }
- // 处理逻辑
- break;
-
- case 132:
- ByteData dv = ByteData.sublistView(Uint8List.fromList(abData));
-
- for (int i = 0; i < abData.length;) {
- int len = dv.getUint8(i);
- yewuSwitch(dv.getUint8(i + 1), abData.sublist(i + 2, i + 1 + len));
- i = i + 1 + len;
- }
-
- break;
-
- default:
- break;
- }
- }
-
- String ab2StrByType(List abData) {
- // Implement your logic for converting abData to String
- String str = "";
- if (abData.isNotEmpty) {
- try {
- str = utf8.decode(abData);
- } catch (e) {
- str = "解析错误";
- }
- }
- return str;
- }
-}
diff --git a/lib/common/util/DailyLogUtils.dart b/lib/common/util/DailyLogUtils.dart
new file mode 100644
index 0000000..7d1d5f7
--- /dev/null
+++ b/lib/common/util/DailyLogUtils.dart
@@ -0,0 +1,87 @@
+import 'dart:io';
+import 'package:path_provider/path_provider.dart';
+import 'package:intl/intl.dart';
+
+class DailyLogUtils {
+ // 获取日志文件路径(按日期命名)
+ static Future _getLogFile() async {
+ final dir = await getApplicationDocumentsDirectory();
+ final date = DateFormat('yyyy-MM-dd').format(DateTime.now());
+ final filePath = '${dir.path}/$date.log';
+ final file = File(filePath);
+ if (!await file.exists()) {
+ await file.create(recursive: true);
+ }
+ return file;
+ }
+
+ // 写入日志核心方法,带日志等级
+ static Future _writeLogWithLevel(String level, String content) async {
+ final file = await _getLogFile();
+ final now = DateTime.now();
+ final time = DateFormat('HH:mm:ss').format(now);
+ final logLine = '[$time][$level] $content\n';
+ await file.writeAsString(logLine, mode: FileMode.append);
+ }
+
+ // 写入 info 日志(原 writeLog 保留)
+ static Future writeLog(String content) async {
+ await _writeLogWithLevel('INFO', content);
+ }
+
+ // 写入 warning 日志
+ static Future writeWarning(String content) async {
+ await _writeLogWithLevel('WARNING', content);
+ }
+
+ // 写入 error 日志
+ static Future writeError(String content) async {
+ await _writeLogWithLevel('ERROR', content);
+ }
+
+ // 写入 debug 日志
+ static Future writeDebug(String content) async {
+ await _writeLogWithLevel('DEBUG', content);
+ }
+
+ // 读取当天日志
+ static Future readTodayLog() async {
+ final file = await _getLogFile();
+ return await file.readAsString();
+ }
+
+ // 获取所有日志文件(返回 File 列表)
+ static Future> listLogFiles() async {
+ final dir = await getApplicationDocumentsDirectory();
+ final files = dir.listSync();
+ return files.where((f) => f.path.endsWith('.log')).toList();
+ }
+
+ // 清除所有日志
+ static Future clearAllLogs() async {
+ final files = await listLogFiles();
+ for (final f in files) {
+ await File(f.path).delete();
+ }
+ }
+
+ /// 获取指定日期范围内的日志文件(包含起止日期)
+ static Future> getLogsBetween(
+ DateTime fromDate, DateTime toDate) async {
+ final dir = await getApplicationDocumentsDirectory();
+ final logFiles = [];
+ final dateFormat = DateFormat('yyyy-MM-dd');
+
+ for (DateTime date = fromDate;
+ !date.isAfter(toDate);
+ date = date.add(Duration(days: 1))) {
+ final fileName = '${dateFormat.format(date)}.log';
+ final file = File('${dir.path}/$fileName');
+ if (await file.exists()) {
+ logFiles.add(file);
+ }
+ }
+
+ return logFiles;
+ }
+}
diff --git a/lib/common/util/MyUtils.dart b/lib/common/util/MyUtils.dart
index 6ea7faf..4d597c8 100644
--- a/lib/common/util/MyUtils.dart
+++ b/lib/common/util/MyUtils.dart
@@ -118,6 +118,57 @@ class MyUtils {
curve: Curves.easeInOut,
);
}
+
+ static String formatBindTime(DateTime d) {
+ final DateFormat formatter = DateFormat('yyyy/MM/dd');
+ return formatter.format(d);
+ }
+
+ static DateTime? formatBirthdayTime(String? device) {
+ if (device == null || device.isEmpty) return null;
+
+ try {
+ return DateTime.parse(device.replaceAll('/', '-')); // 替换为标准格式
+ } catch (e) {
+ return null; // 解析失败时返回 null
+ }
+ }
+
+ static int getAgeByDate(DateTime? formatBirthdayTime) {
+ if (formatBirthdayTime == null) return 0;
+
+ final now = DateTime.now();
+ int age = now.year - formatBirthdayTime.year;
+
+ // 如果还没到今年生日,减一岁
+ if (now.month < formatBirthdayTime.month ||
+ (now.month == formatBirthdayTime.month &&
+ now.day < formatBirthdayTime.day)) {
+ age--;
+ }
+
+ return age;
+ }
+
+ static String formatDateTimeWeek(DateTime date) {
+ DateTime now = DateTime.now();
+ // 去除时间部分,仅比较年月日
+ DateTime today = DateTime(now.year, now.month, now.day);
+ DateTime target = DateTime(date.year, date.month, date.day);
+
+ if (target == today) {
+ return '今日';
+ }
+
+ const List weekdays = ['周日', '周一', '周二', '周三', '周四', '周五', '周六'];
+ return weekdays[date.weekday % 7]; // Dart中星期日是7,要映射到索引0
+ }
+
+ /// 返回 MM/dd 格式
+ static String formatDateTimeDay(DateTime date) {
+ String twoDigits(int n) => n.toString().padLeft(2, '0');
+ return '${twoDigits(date.month)}/${twoDigits(date.day)}';
+ }
}
Color stringToColor(String hexColor) {
diff --git a/lib/component/NullDataComponentWidget.dart b/lib/component/NullDataComponentWidget.dart
new file mode 100644
index 0000000..a69235f
--- /dev/null
+++ b/lib/component/NullDataComponentWidget.dart
@@ -0,0 +1,77 @@
+import 'package:ef/ef.dart';
+import 'package:flutter/material.dart';
+import 'package:flutter_svg/svg.dart';
+import 'package:vbvs_app/common/util/FitTool.dart';
+import 'package:vbvs_app/controller/theme_controller/ThemeController.dart';
+
+class NullDataWidget extends StatefulWidget {
+ const NullDataWidget({super.key});
+
+ @override
+ State createState() => _TestWidgetState();
+}
+
+class _TestWidgetState extends State {
+ final scaffoldKey = GlobalKey();
+
+ @override
+ Widget build(BuildContext context) {
+ ThemeController themeController = Get.find();
+ return GestureDetector(
+ child: Scaffold(
+ backgroundColor: Colors.transparent,
+ key: scaffoldKey,
+ body: SafeArea(
+ top: true,
+ child: Container(
+ decoration: BoxDecoration(
+ color: Colors.transparent,
+ ),
+ width: MediaQuery.sizeOf(context).width,
+ height: MediaQuery.sizeOf(context).height * 1,
+ child: Column(
+ mainAxisSize: MainAxisSize.max,
+ mainAxisAlignment: MainAxisAlignment.center,
+ children: [
+ Padding(
+ padding: EdgeInsetsDirectional.fromSTEB(0, 0.rpx, 0, 0),
+ child: Container(
+ width: 56.rpx,
+ height: 69.rpx,
+ // width: double.infinity,
+ decoration: BoxDecoration(),
+ child: SvgPicture.asset(
+ 'assets/img/icon/nulldata.svg',
+ fit: BoxFit.cover,
+ color: themeController.currentColor.sc4,
+ ),
+ ),
+ ),
+ Container(
+ width: MediaQuery.sizeOf(context).width,
+ height: MediaQuery.sizeOf(context).height * 0.04,
+ constraints: BoxConstraints(
+ minHeight: 40,
+ ),
+ child: Align(
+ alignment: AlignmentDirectional(0, 0),
+ child: Text(
+ '暂无数据'.tr,
+ style: FlutterFlowTheme.of(context).bodyMedium.override(
+ useGoogleFonts: false,
+ fontFamily: "PingFangSC",
+ letterSpacing: 0.0,
+ fontSize: 26.rpx,
+ color: themeController.currentColor.sc4,
+ ),
+ ),
+ ),
+ ),
+ ],
+ ),
+ ),
+ ),
+ ),
+ );
+ }
+}
diff --git a/lib/component/home_page/DynamicReportDetailWidget.dart b/lib/component/home_page/DynamicReportDetailWidget.dart
new file mode 100644
index 0000000..22a4082
--- /dev/null
+++ b/lib/component/home_page/DynamicReportDetailWidget.dart
@@ -0,0 +1,172 @@
+import 'package:ef/ef.dart';
+import 'package:flutter/material.dart';
+import 'package:flutter_svg/svg.dart';
+import 'package:flutterflow_ui/flutterflow_ui.dart';
+import 'package:vbvs_app/common/color/ServiceConstant.dart';
+import 'package:vbvs_app/common/color/appConstants.dart';
+import 'package:vbvs_app/common/util/FitTool.dart';
+import 'package:vbvs_app/common/util/MyUtils.dart';
+import 'package:vbvs_app/component/home_page/SleepDataModuleWidget.dart';
+import 'package:vbvs_app/component/home_page/SleepDateWidget.dart';
+import 'package:vbvs_app/component/tool/ClickableContainer.dart';
+import 'package:vbvs_app/controller/theme_controller/ThemeController.dart';
+
+class DynamicReportDetailWidget extends StatelessWidget {
+ final List sleepDateWidgets;
+ final List sleepDataModuleWidgets;
+ final ThemeController themeController = Get.find();
+ final Map targetDevice;
+
+ DynamicReportDetailWidget({
+ required this.sleepDateWidgets,
+ required this.sleepDataModuleWidgets,
+ required this.targetDevice,
+ });
+
+ @override
+ Widget build(BuildContext context) {
+ return Padding(
+ padding: EdgeInsetsDirectional.fromSTEB(0, 25.rpx, 0, 25.rpx),
+ child: Container(
+ width: double.infinity,
+ decoration: BoxDecoration(
+ color: themeController.currentColor.sc5,
+ borderRadius:
+ BorderRadius.circular(AppConstants().normal_container_radius),
+ ),
+ child: Padding(
+ padding:
+ EdgeInsetsDirectional.fromSTEB(30.rpx, 30.rpx, 30.rpx, 30.rpx),
+ child: Column(
+ mainAxisSize: MainAxisSize.max,
+ children: [
+ _buildHeader(context),
+ _buildSleepDateWidgets(),
+ SizedBox(height: 20.rpx),
+ _buildSleepDataModuleWidgets(),
+ ],
+ ),
+ ),
+ ),
+ );
+ }
+
+ Widget _buildHeader(BuildContext context) {
+ return Container(
+ width: double.infinity,
+ child: Row(
+ mainAxisSize: MainAxisSize.max,
+ mainAxisAlignment: MainAxisAlignment.spaceBetween,
+ children: [
+ Text(
+ '${targetDevice['person']?['name'] == null ? '未命名'.tr : targetDevice['person']['name']}',
+ style: FlutterFlowTheme.of(context).bodyMedium.override(
+ fontFamily: 'Inter',
+ fontSize: 30.rpx,
+ letterSpacing: 0.0,
+ color: themeController.currentColor.sc3,
+ ),
+ ),
+ ClickableContainer(
+ backgroundColor: Colors.transparent,
+ highlightColor: themeController.currentColor.sc3,
+ borderRadius: 0,
+ padding: EdgeInsets.zero,
+ onTap: () {
+ String mac = targetDevice['mac'];
+ List selectedWidgets = sleepDateWidgets
+ .where(
+ (widget) => widget.isSelected == true,
+ )
+ .toList();
+ DateTime dateTime = DateTime.fromMillisecondsSinceEpoch(
+ int.parse(selectedWidgets[0].time!));
+ String time = MyUtils.formatBindTime(dateTime);
+ String sleepReportUrl =
+ "${ServiceConstant.sleep_report_url}?mac=${mac}&token=${ServiceConstant.sleep_token}&date=${time}";
+ Get.toNamed("/sleepReportPage", arguments: sleepReportUrl);
+ },
+ child: Row(
+ mainAxisSize: MainAxisSize.max,
+ children: [
+ Text(
+ '首页.报告详情'.tr,
+ style: FlutterFlowTheme.of(context).bodyMedium.override(
+ fontFamily: 'Inter',
+ fontSize: 26.rpx,
+ letterSpacing: 0.0,
+ color: themeController.currentColor.sc3,
+ ),
+ ),
+ Padding(
+ padding: EdgeInsetsDirectional.fromSTEB(0, 6.rpx, 0, 0.rpx),
+ child: SvgPicture.asset(
+ 'assets/img/icon/arrow_right.svg',
+ width: 14.rpx,
+ height: 14.rpx,
+ color: themeController.currentColor.sc3,
+ ),
+ ),
+ ].divide(SizedBox(width: 22.rpx)),
+ ),
+ ),
+ ],
+ ),
+ );
+ }
+
+ Widget _buildSleepDateWidgets() {
+ return Container(
+ width: double.infinity,
+ decoration: BoxDecoration(),
+ child: SingleChildScrollView(
+ scrollDirection: Axis.horizontal,
+ child: Row(
+ mainAxisSize: MainAxisSize.max,
+ children: sleepDateWidgets
+ .map((widget) => widget)
+ .toList()
+ .divide(SizedBox(width: 20.rpx)),
+ ),
+ ),
+ );
+ }
+
+ Widget _buildSleepDataModuleWidgets() {
+ // bool hasData = sleepDataModuleWidgets.any((w) {
+ // final data = w.data;
+ // return data['state'] != null &&
+ // data['state'].toString().trim().isNotEmpty;
+ // });
+ bool hasData = sleepDataModuleWidgets.length > 0;
+
+ if (!hasData) {
+ return Container(
+ height: 100.rpx,
+ alignment: Alignment.center,
+ child: Text(
+ '暂无数据'.tr,
+ style: FlutterFlowTheme.of(Get.context!).bodyMedium.override(
+ fontFamily: 'Inter',
+ fontSize: 28.rpx,
+ color: themeController.currentColor.sc3,
+ ),
+ ),
+ );
+ }
+
+ return Container(
+ width: double.infinity,
+ child: SingleChildScrollView(
+ scrollDirection: Axis.horizontal,
+ child: Row(
+ mainAxisSize: MainAxisSize.max,
+ children: sleepDataModuleWidgets
+ .map((widget) => widget)
+ .toList()
+ .divide(SizedBox(width: 14.rpx)),
+ ),
+ ),
+ );
+ }
+}
diff --git a/lib/component/home_page/SleepDataModuleWidget.dart b/lib/component/home_page/SleepDataModuleWidget.dart
index 96bcbad..5d0cf74 100644
--- a/lib/component/home_page/SleepDataModuleWidget.dart
+++ b/lib/component/home_page/SleepDataModuleWidget.dart
@@ -1,14 +1,16 @@
-import 'package:ef/base/widget/flutterflow/FlutterFlowTheme.dart';
import 'package:ef/ef.dart';
import 'package:flutter/material.dart';
import 'package:flutterflow_ui/flutterflow_ui.dart';
import 'package:vbvs_app/common/color/appConstants.dart';
import 'package:vbvs_app/common/util/FitTool.dart';
import 'package:vbvs_app/common/util/MyUtils.dart';
+import 'package:vbvs_app/component/tool/ClickableContainer.dart';
import 'package:vbvs_app/controller/theme_controller/ThemeController.dart';
class SleepDataModuleWidget extends StatefulWidget {
- const SleepDataModuleWidget({super.key});
+ final Map data;
+
+ const SleepDataModuleWidget({super.key, required this.data});
@override
State createState() => _SleepDataModuleWidgetState();
@@ -33,111 +35,137 @@ class _SleepDataModuleWidgetState extends State {
@override
Widget build(BuildContext context) {
ThemeController themeController = Get.find();
- return Container(
- width: MediaQuery.sizeOf(context).width * 0.27,
- constraints: BoxConstraints(
- minWidth: 200.rpx,
- ),
- decoration: BoxDecoration(
- color: themeController.currentColor.sc5,
- borderRadius: BorderRadius.circular(20.rpx),
- ),
- child: Padding(
- padding: EdgeInsetsDirectional.fromSTEB(18.rpx, 18.rpx, 18.rpx, 22.rpx),
- child: Container(
- decoration: BoxDecoration(),
- child: Column(
- mainAxisSize: MainAxisSize.max,
- crossAxisAlignment: CrossAxisAlignment.start,
- children: [
- Text(
- '离床次数',
- style: FlutterFlowTheme.of(context).bodyMedium.override(
- fontFamily: 'Inter',
- fontSize: 26.rpx,
- letterSpacing: 0.0,
- color: themeController.currentColor.sc3,
+ return ClickableContainer(
+ backgroundColor: themeController.currentColor.sc5,
+ highlightColor: themeController.currentColor.sc3,
+ borderRadius: 20.rpx,
+ padding: EdgeInsetsDirectional.fromSTEB(18.rpx, 18.rpx, 18.rpx, 22.rpx),
+ onTap: () {
+ print('点击了离床次数卡片');
+ },
+ child: Container(
+ width: MediaQuery.sizeOf(context).width * 0.27,
+ constraints: BoxConstraints(
+ minWidth: 200.rpx,
+ minHeight: 161.rpx,
+ ),
+ child: Column(
+ mainAxisSize: MainAxisSize.max,
+ crossAxisAlignment: CrossAxisAlignment.start,
+ mainAxisAlignment: MainAxisAlignment.spaceEvenly,
+ children: [
+ Text(
+ '${widget.data['name']}',
+ style: FlutterFlowTheme.of(context).bodyMedium.override(
+ fontFamily: 'Inter',
+ fontSize: 26.rpx,
+ letterSpacing: 0.0,
+ color: themeController.currentColor.sc3,
+ ),
+ ),
+ Row(
+ mainAxisSize: MainAxisSize.max,
+ mainAxisAlignment: MainAxisAlignment.spaceBetween,
+ children: [
+ Row(
+ mainAxisSize: MainAxisSize.max,
+ crossAxisAlignment: CrossAxisAlignment.end,
+ children: [
+ Text(
+ '${widget.data['value']}',
+ style: FlutterFlowTheme.of(context).bodyMedium.override(
+ fontFamily: 'Inter',
+ fontSize: 40.rpx,
+ letterSpacing: 0.0,
+ color: themeController.currentColor.sc3,
+ ),
),
- ),
- // SizedBox(
- // height: 21.rpx,
- // ),
- Row(
- mainAxisSize: MainAxisSize.max,
- mainAxisAlignment: MainAxisAlignment.spaceBetween,
- children: [
- Row(
- mainAxisSize: MainAxisSize.max,
- crossAxisAlignment: CrossAxisAlignment.end,
- children: [
- Text(
- '4',
+ Padding(
+ padding: EdgeInsetsDirectional.fromSTEB(0, 0, 0, 10.rpx),
+ child: Text(
+ '${widget.data['unit'] ?? ''}',
style: FlutterFlowTheme.of(context).bodyMedium.override(
fontFamily: 'Inter',
- fontSize: 40.rpx,
+ fontSize: AppConstants().small_text_fontSize,
letterSpacing: 0.0,
color: themeController.currentColor.sc3,
),
),
- Padding(
- padding: EdgeInsetsDirectional.fromSTEB(
- 0.rpx, 0, 0.rpx, 10.rpx),
- child: Text(
- '次',
- style: FlutterFlowTheme.of(context)
- .bodyMedium
- .override(
- fontFamily: 'Inter',
- fontSize: AppConstants().small_text_fontSize,
- letterSpacing: 0.0,
- color: themeController.currentColor.sc3,
- ),
- ),
- ),
- ],
+ ),
+ ],
+ ),
+ // Container(
+ // width: MediaQuery.sizeOf(context).width * 0.07,
+ // height: MediaQuery.sizeOf(context).height * 0.014,
+ // constraints: BoxConstraints(
+ // minWidth: 43.rpx,
+ // minHeight: 36.rpx,
+ // ),
+ // child: FFButtonWidget(
+ // onPressed: () {
+ // print('Button pressed ...');
+ // },
+ // // text: '${widget.data['level']}',
+ // text: '${widget.data['level']}',
+ // options: FFButtonOptions(
+ // height: 40.rpx,
+ // padding: EdgeInsets.zero,
+ // // color: themeController.currentColor.sc14,
+ // color: stringToColor('${widget.data['color']}'),
+ // textStyle:
+ // FlutterFlowTheme.of(context).titleSmall.override(
+ // fontFamily: 'Inter Tight',
+ // color: themeController.currentColor.sc3,
+ // // color: stringToColor('${widget.data['color']}'),
+ // letterSpacing: 0.0,
+ // fontSize: 15.rpx,
+ // ),
+ // elevation: 0,
+ // borderRadius: BorderRadius.circular(8.rpx),
+ // ),
+ // ),
+ // ),
+
+ ClickableContainer(
+ backgroundColor: stringToColor('${widget.data['color']}'),
+ highlightColor: themeController.currentColor.sc3,
+ padding: EdgeInsets.symmetric(
+ horizontal: 0.rpx,
+ vertical: 0.rpx,
),
- Container(
- width: MediaQuery.sizeOf(context).width * 0.07,
- height: MediaQuery.sizeOf(context).height * 0.014,
+ borderRadius: 8.rpx,
+ onTap: () {
+ print('Button pressed ...');
+ },
+ child: Container(
+ alignment: Alignment.center,
constraints: BoxConstraints(
minWidth: 43.rpx,
minHeight: 36.rpx,
),
- decoration: BoxDecoration(),
- child: FFButtonWidget(
- onPressed: () {
- print('Button pressed ...');
- },
- text: '异常',
- options: FFButtonOptions(
- height: 40.rpx,
- padding:
- EdgeInsetsDirectional.fromSTEB(0.rpx, 0, 0.rpx, 0),
- color: themeController.currentColor.sc14,
- textStyle:
- FlutterFlowTheme.of(context).titleSmall.override(
- fontFamily: 'Inter Tight',
- color: themeController.currentColor.sc3,
- letterSpacing: 0.0,
- fontSize: 15.rpx,
- ),
- elevation: 0,
- borderRadius: BorderRadius.circular(8.rpx),
- ),
+ child: Text(
+ '${widget.data['level']}',
+ style: FlutterFlowTheme.of(context).titleSmall.override(
+ fontFamily: 'Inter Tight',
+ color: themeController.currentColor.sc3,
+ letterSpacing: 0.0,
+ fontSize: 15.rpx,
+ ),
),
),
- ],
- ),
- Text(
- '正常值:0~2',
- style: FlutterFlowTheme.of(context).bodyMedium.override(
+ ),
+ ].divide(SizedBox(width: 0.rpx)),
+ ),
+ Text(
+ '${widget.data['range']}',
+ style: FlutterFlowTheme.of(context).bodyMedium.override(
fontFamily: 'Inter',
fontSize: AppConstants().small_text_fontSize,
letterSpacing: 0.0,
- color: themeController.currentColor.sc4),
- ),
- ],
- ),
+ color: themeController.currentColor.sc4,
+ ),
+ ),
+ ],
),
),
);
diff --git a/lib/component/home_page/SleepDateWidget.dart b/lib/component/home_page/SleepDateWidget.dart
index 82838bf..6c7b400 100644
--- a/lib/component/home_page/SleepDateWidget.dart
+++ b/lib/component/home_page/SleepDateWidget.dart
@@ -6,10 +6,28 @@ import 'package:vbvs_app/common/color/appConstants.dart';
import 'package:vbvs_app/common/util/FitTool.dart';
import 'package:vbvs_app/common/util/MyUtils.dart';
import 'package:vbvs_app/component/tool/ClickableContainer.dart';
+import 'package:vbvs_app/controller/device/body_device_controller.dart';
import 'package:vbvs_app/controller/theme_controller/ThemeController.dart';
class SleepDateWidget extends StatefulWidget {
- const SleepDateWidget({super.key});
+ final String? mac;
+ final String? time; // 必传:日期,例如 "07/15"
+ final DateTime date; // 必传:日期,例如 "07/15"
+ final String? score; // 可选:分数,默认为 "--"
+ final String? comment; // 可选:评价,默认为 "暂无".tr
+ final Color? textColor; // 可选:文字颜色,默认为灰色
+ final bool? isSelected; // 是否选中
+
+ const SleepDateWidget({
+ super.key,
+ this.mac,
+ this.time,
+ required this.date,
+ this.score = '--',
+ this.comment = '暂无',
+ this.textColor,
+ this.isSelected = false, // 新增参数,默认不选中
+ });
@override
State createState() => _SleepDateWidgetState();
@@ -19,14 +37,35 @@ class _SleepDateWidgetState extends State {
@override
Widget build(BuildContext context) {
ThemeController themeController = Get.find();
+ BodyDeviceController bodyDeviceController = Get.find();
+ String week = MyUtils.formatDateTimeWeek(widget.date);
+ String day = MyUtils.formatDateTimeDay(widget.date);
+
+ // 选中时背景色为黑色,否则为透明
+ Color backgroundColor =
+ widget.isSelected == true ? Colors.black : Colors.transparent;
+
return ClickableContainer(
- backgroundColor: Colors.transparent, // 原 BoxDecoration 为空
- highlightColor:
- themeController.currentColor.sc3.withOpacity(0.1), // 自定义点击波纹颜色
- borderRadius: AppConstants().normal_container_radius, // 原来没设置圆角
+ backgroundColor: backgroundColor,
+ // highlightColor: themeController.currentColor.sc3.withOpacity(0.1),
+ highlightColor: Colors.transparent,
+ borderRadius: AppConstants().normal_container_radius,
padding: EdgeInsets.zero,
onTap: () {
- print("今日评分卡片点击");
+ final mac = widget.mac;
+ final time = widget.time;
+
+ if (bodyDeviceController.sleepReportData.value.containsKey(mac)) {
+ final list = bodyDeviceController.sleepReportData.value[mac];
+
+ for (var item in list!) {
+ item['selected'] = (item['time'] == time);
+ }
+ bodyDeviceController.sleepReportData.value = {
+ ...bodyDeviceController.sleepReportData.value,
+ };
+ bodyDeviceController.updateAll();
+ }
},
child: Container(
width: MediaQuery.sizeOf(context).width * 0.19,
@@ -36,54 +75,55 @@ class _SleepDateWidgetState extends State {
child: Padding(
padding:
EdgeInsetsDirectional.fromSTEB(10.rpx, 25.rpx, 10.rpx, 22.rpx),
- child: Container(
- child: Column(
- mainAxisSize: MainAxisSize.max,
- mainAxisAlignment: MainAxisAlignment.center,
- children: [
- Padding(
- padding: EdgeInsetsDirectional.fromSTEB(0, 0, 0, 14.rpx),
- child: Text(
- '今日',
- style: FlutterFlowTheme.of(context).bodyMedium.override(
- fontFamily: 'Inter',
- fontSize: AppConstants().title_text_fontSize,
- letterSpacing: 0.0,
- color: themeController.currentColor.sc3,
- ),
- ),
- ),
- Padding(
- padding: EdgeInsetsDirectional.fromSTEB(0, 0, 0, 33.rpx),
- child: Text(
- '07/15',
- style: FlutterFlowTheme.of(context).bodyMedium.override(
- fontFamily: 'Inter',
- fontSize: 20.rpx,
- letterSpacing: 0.0,
- color: themeController.currentColor.sc3,
- ),
- ),
- ),
- Padding(
- padding: EdgeInsetsDirectional.fromSTEB(0, 0, 0, 16.rpx),
- child: Row(
- mainAxisSize: MainAxisSize.max,
- mainAxisAlignment: MainAxisAlignment.center,
- children: [
- Text(
- '70',
- style: FlutterFlowTheme.of(context).bodyMedium.override(
- fontFamily: 'Inter',
- fontSize: 48.rpx,
- letterSpacing: 0.0,
- color: stringToColor("#00C1AA")),
+ child: Column(
+ mainAxisSize: MainAxisSize.max,
+ mainAxisAlignment: MainAxisAlignment.center,
+ children: [
+ Padding(
+ padding: EdgeInsetsDirectional.fromSTEB(0, 0, 0, 14.rpx),
+ child: Text(
+ '${week}',
+ style: FlutterFlowTheme.of(context).bodyMedium.override(
+ fontFamily: 'Inter',
+ fontSize: AppConstants().title_text_fontSize,
+ letterSpacing: 0.0,
+ color: themeController.currentColor.sc3,
),
+ ),
+ ),
+ Padding(
+ padding: EdgeInsetsDirectional.fromSTEB(0, 0, 0, 33.rpx),
+ child: Text(
+ '${day}',
+ style: FlutterFlowTheme.of(context).bodyMedium.override(
+ fontFamily: 'Inter',
+ fontSize: 20.rpx,
+ letterSpacing: 0.0,
+ color: themeController.currentColor.sc3,
+ ),
+ ),
+ ),
+ Padding(
+ padding: EdgeInsetsDirectional.fromSTEB(0, 0, 0, 16.rpx),
+ child: Row(
+ mainAxisSize: MainAxisSize.max,
+ mainAxisAlignment: MainAxisAlignment.center,
+ children: [
+ Text(
+ (widget.score?.isEmpty ?? true) ? '--' : widget.score!,
+ style: FlutterFlowTheme.of(context).bodyMedium.override(
+ fontFamily: 'Inter',
+ fontSize: 48.rpx,
+ letterSpacing: 0.0,
+ color: widget.textColor ??
+ themeController.currentColor.sc4),
+ ),
+ if ((widget.score?.trim().isNotEmpty ?? false))
Padding(
padding:
EdgeInsetsDirectional.fromSTEB(0, 16.rpx, 0, 0.rpx),
child: Text(
- '分',
+ '分'.tr,
style: FlutterFlowTheme.of(context)
.bodyMedium
.override(
@@ -94,40 +134,40 @@ class _SleepDateWidgetState extends State {
),
),
),
- ],
+ ],
+ ),
+ ),
+ Container(
+ width: 0.2.rpx,
+ height: 2.4.rpx,
+ constraints: BoxConstraints(
+ minWidth: 123.rpx,
+ minHeight: 47.rpx,
+ ),
+ child: FFButtonWidget(
+ onPressed: () {
+ print('合格按钮点击');
+ },
+ text: (widget.comment?.trim().isEmpty ?? true)
+ ? '暂无'.tr
+ : widget.comment!,
+ options: FFButtonOptions(
+ height: 40.rpx,
+ padding:
+ EdgeInsetsDirectional.fromSTEB(16.rpx, 0, 16.rpx, 0),
+ iconPadding: EdgeInsetsDirectional.fromSTEB(0, 0, 0, 0),
+ color: widget.textColor ?? themeController.currentColor.sc4,
+ textStyle: FlutterFlowTheme.of(context).titleSmall.override(
+ fontFamily: 'Inter Tight',
+ color: themeController.currentColor.sc3,
+ letterSpacing: 0.0,
+ ),
+ elevation: 0,
+ borderRadius: BorderRadius.circular(50.rpx),
),
),
- Container(
- width: 0.2.rpx,
- height: 2.4.rpx,
- constraints: BoxConstraints(
- minWidth: 123.rpx,
- minHeight: 47.rpx,
- ),
- child: FFButtonWidget(
- onPressed: () {
- print('合格按钮点击');
- },
- text: '合格',
- options: FFButtonOptions(
- height: 40.rpx,
- padding:
- EdgeInsetsDirectional.fromSTEB(16.rpx, 0, 16.rpx, 0),
- iconPadding: EdgeInsetsDirectional.fromSTEB(0, 0, 0, 0),
- color: stringToColor("#00C1AA"),
- textStyle:
- FlutterFlowTheme.of(context).titleSmall.override(
- fontFamily: 'Inter Tight',
- color: themeController.currentColor.sc3,
- letterSpacing: 0.0,
- ),
- elevation: 0,
- borderRadius: BorderRadius.circular(50.rpx),
- ),
- ),
- ),
- ],
- ),
+ ),
+ ],
),
),
),
diff --git a/lib/component/tool/CustomCard.dart b/lib/component/tool/CustomCard.dart
index 1c6975d..69b649f 100644
--- a/lib/component/tool/CustomCard.dart
+++ b/lib/component/tool/CustomCard.dart
@@ -1,4 +1,5 @@
import 'package:flutter/material.dart';
+import 'package:vbvs_app/common/util/FitTool.dart';
class CustomCard extends StatefulWidget {
final double borderRadius; // 圆角
@@ -15,7 +16,7 @@ class CustomCard extends StatefulWidget {
required this.colors,
required this.child,
this.enableAnimation = true, // 默认启用动画效果
- this.enableGradient = true, // 默认启用渐变效果
+ this.enableGradient = true, // 默认启用渐变效果
}) : super(key: key);
@override
@@ -25,36 +26,19 @@ class CustomCard extends StatefulWidget {
class _CustomCardState extends State
with SingleTickerProviderStateMixin {
double _scale = 1.0;
- final Duration _animationDuration = Duration(milliseconds: 50);
- final GlobalKey _inkKey = GlobalKey();
+ final Duration _animationDuration = const Duration(milliseconds: 50);
- Future _handleTap(TapDownDetails details) async {
- setState(() {
- _scale = 0.95;
- });
+ Future _handleTap() async {
+ if (widget.enableAnimation) {
+ setState(() {
+ _scale = 0.95;
+ });
- await Future.delayed(_animationDuration);
+ await Future.delayed(_animationDuration);
- setState(() {
- _scale = 1.0;
- });
-
- await Future.delayed(_animationDuration);
-
- // 手动触发水波纹
- final RenderBox? box =
- _inkKey.currentContext?.findRenderObject() as RenderBox?;
- if (box != null) {
- final Offset localPosition = box.globalToLocal(details.globalPosition);
- InkRipple.splashFactory.create(
- controller: Material.of(_inkKey.currentContext!)!,
- referenceBox: box,
- position: localPosition,
- color: widget.colors.first.withOpacity(0.2),
- containedInkWell: true,
- borderRadius: BorderRadius.circular(widget.borderRadius),
- textDirection: Directionality.of(context),
- );
+ setState(() {
+ _scale = 1.0;
+ });
}
widget.onTap();
@@ -62,51 +46,44 @@ class _CustomCardState extends State
@override
Widget build(BuildContext context) {
- final bool isGradient = widget.enableGradient && widget.colors.length > 1; // 只有启用渐变时,才使用渐变
+ final bool isGradient = widget.enableGradient && widget.colors.length > 1;
final Color baseColor = widget.colors.first;
return Material(
color: Colors.transparent,
borderRadius: BorderRadius.circular(widget.borderRadius),
- child: GestureDetector(
- onTapDown: _handleTap,
- behavior: HitTestBehavior.translucent, // 关键:让空白区域也能点击
- child: widget.enableAnimation // 判断是否启用动画
+ child: InkWell(
+ onTap: _handleTap,
+ borderRadius: BorderRadius.circular(widget.borderRadius),
+ splashColor: widget.colors.first.withOpacity(0.2),
+ child: widget.enableAnimation
? AnimatedScale(
scale: _scale,
duration: _animationDuration,
curve: Curves.easeInOut,
- child: Ink(
- key: _inkKey,
- decoration: BoxDecoration(
- color: isGradient ? null : baseColor,
- gradient: isGradient
- ? LinearGradient(
- colors: widget.colors,
- begin: Alignment.topLeft,
- end: Alignment.bottomRight,
- )
- : null,
- borderRadius: BorderRadius.circular(widget.borderRadius),
- ),
- child: widget.child,
- ),
+ child: _buildContent(isGradient, baseColor),
)
- : Ink(
- key: _inkKey,
- decoration: BoxDecoration(
- color: isGradient ? null : baseColor,
- gradient: isGradient
- ? LinearGradient(
- colors: widget.colors,
- begin: Alignment.topLeft,
- end: Alignment.bottomRight,
- )
- : null,
- borderRadius: BorderRadius.circular(widget.borderRadius),
- ),
- child: widget.child,
- ),
+ : _buildContent(isGradient, baseColor),
+ ),
+ );
+ }
+
+ Widget _buildContent(bool isGradient, Color baseColor) {
+ return Container(
+ decoration: BoxDecoration(
+ color: isGradient ? null : baseColor,
+ gradient: isGradient
+ ? LinearGradient(
+ colors: widget.colors,
+ begin: Alignment.topLeft,
+ end: Alignment.bottomRight,
+ )
+ : null,
+ borderRadius: BorderRadius.circular(widget.borderRadius),
+ ),
+ child: Padding(
+ padding: EdgeInsets.fromLTRB(0.rpx, 0.rpx, 0.rpx, 5.rpx),
+ child: widget.child,
),
);
}
diff --git a/lib/component/tool/SelectableTagButton.dart b/lib/component/tool/SelectableTagButton.dart
new file mode 100644
index 0000000..1b987e5
--- /dev/null
+++ b/lib/component/tool/SelectableTagButton.dart
@@ -0,0 +1,79 @@
+import 'package:ef/ef.dart';
+import 'package:flutter/material.dart';
+import 'package:vbvs_app/common/color/appConstants.dart';
+import 'package:vbvs_app/common/util/FitTool.dart';
+import 'package:vbvs_app/controller/theme_controller/ThemeController.dart';
+
+import 'CustomCard.dart';
+
+class SelectableTagButton extends StatelessWidget {
+ final String label;
+ final bool selected;
+ final VoidCallback onTap;
+ final double minWidth; // 最小宽度,单位:dp(会转 rpx)
+ final double maxWidth; // 最大宽度,单位:dp(会转 rpx)
+
+ ThemeController themeController = Get.find();
+
+ SelectableTagButton({
+ Key? key,
+ required this.label,
+ required this.selected,
+ required this.onTap,
+ this.minWidth = 132, // 默认最小宽度
+ this.maxWidth = 500, // 默认最大宽度
+ }) : super(key: key);
+
+ @override
+ Widget build(BuildContext context) {
+ final double minWidthRpx = minWidth.rpx;
+ final double maxWidthRpx = maxWidth.rpx;
+ final double horizontalPadding = 28.rpx * 2; // 左右各 28,总 56
+ // 估算文本宽度:每个字符按 14.rpx 字号估一个宽度
+ final double estimatedTextWidth = label.length * 33.rpx;
+
+ // 总宽度 = 文本宽度 + padding
+ final double totalWidth = estimatedTextWidth + horizontalPadding;
+
+ // 限制在 min 和 max 之间
+ final double constrainedWidth = totalWidth.clamp(minWidthRpx, maxWidthRpx);
+
+ return CustomCard(
+ onTap: onTap,
+ borderRadius: AppConstants().normal_container_radius,
+ colors: selected
+ ? [themeController.currentColor.sc1, themeController.currentColor.sc2]
+ : [Colors.transparent],
+ // colors: [Colors.transparent],
+ enableGradient: true,
+ child: Container(
+ decoration: BoxDecoration(
+ border: selected
+ ? null
+ : Border.all(
+ color: themeController.currentColor.sc4,
+ width: 1.rpx,
+ ), // 未选中时无边框
+ borderRadius: BorderRadius.circular(12.0), // 如果需要圆角
+ ),
+ padding: EdgeInsets.symmetric(horizontal: 28.rpx),
+ constraints: BoxConstraints(
+ minHeight: 61.rpx,
+ maxWidth: constrainedWidth,
+ ),
+ alignment: Alignment.center,
+ child: Text(
+ label,
+ overflow: TextOverflow.ellipsis,
+ maxLines: 1,
+ style: TextStyle(
+ color: selected
+ ? themeController.currentColor.sc3
+ : themeController.currentColor.sc4,
+ fontSize: AppConstants().normal_text_fontSize, // 字体也用 rpx 控制
+ ),
+ ),
+ ),
+ );
+ }
+}
diff --git a/lib/component/tool/TopSlideNotification.dart b/lib/component/tool/TopSlideNotification.dart
new file mode 100644
index 0000000..ca510d6
--- /dev/null
+++ b/lib/component/tool/TopSlideNotification.dart
@@ -0,0 +1,130 @@
+import 'package:flutter/material.dart';
+import 'package:flutter/scheduler.dart';
+import 'package:get/get.dart';
+import 'package:vbvs_app/common/util/FitTool.dart';
+import 'package:vbvs_app/common/util/MyUtils.dart';
+import 'package:vbvs_app/controller/theme_controller/ThemeController.dart';
+
+class TopSlideNotification extends StatefulWidget {
+ final String text;
+ double? fontSize = 26.rpx;
+ Color? textColor;
+ double? slideOffset = 200;
+ final Duration duration;
+
+ TopSlideNotification({
+ super.key,
+ this.text = '操作成功!',
+ this.fontSize,
+ this.textColor,
+ this.slideOffset,
+ this.duration = const Duration(seconds: 2),
+ });
+
+ @override
+ State createState() => _TopSlideNotificationState();
+
+ /// 工具方法:调用时直接加进 Overlay 上
+ static void show(
+ BuildContext context, {
+ String text = '操作成功!',
+ double fontSize = 16,
+ Color? textColor,
+ double slideOffset = 300.0,
+ Duration duration = const Duration(seconds: 2),
+ }) {
+ final overlay = Overlay.of(context);
+ final entry = OverlayEntry(
+ builder: (_) => TopSlideNotification(
+ text: text,
+ fontSize: fontSize,
+ textColor: textColor,
+ slideOffset: slideOffset,
+ duration: duration,
+ ),
+ );
+ overlay.insert(entry);
+
+ Future.delayed(duration + const Duration(milliseconds: 500), () {
+ entry.remove();
+ });
+ }
+}
+
+class _TopSlideNotificationState extends State
+ with SingleTickerProviderStateMixin {
+ late AnimationController _controller;
+ late Animation _animation;
+
+ @override
+ void initState() {
+ super.initState();
+
+ _controller = AnimationController(
+ duration: const Duration(milliseconds: 300),
+ vsync: this,
+ );
+
+ SchedulerBinding.instance.addPostFrameCallback((_) async {
+ await _controller.forward();
+ await Future.delayed(widget.duration);
+ await _controller.reverse();
+ });
+ }
+
+ @override
+ void didChangeDependencies() {
+ super.didChangeDependencies();
+
+ final screenHeight = MediaQuery.of(context).size.height;
+ final offsetValue = widget.slideOffset! / screenHeight;
+
+ _animation = Tween(
+ begin: const Offset(0, -1),
+ end: Offset(0, offsetValue),
+ ).animate(CurvedAnimation(
+ parent: _controller,
+ curve: Curves.easeOut,
+ reverseCurve: Curves.easeIn,
+ ));
+ }
+
+ @override
+ void dispose() {
+ _controller.dispose();
+ super.dispose();
+ }
+
+ Color get _textColor {
+ return widget.textColor ?? Get.find().currentColor.sc2;
+ }
+
+ @override
+ Widget build(BuildContext context) {
+ return Positioned(
+ top: 0,
+ left: 0,
+ right: 0,
+ child: SlideTransition(
+ position: _animation,
+ child: Material(
+ color: stringToColor("#000000").withOpacity(0.8),
+ child: Padding(
+ padding: const EdgeInsets.symmetric(vertical: 20.0),
+ child: Container(
+ // color: Colors.red,
+ child: Text(
+ widget.text,
+ textAlign: TextAlign.center,
+ style: TextStyle(
+ fontSize: widget.fontSize,
+ color: _textColor,
+ ),
+ ),
+ ),
+ ),
+ ),
+ ),
+ );
+ }
+}
diff --git a/lib/component/tool/WebViewWidget.dart b/lib/component/tool/WebViewWidget.dart
new file mode 100644
index 0000000..25c60df
--- /dev/null
+++ b/lib/component/tool/WebViewWidget.dart
@@ -0,0 +1,51 @@
+// import 'package:flutter/material.dart';
+// import 'package:webview_flutter/webview_flutter.dart';
+
+// class WebViewWidget extends StatefulWidget {
+// final String url;
+// const WebViewWidget({Key? key, required this.url}) : super(key: key);
+
+// @override
+// _WebViewWidgetState createState() => _WebViewWidgetState();
+// }
+
+// class _WebViewWidgetState extends State {
+// late WebViewController _webViewController;
+
+// @override
+// void initState() {
+// super.initState();
+// // 初始化 WebView 控件
+// WebView.platform = SurfaceAndroidWebView();
+// }
+
+// @override
+// Widget build(BuildContext context) {
+// return Scaffold(
+// appBar: AppBar(
+// title: Text('WebView'),
+// ),
+// body: WebView(
+// initialUrl: widget.url, // 设置要打开的网页地址
+// javascriptMode: JavascriptMode.unrestricted, // 启用 JavaScript
+// onWebViewCreated: (WebViewController webViewController) {
+// _webViewController = webViewController;
+// },
+// onPageStarted: (String url) {
+// print("页面开始加载:$url");
+// },
+// onPageFinished: (String url) {
+// print("页面加载完成:$url");
+// },
+// navigationDelegate: (NavigationRequest request) {
+// if (request.url.startsWith('https://www.google.com/')) {
+// print('拦截了URL请求: ${request.url}');
+// return NavigationDecision.prevent; // 拦截特定的请求
+// }
+// return NavigationDecision.navigate;
+// },
+// gestureNavigationEnabled: true, // 启用手势返回
+// ),
+// );
+// }
+// }
diff --git a/lib/component/tool/cmd.dart b/lib/component/tool/cmd.dart
new file mode 100644
index 0000000..8ad6c1e
--- /dev/null
+++ b/lib/component/tool/cmd.dart
@@ -0,0 +1,166 @@
+//蓝牙指令
+
+// wifi列表指令
+import 'package:EasyDartModule/EasyDartModule.dart' as edm;
+import 'package:easydevice/src/app/thapp.dart';
+import 'package:vbvs_app/common/util/DailyLogUtils.dart';
+
+getWifiList(THapp tHapp) async {
+ try {
+ edm.EasyDartModule.logger.info("发送请求网络列表指令");
+ DailyLogUtils.writeLog("发送请求网络列表指令");
+ List data = [];
+ var wifilist = await tHapp.send("wscan scan", true, (log) {
+ print("[bles]${log.log}");
+ if (log.log.contains("SCAN RESULT OVER!")) {
+ final wifiList =