diff --git a/assets/lottie/intro_foriday.json b/assets/lottie/intro_foriday.json
new file mode 100644
index 000000000..e030941b4
--- /dev/null
+++ b/assets/lottie/intro_foriday.json
@@ -0,0 +1 @@
+{"nm":"Comp 1","ddd":0,"h":700,"w":700,"meta":{"g":"@lottiefiles/toolkit-js 0.22.1"},"layers":[{"ty":0,"nm":"Pre-comp 3","sr":1,"st":141.000005743048,"op":231.000009408823,"ip":141.000005743048,"hd":false,"ddd":0,"bm":0,"hasMask":false,"ao":0,"ks":{"a":{"a":0,"k":[350,350,0],"ix":1},"s":{"a":0,"k":[100,100,100],"ix":6},"sk":{"a":0,"k":0},"p":{"a":0,"k":[350,350,0],"ix":2},"r":{"a":0,"k":0,"ix":10},"sa":{"a":0,"k":0},"o":{"a":0,"k":100,"ix":11}},"ef":[],"w":700,"h":700,"refId":"comp_0","ind":1},{"ty":0,"nm":"Pre-comp 3","sr":1,"st":71.0000028918893,"op":161.000006557664,"ip":71.0000028918893,"hd":false,"ddd":0,"bm":0,"hasMask":false,"ao":0,"ks":{"a":{"a":0,"k":[350,350,0],"ix":1},"s":{"a":0,"k":[100,100,100],"ix":6},"sk":{"a":0,"k":0},"p":{"a":0,"k":[350,350,0],"ix":2},"r":{"a":0,"k":0,"ix":10},"sa":{"a":0,"k":0},"o":{"a":0,"k":100,"ix":11}},"ef":[],"w":700,"h":700,"refId":"comp_0","ind":2},{"ty":0,"nm":"Pre-comp 3","sr":1,"st":0,"op":90.0000036657751,"ip":0,"hd":false,"ddd":0,"bm":0,"hasMask":false,"ao":0,"ks":{"a":{"a":0,"k":[350,350,0],"ix":1},"s":{"a":0,"k":[100,100,100],"ix":6},"sk":{"a":0,"k":0},"p":{"a":0,"k":[350,350,0],"ix":2},"r":{"a":0,"k":0,"ix":10},"sa":{"a":0,"k":0},"o":{"a":0,"k":100,"ix":11}},"ef":[],"w":700,"h":700,"refId":"comp_0","ind":3},{"ty":0,"nm":"bubble","sr":1,"st":0,"op":600.000024438501,"ip":0,"hd":false,"ddd":0,"bm":0,"hasMask":false,"ao":0,"ks":{"a":{"a":0,"k":[350,350,0],"ix":1},"s":{"a":0,"k":[91,91,100],"ix":6},"sk":{"a":0,"k":0},"p":{"a":0,"k":[350,350,0],"ix":2},"r":{"a":0,"k":43,"ix":10},"sa":{"a":0,"k":0},"o":{"a":0,"k":70,"ix":11}},"ef":[{"ty":0,"mn":"ADBE Gaussian Blur 2","nm":"Gaussian Blur","ix":1,"en":1,"ef":[{"ty":0,"mn":"ADBE Gaussian Blur 2-0001","nm":"Blurriness","ix":1,"v":{"a":0,"k":40.5,"ix":1}},{"ty":7,"mn":"ADBE Gaussian Blur 2-0002","nm":"Blur Dimensions","ix":2,"v":{"a":0,"k":1,"ix":2}},{"ty":7,"mn":"ADBE Gaussian Blur 2-0003","nm":"Repeat Edge Pixels","ix":3,"v":{"a":0,"k":0,"ix":3}}]}],"w":700,"h":700,"refId":"comp_4","ind":4},{"ty":0,"nm":"bubble","sr":1,"st":0,"op":600.000024438501,"ip":0,"hd":false,"ddd":0,"bm":0,"hasMask":false,"ao":0,"ks":{"a":{"a":0,"k":[350,350,0],"ix":1},"s":{"a":0,"k":[100,100,100],"ix":6},"sk":{"a":0,"k":0},"p":{"a":0,"k":[350,350,0],"ix":2},"r":{"a":0,"k":226,"ix":10},"sa":{"a":0,"k":0},"o":{"a":0,"k":48,"ix":11}},"ef":[{"ty":0,"mn":"ADBE Tint","nm":"Tint","ix":1,"en":1,"ef":[{"ty":2,"mn":"ADBE Tint-0001","nm":"Map Black To","ix":1,"v":{"a":0,"k":[0,0,0,0],"ix":1}},{"ty":2,"mn":"ADBE Tint-0002","nm":"Map White To","ix":2,"v":{"a":0,"k":[1,0,0.7765],"ix":2}},{"ty":0,"mn":"ADBE Tint-0003","nm":"Amount to Tint","ix":3,"v":{"a":0,"k":100,"ix":3}},{"ty":6,"mn":"ADBE Tint-0004","nm":"","ix":4,"v":0}]}],"w":700,"h":700,"refId":"comp_4","ind":5},{"ty":0,"nm":"bubble","sr":1,"st":0,"op":600.000024438501,"ip":0,"hd":false,"ddd":0,"bm":0,"hasMask":false,"ao":0,"ks":{"a":{"a":0,"k":[350,350,0],"ix":1},"s":{"a":0,"k":[100,100,100],"ix":6},"sk":{"a":0,"k":0},"p":{"a":0,"k":[350,350,0],"ix":2},"r":{"a":0,"k":0,"ix":10},"sa":{"a":0,"k":0},"o":{"a":0,"k":100,"ix":11}},"ef":[],"w":700,"h":700,"refId":"comp_4","ind":6}],"v":"5.5.5","fr":29.9700012207031,"op":231.000009408823,"ip":0,"assets":[{"nm":"","id":"comp_0","layers":[{"ty":0,"nm":"Pre-comp 2","sr":1,"st":13.0000005295009,"op":90.0000036657751,"ip":13.0000005295009,"hd":false,"ddd":0,"bm":0,"hasMask":false,"ao":0,"ks":{"a":{"a":0,"k":[350,350,0],"ix":1},"s":{"a":0,"k":[100,100,100],"ix":6},"sk":{"a":0,"k":0},"p":{"a":0,"k":[350,350,0],"ix":2},"r":{"a":0,"k":42,"ix":10},"sa":{"a":0,"k":0},"o":{"a":0,"k":55,"ix":11}},"ef":[],"w":700,"h":700,"refId":"comp_1","ind":1},{"ty":0,"nm":"Pre-comp 2","sr":1,"st":0,"op":77.0000031362743,"ip":0,"hd":false,"ddd":0,"bm":0,"hasMask":false,"ao":0,"ks":{"a":{"a":0,"k":[350,350,0],"ix":1},"s":{"a":0,"k":[100,100,100],"ix":6},"sk":{"a":0,"k":0},"p":{"a":0,"k":[350,350,0],"ix":2},"r":{"a":0,"k":0,"ix":10},"sa":{"a":0,"k":0},"o":{"a":0,"k":100,"ix":11}},"ef":[],"w":700,"h":700,"refId":"comp_1","ind":2}]},{"nm":"","id":"comp_1","layers":[{"ty":0,"nm":"Pre-comp 1","sr":1,"st":9.00000036657752,"op":77.0000031362743,"ip":9.00000036657752,"hd":false,"ddd":0,"bm":0,"hasMask":false,"ao":0,"ks":{"a":{"a":0,"k":[350,350,0],"ix":1},"s":{"a":0,"k":[100,100,100],"ix":6},"sk":{"a":0,"k":0},"p":{"a":0,"k":[350,350,0],"ix":2},"r":{"a":0,"k":69,"ix":10},"sa":{"a":0,"k":0},"o":{"a":0,"k":100,"ix":11}},"ef":[{"ty":0,"mn":"ADBE Tint","nm":"Tint","ix":1,"en":1,"ef":[{"ty":2,"mn":"ADBE Tint-0001","nm":"Map Black To","ix":1,"v":{"a":0,"k":[0,0,0,0],"ix":1}},{"ty":2,"mn":"ADBE Tint-0002","nm":"Map White To","ix":2,"v":{"a":0,"k":[1,0.4078,0.6588],"ix":2}},{"ty":0,"mn":"ADBE Tint-0003","nm":"Amount to Tint","ix":3,"v":{"a":0,"k":100,"ix":3}},{"ty":6,"mn":"ADBE Tint-0004","nm":"","ix":4,"v":0}]}],"w":700,"h":700,"refId":"comp_2","ind":1},{"ty":0,"nm":"Pre-comp 1","sr":1,"st":0,"op":68.0000027696968,"ip":0,"hd":false,"ddd":0,"bm":0,"hasMask":false,"ao":0,"ks":{"a":{"a":0,"k":[350,350,0],"ix":1},"s":{"a":0,"k":[100,100,100],"ix":6},"sk":{"a":0,"k":0},"p":{"a":0,"k":[350,350,0],"ix":2},"r":{"a":0,"k":0,"ix":10},"sa":{"a":0,"k":0},"o":{"a":0,"k":100,"ix":11}},"ef":[],"w":700,"h":700,"refId":"comp_2","ind":2}]},{"nm":"","id":"comp_2","layers":[{"ty":0,"nm":"Shape Layer 1 Comp 1","sr":1,"st":0,"op":47.0000019143492,"ip":0,"hd":false,"ddd":0,"bm":0,"hasMask":false,"ao":0,"ks":{"a":{"a":0,"k":[350,350,0],"ix":1},"s":{"a":0,"k":[100,100,100],"ix":6},"sk":{"a":0,"k":0},"p":{"a":0,"k":[350,350,0],"ix":2},"r":{"a":0,"k":275,"ix":10},"sa":{"a":0,"k":0},"o":{"a":0,"k":100,"ix":11}},"ef":[],"w":700,"h":700,"refId":"comp_3","ind":1},{"ty":0,"nm":"Shape Layer 1 Comp 1","sr":1,"st":12.00000048877,"op":59.0000024031193,"ip":12.00000048877,"hd":false,"ddd":0,"bm":0,"hasMask":false,"ao":0,"ks":{"a":{"a":0,"k":[350,350,0],"ix":1},"s":{"a":0,"k":[100,100,100],"ix":6},"sk":{"a":0,"k":0},"p":{"a":0,"k":[350,350,0],"ix":2},"r":{"a":0,"k":227,"ix":10},"sa":{"a":0,"k":0},"o":{"a":0,"k":100,"ix":11}},"ef":[],"w":700,"h":700,"refId":"comp_3","ind":2},{"ty":0,"nm":"Shape Layer 1 Comp 1","sr":1,"st":5.00000020365417,"op":52.0000021180034,"ip":5.00000020365417,"hd":false,"ddd":0,"bm":0,"hasMask":false,"ao":0,"ks":{"a":{"a":0,"k":[350,350,0],"ix":1},"s":{"a":0,"k":[100,100,100],"ix":6},"sk":{"a":0,"k":0},"p":{"a":0,"k":[350,350,0],"ix":2},"r":{"a":0,"k":140,"ix":10},"sa":{"a":0,"k":0},"o":{"a":0,"k":100,"ix":11}},"ef":[],"w":700,"h":700,"refId":"comp_3","ind":3},{"ty":0,"nm":"Shape Layer 1 Comp 1","sr":1,"st":21.0000008553475,"op":68.0000027696968,"ip":21.0000008553475,"hd":false,"ddd":0,"bm":0,"hasMask":false,"ao":0,"ks":{"a":{"a":0,"k":[350,350,0],"ix":1},"s":{"a":0,"k":[100,100,100],"ix":6},"sk":{"a":0,"k":0},"p":{"a":0,"k":[350,350,0],"ix":2},"r":{"a":0,"k":48,"ix":10},"sa":{"a":0,"k":0},"o":{"a":0,"k":100,"ix":11}},"ef":[],"w":700,"h":700,"refId":"comp_3","ind":4},{"ty":0,"nm":"Shape Layer 1 Comp 1","sr":1,"st":14.0000005702317,"op":61.0000024845809,"ip":14.0000005702317,"hd":false,"ddd":0,"bm":0,"hasMask":false,"ao":0,"ks":{"a":{"a":0,"k":[350,350,0],"ix":1},"s":{"a":0,"k":[100,100,100],"ix":6},"sk":{"a":0,"k":0},"p":{"a":0,"k":[350,350,0],"ix":2},"r":{"a":0,"k":0,"ix":10},"sa":{"a":0,"k":0},"o":{"a":0,"k":100,"ix":11}},"ef":[],"w":700,"h":700,"refId":"comp_3","ind":5}]},{"nm":"","id":"comp_3","layers":[{"ty":4,"nm":"Shape Layer 1","sr":1,"st":0,"op":47.0000019143492,"ip":0,"hd":false,"ddd":0,"bm":0,"hasMask":false,"ao":0,"ks":{"a":{"a":0,"k":[-261,141,0],"ix":1},"s":{"a":1,"k":[{"o":{"x":0.167,"y":0.167},"i":{"x":0.833,"y":0.833},"s":[0,0,100],"t":0},{"s":[100,100,100],"t":48.0000019550801}],"ix":6},"sk":{"a":0,"k":0},"p":{"a":1,"k":[{"o":{"x":0.167,"y":0.167},"i":{"x":0.833,"y":0.833},"s":[349,350,0],"t":0,"ti":[19.167,-15.333,0],"to":[-19.167,15.333,0]},{"s":[234,442,0],"t":48.0000019550801}],"ix":2},"r":{"a":0,"k":0,"ix":10},"sa":{"a":0,"k":0},"o":{"a":1,"k":[{"o":{"x":0.167,"y":0.167},"i":{"x":0.833,"y":0.833},"s":[100],"t":26},{"s":[0],"t":46.0000018736184}],"ix":11}},"ef":[],"shapes":[{"ty":"gr","bm":0,"hd":false,"mn":"ADBE Vector Group","nm":"Ellipse 1","ix":1,"cix":2,"np":3,"it":[{"ty":"el","bm":0,"hd":false,"mn":"ADBE Vector Shape - Ellipse","nm":"Ellipse Path 1","d":1,"p":{"a":0,"k":[0,0],"ix":3},"s":{"a":0,"k":[12,12],"ix":2}},{"ty":"st","bm":0,"hd":false,"mn":"ADBE Vector Graphic - Stroke","nm":"Stroke 1","lc":1,"lj":1,"ml":4,"o":{"a":0,"k":0,"ix":4},"w":{"a":0,"k":3,"ix":5},"c":{"a":0,"k":[0.9529,0.9608,0.9686],"ix":3}},{"ty":"fl","bm":0,"hd":false,"mn":"ADBE Vector Graphic - Fill","nm":"Fill 1","c":{"a":0,"k":[1,0.9529,0.9725],"ix":4},"r":1,"o":{"a":0,"k":100,"ix":5}},{"ty":"tr","a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"sk":{"a":0,"k":0,"ix":4},"p":{"a":0,"k":[-261,141],"ix":2},"r":{"a":0,"k":0,"ix":6},"sa":{"a":0,"k":0,"ix":5},"o":{"a":0,"k":100,"ix":7}}]}],"ind":1}]},{"nm":"","id":"comp_4","layers":[{"ty":4,"nm":"Shape Layer 1","sr":1,"st":0,"op":600.000024438501,"ip":0,"hd":false,"ddd":0,"bm":0,"hasMask":false,"ao":0,"ks":{"a":{"a":0,"k":[0,0,0],"ix":1},"s":{"a":0,"k":[100,100,100],"ix":6},"sk":{"a":0,"k":0},"p":{"a":0,"k":[350,350,0],"ix":2},"r":{"a":1,"k":[{"o":{"x":0.167,"y":0.167},"i":{"x":0.833,"y":0.833},"s":[0],"t":0},{"s":[720],"t":231.000009408823}],"ix":10},"sa":{"a":0,"k":0},"o":{"a":0,"k":100,"ix":11}},"ef":[],"shapes":[{"ty":"gr","bm":0,"hd":false,"mn":"ADBE Vector Group","nm":"Ellipse 1","ix":1,"cix":2,"np":3,"it":[{"ty":"el","bm":0,"hd":false,"mn":"ADBE Vector Shape - Ellipse","nm":"Ellipse Path 1","d":1,"p":{"a":0,"k":[0,0],"ix":3},"s":{"a":0,"k":[400,400],"ix":2}},{"ty":"gs","bm":0,"hd":false,"mn":"ADBE Vector Graphic - G-Stroke","nm":"Gradient Stroke 1","e":{"a":0,"k":[-761.875,495.96],"ix":5},"g":{"p":3,"k":{"a":0,"k":[0,1,1,1,0.496,0.9882352941176471,0.39215686274509803,0.17647058823529413,0.993,1,0.35294117647058826,0.37254901960784315,0,0,0.496,1,0.993,1],"ix":8}},"t":2,"a":{"a":0,"k":0,"ix":7},"h":{"a":0,"k":0,"ix":6},"s":{"a":0,"k":[154.435,-126.834],"ix":4},"lc":1,"lj":1,"ml":4,"o":{"a":0,"k":100,"ix":9},"w":{"a":0,"k":1,"ix":10}},{"ty":"fl","bm":0,"hd":false,"mn":"ADBE Vector Graphic - Fill","nm":"Fill 1","c":{"a":0,"k":[1,0.9529,0.9725],"ix":4},"r":1,"o":{"a":0,"k":0,"ix":5}},{"ty":"tr","a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"sk":{"a":0,"k":0,"ix":4},"p":{"a":0,"k":[1,-1],"ix":2},"r":{"a":0,"k":0,"ix":6},"sa":{"a":0,"k":0,"ix":5},"o":{"a":0,"k":100,"ix":7}}]}],"ind":1},{"ty":4,"nm":"Shape Layer 10","sr":1,"st":0,"op":600.000024438501,"ip":0,"hd":false,"ddd":0,"bm":0,"hasMask":false,"ao":0,"ks":{"a":{"a":0,"k":[59.113,-37.893,0],"ix":1},"s":{"a":0,"k":[131.951,131.951,100],"ix":6},"sk":{"a":0,"k":0},"p":{"a":0,"k":[351,351,0],"ix":2},"r":{"a":1,"k":[{"o":{"x":0.167,"y":0.167},"i":{"x":0.833,"y":0.833},"s":[382],"t":0},{"s":[742],"t":231.000009408823}],"ix":10},"sa":{"a":0,"k":0},"o":{"a":0,"k":100,"ix":11}},"ef":[],"shapes":[{"ty":"gr","bm":0,"hd":false,"mn":"ADBE Vector Group","nm":"Shape 1","ix":1,"cix":2,"np":3,"it":[{"ty":"sh","bm":0,"hd":false,"mn":"ADBE Vector Shape - Group","nm":"Path 1","ix":1,"d":1,"ks":{"a":0,"k":{"c":true,"i":[[0,75.084],[-2.326,11.481],[8.484,-20.679],[0,-18.258],[-75.111,0],[-2.638,0.152]],"o":[[0,-12.43],[4.439,-21.907],[-6.526,15.906],[0,75.111],[2.677,0],[-56.982,-4.209]],"v":[[-30.208,-36.759],[-26.642,-72.724],[-61.861,-83.451],[-71.985,-31.87],[64.015,104.13],[71.985,103.892]]},"ix":2}},{"ty":"st","bm":0,"hd":false,"mn":"ADBE Vector Graphic - Stroke","nm":"Stroke 1","lc":1,"lj":1,"ml":4,"o":{"a":0,"k":0,"ix":4},"w":{"a":0,"k":3,"ix":5},"c":{"a":0,"k":[0.9529,0.9608,0.9686],"ix":3}},{"ty":"gf","bm":0,"hd":false,"mn":"ADBE Vector Graphic - G-Fill","nm":"Gradient Fill 1","e":{"a":0,"k":[100,0],"ix":6},"g":{"p":3,"k":{"a":0,"k":[0,1,0.9529411764705882,0.9725490196078431,0.487,1,0.9529411764705882,0.9725490196078431,0.998,0.5176470588235295,0,1,0,1,0.487,1,0.998,0],"ix":9}},"t":2,"a":{"a":0,"k":0,"ix":8},"h":{"a":0,"k":0,"ix":7},"s":{"a":0,"k":[0,0],"ix":5},"r":1,"o":{"a":0,"k":100,"ix":10}},{"ty":"tr","a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"sk":{"a":0,"k":0,"ix":4},"p":{"a":0,"k":[0,0],"ix":2},"r":{"a":0,"k":0,"ix":6},"sa":{"a":0,"k":0,"ix":5},"o":{"a":0,"k":100,"ix":7}}]}],"ind":2},{"ty":4,"nm":"Shape Layer 4","sr":1,"st":0,"op":600.000024438501,"ip":0,"hd":false,"ddd":0,"bm":0,"hasMask":false,"ao":0,"ks":{"a":{"a":0,"k":[-128,0,0],"ix":1},"s":{"a":0,"k":[65.625,100,100],"ix":6},"sk":{"a":0,"k":0},"p":{"a":0,"k":[222,350,0],"ix":2},"r":{"a":0,"k":0,"ix":10},"sa":{"a":0,"k":0},"o":{"a":0,"k":54,"ix":11}},"ef":[],"shapes":[{"ty":"gr","bm":0,"hd":false,"mn":"ADBE Vector Group","nm":"Ellipse 1","ix":1,"cix":2,"np":3,"it":[{"ty":"el","bm":0,"hd":false,"mn":"ADBE Vector Shape - Ellipse","nm":"Ellipse Path 1","d":1,"p":{"a":0,"k":[0,0],"ix":3},"s":{"a":0,"k":[16,16],"ix":2}},{"ty":"st","bm":0,"hd":false,"mn":"ADBE Vector Graphic - Stroke","nm":"Stroke 1","lc":1,"lj":1,"ml":4,"o":{"a":0,"k":0,"ix":4},"w":{"a":0,"k":3,"ix":5},"c":{"a":0,"k":[0.9529,0.9608,0.9686],"ix":3}},{"ty":"gf","bm":0,"hd":false,"mn":"ADBE Vector Graphic - G-Fill","nm":"Gradient Fill 1","e":{"a":0,"k":[100,0],"ix":6},"g":{"p":3,"k":{"a":0,"k":[0.002,1,1,1,0.488,1,1,1,0.998,1,1,1,0.002,1,0.488,0.5,0.998,0],"ix":9}},"t":2,"a":{"a":0,"k":0,"ix":8},"h":{"a":0,"k":0,"ix":7},"s":{"a":0,"k":[0,0],"ix":5},"r":1,"o":{"a":0,"k":100,"ix":10}},{"ty":"tr","a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"sk":{"a":0,"k":0,"ix":4},"p":{"a":0,"k":[-128,0],"ix":2},"r":{"a":0,"k":0,"ix":6},"sa":{"a":0,"k":0,"ix":5},"o":{"a":0,"k":100,"ix":7}}]}],"ind":3},{"ty":4,"nm":"Shape Layer 8","sr":1,"st":0,"op":600.000024438501,"ip":0,"hd":false,"ddd":0,"bm":0,"hasMask":false,"td":1,"ao":0,"ks":{"a":{"a":0,"k":[0,0,0],"ix":1},"s":{"a":0,"k":[100,100,100],"ix":6},"sk":{"a":0,"k":0},"p":{"a":0,"k":[350,350,0],"ix":2},"r":{"a":1,"k":[{"o":{"x":0.167,"y":0.167},"i":{"x":0.833,"y":0.833},"s":[0],"t":0},{"s":[360],"t":59.0000024031193}],"ix":10},"sa":{"a":0,"k":0},"o":{"a":0,"k":100,"ix":11}},"ef":[],"shapes":[{"ty":"gr","bm":0,"hd":false,"mn":"ADBE Vector Group","nm":"Ellipse 1","ix":1,"cix":2,"np":3,"it":[{"ty":"el","bm":0,"hd":false,"mn":"ADBE Vector Shape - Ellipse","nm":"Ellipse Path 1","d":1,"p":{"a":0,"k":[0,0],"ix":3},"s":{"a":0,"k":[400,400],"ix":2}},{"ty":"st","bm":0,"hd":false,"mn":"ADBE Vector Graphic - Stroke","nm":"Stroke 1","lc":1,"lj":1,"ml":4,"o":{"a":0,"k":0,"ix":4},"w":{"a":0,"k":3,"ix":5},"c":{"a":0,"k":[0.9529,0.9608,0.9686],"ix":3}},{"ty":"gf","bm":0,"hd":false,"mn":"ADBE Vector Graphic - G-Fill","nm":"Gradient Fill 1","e":{"a":0,"k":[202.396,-0.369],"ix":6},"g":{"p":3,"k":{"a":0,"k":[0,1,1,1,0.487,1,1,1,0.998,1,1,1,0,0.44,0.487,0.22,0.998,0],"ix":9}},"t":2,"a":{"a":0,"k":0,"ix":8},"h":{"a":0,"k":0,"ix":7},"s":{"a":0,"k":[0,0],"ix":5},"r":1,"o":{"a":0,"k":100,"ix":10}},{"ty":"tr","a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"sk":{"a":0,"k":0,"ix":4},"p":{"a":0,"k":[1,-1],"ix":2},"r":{"a":0,"k":0,"ix":6},"sa":{"a":0,"k":0,"ix":5},"o":{"a":0,"k":100,"ix":7}}]}],"ind":4},{"ty":4,"nm":"Shape Layer 7","sr":1,"st":0,"op":600.000024438501,"ip":0,"hd":false,"ddd":0,"bm":0,"tt":1,"hasMask":false,"ao":0,"ks":{"a":{"a":0,"k":[-118,46,0],"ix":1},"s":{"a":1,"k":[{"o":{"x":0.167,"y":0.167},"i":{"x":0.833,"y":0.833},"s":[33.333,100,100],"t":0},{"o":{"x":0.167,"y":0.167},"i":{"x":0.833,"y":0.833},"s":[100,100,100],"t":29},{"s":[33.333,100,100],"t":59.0000024031193}],"ix":6},"sk":{"a":0,"k":0},"p":{"a":1,"k":[{"o":{"x":0.167,"y":0.167},"i":{"x":0.833,"y":0.833},"s":[106,342,0],"t":0,"ti":[-81.667,0,0],"to":[81.667,0,0]},{"s":[596,342,0],"t":59.0000024031193}],"ix":2},"r":{"a":0,"k":38,"ix":10},"sa":{"a":0,"k":0},"o":{"a":0,"k":39,"ix":11}},"ef":[],"shapes":[{"ty":"gr","bm":0,"hd":false,"mn":"ADBE Vector Group","nm":"Rectangle 1","ix":1,"cix":2,"np":3,"it":[{"ty":"rc","bm":0,"hd":false,"mn":"ADBE Vector Shape - Rect","nm":"Rectangle Path 1","d":1,"p":{"a":0,"k":[0,0],"ix":3},"r":{"a":0,"k":0,"ix":4},"s":{"a":0,"k":[72,596],"ix":2}},{"ty":"st","bm":0,"hd":false,"mn":"ADBE Vector Graphic - Stroke","nm":"Stroke 1","lc":1,"lj":1,"ml":4,"o":{"a":0,"k":0,"ix":4},"w":{"a":0,"k":3,"ix":5},"c":{"a":0,"k":[0.9529,0.9608,0.9686],"ix":3}},{"ty":"gf","bm":0,"hd":false,"mn":"ADBE Vector Graphic - G-Fill","nm":"Gradient Fill 1","e":{"a":0,"k":[-2.662,190],"ix":6},"g":{"p":3,"k":{"a":0,"k":[0,1,1,1,0.487,1,1,1,0.998,1,1,1,0,0.44,0.487,0.22,0.998,0],"ix":9}},"t":2,"a":{"a":0,"k":0,"ix":8},"h":{"a":0,"k":0,"ix":7},"s":{"a":0,"k":[1.369,-19],"ix":5},"r":1,"o":{"a":0,"k":100,"ix":10}},{"ty":"tr","a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"sk":{"a":0,"k":0,"ix":4},"p":{"a":0,"k":[-118,46],"ix":2},"r":{"a":0,"k":0,"ix":6},"sa":{"a":0,"k":0,"ix":5},"o":{"a":0,"k":100,"ix":7}}]}],"ind":5},{"ty":4,"nm":"Shape Layer 6","sr":1,"st":0,"op":600.000024438501,"ip":0,"hd":false,"ddd":0,"bm":0,"hasMask":false,"ao":0,"ks":{"a":{"a":0,"k":[0,0,0],"ix":1},"s":{"a":0,"k":[146.132,146.132,100],"ix":6},"sk":{"a":0,"k":0},"p":{"a":0,"k":[350,350,0],"ix":2},"r":{"a":0,"k":0,"ix":10},"sa":{"a":0,"k":0},"o":{"a":0,"k":100,"ix":11}},"ef":[],"shapes":[{"ty":"gr","bm":0,"hd":false,"mn":"ADBE Vector Group","nm":"Ellipse 1","ix":1,"cix":2,"np":3,"it":[{"ty":"el","bm":0,"hd":false,"mn":"ADBE Vector Shape - Ellipse","nm":"Ellipse Path 1","d":1,"p":{"a":0,"k":[0,0],"ix":3},"s":{"a":0,"k":[142,142],"ix":2}},{"ty":"st","bm":0,"hd":false,"mn":"ADBE Vector Graphic - Stroke","nm":"Stroke 1","lc":1,"lj":1,"ml":4,"o":{"a":0,"k":0,"ix":4},"w":{"a":0,"k":3,"ix":5},"c":{"a":0,"k":[0.9529,0.9608,0.9686],"ix":3}},{"ty":"gf","bm":0,"hd":false,"mn":"ADBE Vector Graphic - G-Fill","nm":"Gradient Fill 1","e":{"a":0,"k":[60.839,0],"ix":6},"g":{"p":3,"k":{"a":0,"k":[0,0.8549019607843137,0.8549019607843137,0.8549019607843137,0.487,0.9254901960784314,0.9254901960784314,0.9254901960784314,0.998,1,1,1,0,0.44,0.487,0.22,0.998,0],"ix":9}},"t":2,"a":{"a":0,"k":0,"ix":8},"h":{"a":0,"k":0,"ix":7},"s":{"a":0,"k":[0,0],"ix":5},"r":1,"o":{"a":0,"k":100,"ix":10}},{"ty":"tr","a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[181.304,181.304],"ix":3},"sk":{"a":0,"k":0,"ix":4},"p":{"a":0,"k":[0,-3],"ix":2},"r":{"a":0,"k":0,"ix":6},"sa":{"a":0,"k":0,"ix":5},"o":{"a":0,"k":100,"ix":7}}]}],"ind":6},{"ty":4,"nm":"Shape Layer 5","sr":1,"st":0,"op":600.000024438501,"ip":0,"hd":false,"ddd":0,"bm":0,"hasMask":false,"ao":0,"ks":{"a":{"a":0,"k":[0,0,0],"ix":1},"s":{"a":0,"k":[124.841,124.841,100],"ix":6},"sk":{"a":0,"k":0},"p":{"a":0,"k":[350,458,0],"ix":2},"r":{"a":0,"k":180,"ix":10},"sa":{"a":0,"k":0},"o":{"a":0,"k":10,"ix":11}},"ef":[],"shapes":[{"ty":"gr","bm":0,"hd":false,"mn":"ADBE Vector Group","nm":"Shape 2","ix":1,"cix":2,"np":3,"it":[{"ty":"sh","bm":0,"hd":false,"mn":"ADBE Vector Shape - Group","nm":"Path 1","ix":1,"d":1,"ks":{"a":0,"k":{"c":true,"i":[[0,0],[-36.43,1.19],[0,0],[24.53,-18.36]],"o":[[25.85,-22.17],[0,0],[-32.74,1.05],[0,0]],"v":[[-100.64,-30.635],[-5.11,-67.775],[-5.11,37.125],[-92.55,67.775]]},"ix":2}},{"ty":"st","bm":0,"hd":false,"mn":"ADBE Vector Graphic - Stroke","nm":"Stroke 1","lc":1,"lj":1,"ml":4,"o":{"a":0,"k":0,"ix":4},"w":{"a":0,"k":3,"ix":5},"c":{"a":0,"k":[0.9529,0.9608,0.9686],"ix":3}},{"ty":"gf","bm":0,"hd":false,"mn":"ADBE Vector Graphic - G-Fill","nm":"Gradient Fill 1","e":{"a":0,"k":[0,105.643],"ix":6},"g":{"p":3,"k":{"a":0,"k":[0,1,1,1,0.487,1,1,1,0.998,1,1,1,0,1,0.487,0.5,0.998,0],"ix":9}},"t":1,"a":{"a":0,"k":0},"h":{"a":0,"k":0},"s":{"a":0,"k":[-1,-118.622],"ix":5},"r":1,"o":{"a":0,"k":100,"ix":10}},{"ty":"tr","a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"sk":{"a":0,"k":0,"ix":4},"p":{"a":0,"k":[0,0],"ix":2},"r":{"a":0,"k":0,"ix":6},"sa":{"a":0,"k":0,"ix":5},"o":{"a":0,"k":100,"ix":7}}]},{"ty":"gr","bm":0,"hd":false,"mn":"ADBE Vector Group","nm":"Shape 1","ix":2,"cix":2,"np":3,"it":[{"ty":"sh","bm":0,"hd":false,"mn":"ADBE Vector Shape - Group","nm":"Path 1","ix":1,"d":1,"ks":{"a":0,"k":{"c":true,"i":[[-25.85,-22.17],[0,0],[32.74,1.05],[0,0]],"o":[[0,0],[-24.53,-18.36],[0,0],[36.43,1.19]],"v":[[100.64,-30.635],[92.55,67.775],[5.11,37.125],[5.11,-67.775]]},"ix":2}},{"ty":"st","bm":0,"hd":false,"mn":"ADBE Vector Graphic - Stroke","nm":"Stroke 1","lc":1,"lj":1,"ml":4,"o":{"a":0,"k":0,"ix":4},"w":{"a":0,"k":3,"ix":5},"c":{"a":0,"k":[0.9529,0.9608,0.9686],"ix":3}},{"ty":"gf","bm":0,"hd":false,"mn":"ADBE Vector Graphic - G-Fill","nm":"Gradient Fill 1","e":{"a":0,"k":[0,105.643],"ix":6},"g":{"p":3,"k":{"a":0,"k":[0,1,1,1,0.487,1,1,1,0.998,1,1,1,0,1,0.487,0.5,0.998,0],"ix":9}},"t":1,"a":{"a":0,"k":0},"h":{"a":0,"k":0},"s":{"a":0,"k":[-1,-118.622],"ix":5},"r":1,"o":{"a":0,"k":100,"ix":10}},{"ty":"tr","a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"sk":{"a":0,"k":0,"ix":4},"p":{"a":0,"k":[0,0],"ix":2},"r":{"a":0,"k":0,"ix":6},"sa":{"a":0,"k":0,"ix":5},"o":{"a":0,"k":100,"ix":7}}]}],"ind":7},{"ty":4,"nm":"Shape Layer 3","sr":1,"st":0,"op":600.000024438501,"ip":0,"hd":false,"ddd":0,"bm":0,"hasMask":false,"ao":0,"ks":{"a":{"a":0,"k":[0,0,0],"ix":1},"s":{"a":0,"k":[100,100,100],"ix":6},"sk":{"a":0,"k":0},"p":{"a":0,"k":[343,317,0],"ix":2},"r":{"a":0,"k":0,"ix":10},"sa":{"a":0,"k":0},"o":{"a":0,"k":100,"ix":11}},"ef":[],"shapes":[{"ty":"gr","bm":0,"hd":false,"mn":"ADBE Vector Group","nm":"Ellipse 1","ix":1,"cix":2,"np":3,"it":[{"ty":"el","bm":0,"hd":false,"mn":"ADBE Vector Shape - Ellipse","nm":"Ellipse Path 1","d":1,"p":{"a":0,"k":[0,0],"ix":3},"s":{"a":0,"k":[177,116],"ix":2}},{"ty":"st","bm":0,"hd":false,"mn":"ADBE Vector Graphic - Stroke","nm":"Stroke 1","lc":1,"lj":1,"ml":4,"o":{"a":0,"k":0,"ix":4},"w":{"a":0,"k":3,"ix":5},"c":{"a":0,"k":[0.9529,0.9608,0.9686],"ix":3}},{"ty":"gf","bm":0,"hd":false,"mn":"ADBE Vector Graphic - G-Fill","nm":"Gradient Fill 1","e":{"a":0,"k":[100,0],"ix":6},"g":{"p":3,"k":{"a":0,"k":[0.002,0.9529411764705882,0.9529411764705882,0.9529411764705882,0.488,0.9764705882352941,0.9764705882352941,0.9764705882352941,0.998,1,1,1,0.002,1,0.488,0.5,0.998,0],"ix":9}},"t":2,"a":{"a":0,"k":0,"ix":8},"h":{"a":0,"k":0,"ix":7},"s":{"a":0,"k":[-10,-146],"ix":5},"r":1,"o":{"a":0,"k":100,"ix":10}},{"ty":"tr","a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"sk":{"a":0,"k":0,"ix":4},"p":{"a":0,"k":[7.5,-104],"ix":2},"r":{"a":0,"k":0,"ix":6},"sa":{"a":0,"k":0,"ix":5},"o":{"a":0,"k":100,"ix":7}}]}],"ind":8},{"ty":4,"nm":"Shape Layer 9","sr":1,"st":0,"op":600.000024438501,"ip":0,"hd":false,"ddd":0,"bm":0,"hasMask":false,"ao":0,"ks":{"a":{"a":0,"k":[0,0,0],"ix":1},"s":{"a":0,"k":[100,100,100],"ix":6},"sk":{"a":0,"k":0},"p":{"a":0,"k":[350,350,0],"ix":2},"r":{"a":1,"k":[{"o":{"x":0.167,"y":0.167},"i":{"x":0.833,"y":0.833},"s":[190],"t":0},{"s":[550],"t":230.000009368092}],"ix":10},"sa":{"a":0,"k":0},"o":{"a":0,"k":100,"ix":11}},"ef":[],"shapes":[{"ty":"gr","bm":0,"hd":false,"mn":"ADBE Vector Group","nm":"Ellipse 1","ix":1,"cix":2,"np":3,"it":[{"ty":"el","bm":0,"hd":false,"mn":"ADBE Vector Shape - Ellipse","nm":"Ellipse Path 1","d":1,"p":{"a":0,"k":[0,0],"ix":3},"s":{"a":0,"k":[400,400],"ix":2}},{"ty":"st","bm":0,"hd":false,"mn":"ADBE Vector Graphic - Stroke","nm":"Stroke 1","lc":1,"lj":1,"ml":4,"o":{"a":0,"k":0,"ix":4},"w":{"a":0,"k":3,"ix":5},"c":{"a":0,"k":[0.9529,0.9608,0.9686],"ix":3}},{"ty":"gf","bm":0,"hd":false,"mn":"ADBE Vector Graphic - G-Fill","nm":"Gradient Fill 1","e":{"a":0,"k":[29.602,-44.441],"ix":6},"g":{"p":3,"k":{"a":0,"k":[0.002,0.5450980392156862,0.9882352941176471,0.9725490196078431,0.488,0.5686274509803921,0.7294117647058823,0.7176470588235294,0.998,1,0,0,0.002,1,0.488,0.5,0.998,0],"ix":9}},"t":2,"a":{"a":0,"k":0,"ix":8},"h":{"a":0,"k":0,"ix":7},"s":{"a":0,"k":[-131.837,65.407],"ix":5},"r":1,"o":{"a":0,"k":100,"ix":10}},{"ty":"tr","a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"sk":{"a":0,"k":0,"ix":4},"p":{"a":0,"k":[1,-1],"ix":2},"r":{"a":0,"k":0,"ix":6},"sa":{"a":0,"k":0,"ix":5},"o":{"a":0,"k":100,"ix":7}}]}],"ind":9},{"ty":4,"nm":"Shape Layer 2","sr":1,"st":0,"op":600.000024438501,"ip":0,"hd":false,"ddd":0,"bm":0,"hasMask":false,"ao":0,"ks":{"a":{"a":0,"k":[0,0,0],"ix":1},"s":{"a":0,"k":[100,100,100],"ix":6},"sk":{"a":0,"k":0},"p":{"a":0,"k":[350,350,0],"ix":2},"r":{"a":1,"k":[{"o":{"x":0.167,"y":0.167},"i":{"x":0.833,"y":0.833},"s":[190],"t":0},{"s":[1270],"t":230.000009368092}],"ix":10},"sa":{"a":0,"k":0},"o":{"a":0,"k":100,"ix":11}},"ef":[],"shapes":[{"ty":"gr","bm":0,"hd":false,"mn":"ADBE Vector Group","nm":"Ellipse 1","ix":1,"cix":2,"np":3,"it":[{"ty":"el","bm":0,"hd":false,"mn":"ADBE Vector Shape - Ellipse","nm":"Ellipse Path 1","d":1,"p":{"a":0,"k":[0,0],"ix":3},"s":{"a":0,"k":[400,400],"ix":2}},{"ty":"st","bm":0,"hd":false,"mn":"ADBE Vector Graphic - Stroke","nm":"Stroke 1","lc":1,"lj":1,"ml":4,"o":{"a":0,"k":0,"ix":4},"w":{"a":0,"k":3,"ix":5},"c":{"a":0,"k":[0.9529,0.9608,0.9686],"ix":3}},{"ty":"gf","bm":0,"hd":false,"mn":"ADBE Vector Graphic - G-Fill","nm":"Gradient Fill 1","e":{"a":0,"k":[-48.365,-83.495],"ix":6},"g":{"p":3,"k":{"a":0,"k":[0.002,0.9882352941176471,0.7333333333333333,0.984313725490196,0.488,0.5254901960784314,0.9686274509803922,0.9294117647058824,0.998,1,0,0.8470588235294118,0.002,1,0.488,1,0.998,0],"ix":9}},"t":1,"a":{"a":0,"k":0},"h":{"a":0,"k":0},"s":{"a":0,"k":[-134.449,-317.964],"ix":5},"r":1,"o":{"a":0,"k":100,"ix":10}},{"ty":"tr","a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"sk":{"a":0,"k":0,"ix":4},"p":{"a":0,"k":[1,-1],"ix":2},"r":{"a":0,"k":0,"ix":6},"sa":{"a":0,"k":0,"ix":5},"o":{"a":0,"k":100,"ix":7}}]}],"ind":10}]}]}
\ No newline at end of file
diff --git a/ios/Runner.xcodeproj/project.pbxproj b/ios/Runner.xcodeproj/project.pbxproj
index cbcbc2de7..0a7a4df09 100644
--- a/ios/Runner.xcodeproj/project.pbxproj
+++ b/ios/Runner.xcodeproj/project.pbxproj
@@ -514,7 +514,7 @@
NEW_SETTING = "";
SDKROOT = iphoneos;
SUPPORTED_PLATFORMS = iphoneos;
- TARGETED_DEVICE_FAMILY = "1,2";
+ TARGETED_DEVICE_FAMILY = 1;
VALIDATE_PRODUCT = YES;
};
name = Profile;
@@ -539,8 +539,14 @@
MARKETING_VERSION = 1.0.1;
PRODUCT_BUNDLE_IDENTIFIER = com.saitong.voitrans;
PRODUCT_NAME = "$(TARGET_NAME)";
+ REGISTER_APP_GROUPS = NO;
+ SUPPORTED_PLATFORMS = "iphoneos iphonesimulator";
+ SUPPORTS_MACCATALYST = YES;
+ SUPPORTS_MAC_DESIGNED_FOR_IPHONE_IPAD = YES;
+ SUPPORTS_XR_DESIGNED_FOR_IPHONE_IPAD = YES;
SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h";
SWIFT_VERSION = 5.0;
+ TARGETED_DEVICE_FAMILY = "1,2";
VERSIONING_SYSTEM = "apple-generic";
};
name = Profile;
@@ -572,6 +578,7 @@
BUNDLE_LOADER = "$(TEST_HOST)";
CODE_SIGN_STYLE = Automatic;
CURRENT_PROJECT_VERSION = 1;
+ DEVELOPMENT_TEAM = K39FB49XKY;
GENERATE_INFOPLIST_FILE = YES;
IPHONEOS_DEPLOYMENT_TARGET = 18.0;
MARKETING_VERSION = 1.0;
@@ -589,6 +596,7 @@
BUNDLE_LOADER = "$(TEST_HOST)";
CODE_SIGN_STYLE = Automatic;
CURRENT_PROJECT_VERSION = 1;
+ DEVELOPMENT_TEAM = K39FB49XKY;
GENERATE_INFOPLIST_FILE = YES;
IPHONEOS_DEPLOYMENT_TARGET = 18.0;
MARKETING_VERSION = 1.0;
@@ -654,7 +662,7 @@
NEW_SETTING = "";
ONLY_ACTIVE_ARCH = YES;
SDKROOT = iphoneos;
- TARGETED_DEVICE_FAMILY = "1,2";
+ TARGETED_DEVICE_FAMILY = 1;
};
name = Debug;
};
@@ -709,7 +717,7 @@
SUPPORTED_PLATFORMS = iphoneos;
SWIFT_COMPILATION_MODE = wholemodule;
SWIFT_OPTIMIZATION_LEVEL = "-O";
- TARGETED_DEVICE_FAMILY = "1,2";
+ TARGETED_DEVICE_FAMILY = 1;
VALIDATE_PRODUCT = YES;
};
name = Release;
@@ -734,9 +742,15 @@
MARKETING_VERSION = 1.0.1;
PRODUCT_BUNDLE_IDENTIFIER = com.saitong.voitrans;
PRODUCT_NAME = "$(TARGET_NAME)";
+ REGISTER_APP_GROUPS = NO;
+ SUPPORTED_PLATFORMS = "iphoneos iphonesimulator";
+ SUPPORTS_MACCATALYST = YES;
+ SUPPORTS_MAC_DESIGNED_FOR_IPHONE_IPAD = YES;
+ SUPPORTS_XR_DESIGNED_FOR_IPHONE_IPAD = YES;
SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h";
SWIFT_OPTIMIZATION_LEVEL = "-Onone";
SWIFT_VERSION = 5.0;
+ TARGETED_DEVICE_FAMILY = "1,2";
VERSIONING_SYSTEM = "apple-generic";
};
name = Debug;
@@ -761,8 +775,14 @@
MARKETING_VERSION = 1.0.1;
PRODUCT_BUNDLE_IDENTIFIER = com.saitong.voitrans;
PRODUCT_NAME = "$(TARGET_NAME)";
+ REGISTER_APP_GROUPS = NO;
+ SUPPORTED_PLATFORMS = "iphoneos iphonesimulator";
+ SUPPORTS_MACCATALYST = YES;
+ SUPPORTS_MAC_DESIGNED_FOR_IPHONE_IPAD = YES;
+ SUPPORTS_XR_DESIGNED_FOR_IPHONE_IPAD = YES;
SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h";
SWIFT_VERSION = 5.0;
+ TARGETED_DEVICE_FAMILY = "1,2";
VERSIONING_SYSTEM = "apple-generic";
};
name = Release;
diff --git a/ios/Runner/Info.plist b/ios/Runner/Info.plist
index 3d78b7eda..75a82d84d 100644
--- a/ios/Runner/Info.plist
+++ b/ios/Runner/Info.plist
@@ -57,14 +57,10 @@
FacebookClientToken
193d0a2b1757c41cfa557ea8e8f21c33
-
- SKAdNetworkItems
-
-
- NSAdvertisingAttributionReportEndpoint
-
LSApplicationQueriesSchemes
+ App-Prefs
+ prefs
weixin
weixinULAPI
weixinURLParamsAPI
@@ -86,6 +82,8 @@
LSRequiresIPhoneOS
+ NSAdvertisingAttributionReportEndpoint
+
NSAppTransportSecurity
NSAllowsArbitraryLoads
@@ -94,23 +92,27 @@
NSBluetoothAlwaysUsageDescription
- 需要蓝牙权限用于连接和管理音频设备
+ Bluetooth permission is required to connect and manage audio devices
NSBluetoothPeripheralUsageDescription
- 需要蓝牙权限用于连接和管理音频设备
+ Bluetooth permission is required to connect and manage audio devices
NSCalendarsUsageDescription
- 需要日历权限用于安排和管理您的音频会议和录音任务
+ Calendar permission is required to schedule and manage your audio meetings and recording tasks
NSCameraUsageDescription
- 需要相机权限用于扫描二维码和视频通话功能
+ Camera permission is required for QR code scanning and video calling functions
NSLocationAlwaysAndWhenInUseUsageDescription
- 语音助手需要位置权限来为您提供基于位置的信息和服务
+ Location access enables the app to provide location-based voice translation services, such as translating local place names and providing contextual information relevant to your current location during conversations.
NSLocationWhenInUseUsageDescription
- 语音助手需要位置权限来为您提供基于位置的信息和服务
+ Location access enables the app to provide location-based voice translation services, such as translating local place names and providing contextual information relevant to your current location during conversations.
NSMicrophoneUsageDescription
- 需要麦克风权限用于语音识别和录音功能
+ Our app's voice AI interaction features (such as voice wake-up for AI, issuing commands, and real-time conversations) require microphone access. With this permission, you can interact with AI directly through speech, improving operational efficiency. We strictly protect your voice data—only used for real-time processing of interaction requests, and raw audio will not be stored.
NSMotionUsageDescription
- 我们需要访问您的运动数据以提供更精准的导航服务
+ This app uses motion and fitness data to detect when you're walking or moving, which helps optimize voice recognition accuracy and automatically adjust microphone sensitivity for better audio recording quality during different activities.
NSPhotoLibraryUsageDescription
- 需要相册权限用于保存录音文件和导入音频文件
+ Photo library permission is required to save recording files and import audio files
+ NSUserTrackingUsageDescription
+ This identifier will be used to deliver personalized ads to you.
+ SKAdNetworkItems
+
UIApplicationSupportsIndirectInputEvents
UIBackgroundModes
@@ -119,17 +121,15 @@
audio
location
+ UIDeviceFamily
+
+ 1
+
UILaunchStoryboardName
LaunchScreen
UIMainStoryboardFile
Main
UISupportedInterfaceOrientations
-
- UIInterfaceOrientationPortrait
- UIInterfaceOrientationLandscapeLeft
- UIInterfaceOrientationLandscapeRight
-
- UISupportedInterfaceOrientations~ipad
UIInterfaceOrientationPortrait
UIInterfaceOrientationPortraitUpsideDown
diff --git a/ios/Runner/Runner.entitlements b/ios/Runner/Runner.entitlements
index ed86a9636..f05d7901a 100644
--- a/ios/Runner/Runner.entitlements
+++ b/ios/Runner/Runner.entitlements
@@ -2,6 +2,10 @@
+ com.apple.developer.applesignin
+
+ Default
+
com.apple.developer.associated-domains
applinks:voitrans.net
@@ -10,9 +14,21 @@
com.apple.external-accessory.wireless-configuration
- com.apple.developer.applesignin
-
- Default
-
+ com.apple.security.app-sandbox
+
+ com.apple.security.device.audio-input
+
+ com.apple.security.device.bluetooth
+
+ com.apple.security.device.camera
+
+ com.apple.security.network.client
+
+ com.apple.security.personal-information.calendars
+
+ com.apple.security.personal-information.location
+
+ com.apple.security.personal-information.photos-library
+
diff --git a/lib/core/bindings/initial_binding.dart b/lib/core/bindings/initial_binding.dart
index 14d6700d5..831da3344 100644
--- a/lib/core/bindings/initial_binding.dart
+++ b/lib/core/bindings/initial_binding.dart
@@ -8,6 +8,7 @@ import '../../../data/services/speech_impl/azure_ast_service.dart';
import 'package:get/get.dart';
import '../../data/services/meeting/meeting_task_service.dart';
import '../../data/services/meeting/meeting_upload_service.dart';
+import '../../data/services/novice_guide_service.dart';
import '../../data/services/speech_factory.dart';
import '../../data/services/volcano_translation_service.dart';
import '../../data/services/volcano_ai_service.dart';
@@ -68,6 +69,9 @@ class InitialBinding extends Bindings {
// 会议助手音频上传
Get.lazyPut(() => UserportraitManager(), fenix: true);
+ // 新手引导
+ Get.lazyPut(() => NoviceGuideService(), fenix: true);
+
// 杰理蓝牙服务
// // 语言管理器(需要最先初始化)
diff --git a/lib/core/translations/language/en_us.dart b/lib/core/translations/language/en_us.dart
index bf95c258d..7dca392ef 100644
--- a/lib/core/translations/language/en_us.dart
+++ b/lib/core/translations/language/en_us.dart
@@ -436,6 +436,8 @@ const Map enUS = {
'deleteAccount': 'Delete Account',
'deleteAccountTips':
'Are you sure you want to permanently delete your account? This action cannot be undone.',
+ 'clean': 'Clear Cache', // 清除缓存
+ 'cleanTips': 'Are you sure you want to clear personal cache?', // 确定要清除个人缓存吗?
//Profile 错误信息
'networkConnectionFailed':
@@ -742,7 +744,7 @@ const Map enUS = {
// 配对模块
// 配对页面
"devicePairing": "Device Pairing",
- "connectYourDeepSoundHeadset": "Connect Your DeepSound Headset",
+ "connectYourDeepSoundHeadset": "Connect Your Voitrans Headset",
"bind": "Bind",
"authorizeBind": "Authorize Binding",
"enterCode": "Please enter code",
@@ -813,6 +815,19 @@ const Map enUS = {
'deviceAuthorizationFailedCheckCode':
'Device authorization failed, please check authorization code',
+ ///iOS蓝牙引导窗
+ 'bluetoothGuideTitle': 'Manual Bluetooth Activation Required', // 需要手动开启蓝牙
+ 'bluetoothGuideSteps': 'Please follow these steps:', // 请按照以下步骤操作:
+ 'bluetoothGuideStep1':
+ 'Enable Bluetooth: Settings → Bluetooth → Turn On', // 开启蓝牙:设置 → 蓝牙 → 开启
+ 'bluetoothGuideStep2':
+ 'Connect Device: Find your device in "My Devices" and tap to connect', // 连接设备:在"我的设备"中找到您的设备并点击连接
+ 'bluetoothGuideStep3':
+ 'Return to app to continue pairing process', // 返回应用继续配对流程
+ 'bluetoothGuideTip':
+ 'Tip: You can also long press the Bluetooth icon in Control Center to quickly access Bluetooth settings', // 提示:也可以从控制中心长按蓝牙图标快速进入蓝牙设置
+ 'iKnow': 'I Know', // 我知道了
+
// 用户反馈页
"feedback": "Feedback",
'feedbackInfo': 'Provide feedback or suggestions',
diff --git a/lib/core/translations/language/ru_ru.dart b/lib/core/translations/language/ru_ru.dart
index eb613e7ae..c96599c05 100644
--- a/lib/core/translations/language/ru_ru.dart
+++ b/lib/core/translations/language/ru_ru.dart
@@ -702,7 +702,7 @@ const Map ruRU = {
// 配对模块
// 配对页面 (pairing_view.dart & pairing_view_new.dart)
"devicePairing": "Сопряжение устройства",
- "connectYourDeepSoundHeadset": "Подключите ваши наушники DeepSound",
+ "connectYourDeepSoundHeadset": "Подключите ваши наушники Voitrans",
"bind": "Привязать",
"authorizeBind": "Авторизовать привязку",
"enterCode": "Пожалуйста, введите код",
diff --git a/lib/core/translations/language/th_th.dart b/lib/core/translations/language/th_th.dart
index e6604304c..f037318e9 100644
--- a/lib/core/translations/language/th_th.dart
+++ b/lib/core/translations/language/th_th.dart
@@ -736,7 +736,7 @@ const Map thTH = {
'retryAuthorization': 'ลองอนุญาตอีกครั้ง',
'pleaseGrantNecessaryPermissions': 'กรุณาให้สิทธิ์ที่จำเป็น',
'checkFailedRetry': 'การตรวจสอบล้มเหลว โปรดลองอีกครั้ง',
- 'pleaseConnectDeepSoundDevice': 'กรุณาเชื่อมต่ออุปกรณ์ DeepSound',
+ 'pleaseConnectDeepSoundDevice': 'กรุณาเชื่อมต่ออุปกรณ์ Voitrans',
'verificationFailedRetry': 'การตรวจสอบล้มเหลว โปรดลองอีกครั้ง',
'invalidDeviceAddress': 'ที่อยู่อุปกรณ์ไม่ถูกต้อง',
'bleDeviceAssociationFailed': 'การเชื่อมโยงอุปกรณ์ BLE ล้มเหลว',
diff --git a/lib/core/translations/language/tr_tr.dart b/lib/core/translations/language/tr_tr.dart
index 44ba8ec2c..b2d537d51 100644
--- a/lib/core/translations/language/tr_tr.dart
+++ b/lib/core/translations/language/tr_tr.dart
@@ -695,7 +695,7 @@ const Map trTR = {
// 配对模块
// 配对页面 (pairing_view.dart & pairing_view_new.dart)
"devicePairing": "Cihaz Eşleştirme",
- "connectYourDeepSoundHeadset": "DeepSound Kulaklığınızı Bağlayın",
+ "connectYourDeepSoundHeadset": "Voitrans Kulaklığınızı Bağlayın",
"bind": "Bağla",
"authorizeBind": "Bağlamayı Yetkilendir",
"enterCode": "Lütfen kodu girin",
diff --git a/lib/core/translations/language/uk_ua.dart b/lib/core/translations/language/uk_ua.dart
index 65e2ac2fd..95ea53215 100644
--- a/lib/core/translations/language/uk_ua.dart
+++ b/lib/core/translations/language/uk_ua.dart
@@ -702,7 +702,7 @@ const Map ukUA = {
// 配对模块
// 配对页面 (pairing_view.dart & pairing_view_new.dart)
"devicePairing": "Спарювання пристрою",
- "connectYourDeepSoundHeadset": "Підключіть ваші навушники DeepSound",
+ "connectYourDeepSoundHeadset": "Підключіть ваші навушники Voitrans",
"bind": "Прив'язати",
"authorizeBind": "Авторизувати прив'язку",
"enterCode": "Будь ласка, введіть код",
@@ -757,7 +757,7 @@ const Map ukUA = {
'retryAuthorization': 'Повторити авторизацію',
'pleaseGrantNecessaryPermissions': 'Будь ласка, надайте необхідні дозволи',
'checkFailedRetry': 'Перевірка не вдалася, спробуйте ще раз',
- 'pleaseConnectDeepSoundDevice': 'Будь ласка, підключіть пристрій DeepSound',
+ 'pleaseConnectDeepSoundDevice': 'Будь ласка, підключіть пристрій Voitrans',
'verificationFailedRetry': 'Перевірка не вдалася, спробуйте ще раз',
'invalidDeviceAddress': 'Недійсна адреса пристрою',
'bleDeviceAssociationFailed': 'Не вдалося асоціювати пристрій BLE',
diff --git a/lib/core/translations/language/zh_cn.dart b/lib/core/translations/language/zh_cn.dart
index da9628b86..d73546416 100644
--- a/lib/core/translations/language/zh_cn.dart
+++ b/lib/core/translations/language/zh_cn.dart
@@ -411,6 +411,8 @@ const Map zhCN = {
"logoutTips": "确定要退出当前账号吗?",
'deleteAccount': '注销账号',
'deleteAccountTips': '确定要永久删除您的账号吗?此操作不可撤销',
+ 'clean': '清除缓存',
+ 'cleanTips': '确定要清除个人缓存吗?',
// Profile 错误信息
'networkConnectionFailed': '网络连接失败,请检查网络设置后重试',
@@ -684,7 +686,7 @@ const Map zhCN = {
// 配对模块
// 配对页面 (pairing_view.dart & pairing_view_new.dart)
"devicePairing": "设备配对",
- "connectYourDeepSoundHeadset": "连接您的 DeepSound 耳机",
+ "connectYourDeepSoundHeadset": "连接您的 Voitrans 耳机",
"bind": "绑定",
"authorizeBind": "授权绑定",
"enterCode": "请输入代码",
@@ -736,7 +738,7 @@ const Map zhCN = {
'retryAuthorization': '重新授权',
'pleaseGrantNecessaryPermissions': '请授予必要权限',
'checkFailedRetry': '检查失败,请重试',
- 'pleaseConnectDeepSoundDevice': '请连接 DeepSound 设备',
+ 'pleaseConnectDeepSoundDevice': '请连接 Voitrans 设备',
'verificationFailedRetry': '验证失败,请重试',
'invalidDeviceAddress': '设备地址无效',
'bleDeviceAssociationFailed': 'BLE设备关联失败',
@@ -746,6 +748,15 @@ const Map zhCN = {
'bleConnectionFailed': 'BLE连接失败',
'deviceAuthorizationFailedCheckCode': '设备授权失败,请检查授权码',
+ ///iOS蓝牙引导窗
+ 'bluetoothGuideTitle': '需要手动开启蓝牙', // 需要手动开启蓝牙
+ 'bluetoothGuideSteps': '请按照以下步骤操作:', // 请按照以下步骤操作:
+ 'bluetoothGuideStep1': '开启蓝牙:设置 → 蓝牙 → 开启', // 开启蓝牙:设置 → 蓝牙 → 开启
+ 'bluetoothGuideStep2': '连接设备:在"我的设备"中找到您的设备并点击连接', // 连接设备:在"我的设备"中找到您的设备并点击连接
+ 'bluetoothGuideStep3': '返回应用继续配对流程', // 返回应用继续配对流程
+ 'bluetoothGuideTip': '提示:也可以从控制中心长按蓝牙图标快速进入蓝牙设置', // 提示:也可以从控制中心长按蓝牙图标快速进入蓝牙设置
+ 'iKnow': '我知道了', // 我知道了
+
//用户反馈页
"feedback": "用户反馈",
'feedbackInfo': '提供反馈或建议',
diff --git a/lib/core/translations/language/zh_hk.dart b/lib/core/translations/language/zh_hk.dart
index af7a90433..aa83a2427 100644
--- a/lib/core/translations/language/zh_hk.dart
+++ b/lib/core/translations/language/zh_hk.dart
@@ -648,7 +648,7 @@ const Map zhHK = {
// 配對模塊
// 配對頁面 (pairing_view.dart & pairing_view_new.dart)
"devicePairing": "設備配對",
- "connectYourDeepSoundHeadset": "連接您的 DeepSound 耳機",
+ "connectYourDeepSoundHeadset": "連接您的 Voitrans 耳機",
"bind": "綁定",
"authorizeBind": "授權綁定",
"enterCode": "請輸入代碼",
@@ -695,7 +695,7 @@ const Map zhHK = {
'retryAuthorization': '重新授權',
'pleaseGrantNecessaryPermissions': '請授予必要權限',
'checkFailedRetry': '檢查失敗,請重試',
- 'pleaseConnectDeepSoundDevice': '請連接 DeepSound 設備',
+ 'pleaseConnectDeepSoundDevice': '請連接 Voitrans 設備',
'verificationFailedRetry': '驗證失敗,請重試',
'invalidDeviceAddress': '設備地址無效',
'bleDeviceAssociationFailed': 'BLE設備關聯失敗',
diff --git a/lib/core/widgets/intro_overlay_builder.dart b/lib/core/widgets/intro_overlay_builder.dart
new file mode 100644
index 000000000..bab8412b0
--- /dev/null
+++ b/lib/core/widgets/intro_overlay_builder.dart
@@ -0,0 +1,168 @@
+import 'package:flutter/material.dart';
+import 'package:flutter_intro/flutter_intro.dart';
+import 'package:flutter_screenutil/flutter_screenutil.dart';
+import 'package:lottie/lottie.dart';
+
+class IntroOverlayBuilder {
+ static Widget homeBuilder(
+ StepWidgetParams params,
+ bool isWakeup, {
+ Function? onNext,
+ }) {
+ return Column(
+ children: [
+ isWakeup
+ ? _AnimatedLottieWidget()
+ : SizedBox(
+ width: 200.w,
+ height: 200.w,
+ child: Lottie.asset('assets/lottie/intro_foriday.json'),
+ ),
+ Padding(
+ padding: EdgeInsets.only(bottom: 20.w),
+ child: Text(
+ isWakeup ? '我在呢~' : '对着麦克风说:“小言小言”',
+ style: TextStyle(
+ fontSize: 15.sp,
+ color: Colors.white,
+ fontWeight: FontWeight.bold,
+ ),
+ ),
+ ),
+ if (onNext != null)
+ _button(
+ '下一步',
+ () {
+ params.onNext!();
+ onNext();
+ },
+ ),
+ ],
+ );
+ }
+
+ static Widget aiBuilder(
+ StepWidgetParams params,
+ String text, {
+ String buttonText = '下一步',
+ bool alignment = true,
+ Function? onNext,
+ Function? onFinish,
+ }) {
+ return Column(
+ crossAxisAlignment:
+ alignment ? CrossAxisAlignment.start : CrossAxisAlignment.end,
+ children: [
+ Padding(
+ padding: EdgeInsets.only(bottom: 10.w),
+ child: Text(
+ text,
+ style: TextStyle(
+ fontSize: 15.sp,
+ color: Colors.white,
+ fontWeight: FontWeight.bold,
+ ),
+ ),
+ ),
+ Row(
+ children: [
+ if (onNext != null)
+ _button(
+ buttonText,
+ () {
+ params.onNext!();
+ onNext();
+ },
+ ),
+ if (onFinish != null)
+ _button(
+ buttonText,
+ () {
+ params.onFinish();
+ onFinish();
+ },
+ ),
+ ],
+ ),
+ ],
+ );
+ }
+
+ static Widget _button(String text, Function() onTap) {
+ return GestureDetector(
+ onTap: onTap,
+ child: Container(
+ padding: EdgeInsets.symmetric(horizontal: 10.w, vertical: 5.w),
+ decoration: BoxDecoration(
+ borderRadius: BorderRadius.circular(20.r),
+ border: Border.all(color: Colors.white),
+ ),
+ child: Text(
+ text,
+ style: TextStyle(
+ fontSize: 14.sp,
+ color: Colors.white,
+ ),
+ ),
+ ),
+ );
+ }
+}
+
+// 新增的动画Widget
+class _AnimatedLottieWidget extends StatefulWidget {
+ @override
+ _AnimatedLottieWidgetState createState() => _AnimatedLottieWidgetState();
+}
+
+class _AnimatedLottieWidgetState extends State<_AnimatedLottieWidget>
+ with SingleTickerProviderStateMixin {
+ late AnimationController _animationController;
+ late Animation _scaleAnimation;
+
+ @override
+ void initState() {
+ super.initState();
+
+ // 创建动画控制器,持续时间1.5秒
+ _animationController = AnimationController(
+ duration: const Duration(milliseconds: 1000),
+ vsync: this,
+ );
+
+ // 创建缩放动画,从0.8到1.2再回到1.0
+ _scaleAnimation = Tween(
+ begin: 0.8,
+ end: 1.2,
+ ).animate(CurvedAnimation(
+ parent: _animationController,
+ curve: Curves.elasticInOut,
+ ));
+
+ // 启动动画并设置为循环
+ _animationController.repeat(reverse: true);
+ }
+
+ @override
+ void dispose() {
+ _animationController.dispose();
+ super.dispose();
+ }
+
+ @override
+ Widget build(BuildContext context) {
+ return AnimatedBuilder(
+ animation: _scaleAnimation,
+ builder: (context, child) {
+ return Transform.scale(
+ scale: _scaleAnimation.value,
+ child: SizedBox(
+ width: 200.w,
+ height: 200.w,
+ child: Lottie.asset('assets/lottie/intro_foriday.json'),
+ ),
+ );
+ },
+ );
+ }
+}
diff --git a/lib/core/widgets/select_location_map.dart b/lib/core/widgets/select_location_map.dart
deleted file mode 100644
index c783901ed..000000000
--- a/lib/core/widgets/select_location_map.dart
+++ /dev/null
@@ -1,15 +0,0 @@
-import 'package:flutter/material.dart';
-
-class SelectLocationMap extends StatefulWidget {
- const SelectLocationMap({super.key});
-
- @override
- State createState() => _SelectLocationMapState();
-}
-
-class _SelectLocationMapState extends State {
- @override
- Widget build(BuildContext context) {
- return const Placeholder();
- }
-}
diff --git a/lib/data/models/appconfig.dart b/lib/data/models/appconfig.dart
index 19dcf0565..22a09d84a 100644
--- a/lib/data/models/appconfig.dart
+++ b/lib/data/models/appconfig.dart
@@ -4,9 +4,20 @@ import '../../../data/models/appconfig_model.dart';
class AppConfig {
static late UserGetAppConfigResp config;
+
+ // 新增一个私有的静态布尔值来跟踪初始化状态
+ static bool _isInitialized = false;
+
+ /// 检查AppConfig单例是否已经被初始化
+ /// 外部通过调用此方法来安全地检查状态
+ static bool isInitialized() {
+ return _isInitialized;
+ }
+
// 可选:提供初始化方法
static void initialize(UserGetAppConfigResp resp) {
config = resp;
+ _isInitialized = true; // <-- 新增此行
}
//环境变量
diff --git a/lib/data/models/channel_app_model.dart b/lib/data/models/channel_app_model.dart
index 7fc3dcabf..dac792f28 100644
--- a/lib/data/models/channel_app_model.dart
+++ b/lib/data/models/channel_app_model.dart
@@ -177,6 +177,30 @@ enum UserChannel {
);
}
+ // 从字符串名称创建枚举
+ static UserChannel fromName(String name) {
+ switch (name.toUpperCase()) {
+ case 'CHINA_HUAWEI':
+ return UserChannel.CHINA_HUAWEI;
+ case 'CHINA_XIAOMI':
+ return UserChannel.CHINA_XIAOMI;
+ case 'CHINA_OPPO':
+ return UserChannel.CHINA_OPPO;
+ case 'CHINA_VIVO':
+ return UserChannel.CHINA_VIVO;
+ case 'CHINA_HONOR':
+ return UserChannel.CHINA_HONOR;
+ case 'OFFICIAL':
+ return UserChannel.OFFICIAL;
+ case 'GLOBAL_APPLE':
+ return UserChannel.GLOBAL_APPLE;
+ case 'GLOBAL_GOOGLE':
+ return UserChannel.GLOBAL_GOOGLE;
+ default:
+ return UserChannel.UNKNOWN;
+ }
+ }
+
// JSON 序列化时返回数字值
int toJson() => value;
diff --git a/lib/data/models/message_model.dart b/lib/data/models/message_model.dart
index aef99cbc9..09744a939 100644
--- a/lib/data/models/message_model.dart
+++ b/lib/data/models/message_model.dart
@@ -1,3 +1,7 @@
+import 'dart:io';
+
+import 'package:path_provider/path_provider.dart';
+
class Message {
final bool isUser; //标识信息是否来自用户还是AI
final String text; //信息文本内容
diff --git a/lib/data/models/user_Info.dart b/lib/data/models/user_Info.dart
index e5efa62ec..d5d369ba6 100644
--- a/lib/data/models/user_Info.dart
+++ b/lib/data/models/user_Info.dart
@@ -1,9 +1,15 @@
-import 'dart:ffi';
+import 'package:get_storage/get_storage.dart';
+import '../../../core/utils/logger.dart';
class User {
// 单例实例
static User? _instance;
+ /// 安全地检查用户是否已经登录(即单例是否已初始化)
+ static bool isLoggedIn() {
+ return _instance != null;
+ }
+
// 单例访问器(增加空安全保护)
static User get instance {
if (_instance == null) {
@@ -12,6 +18,18 @@ class User {
return _instance!;
}
+ // --- 新增 ---
+ /// 清理所有与用户会话相关的数据
+ static Future clearUserSession() async {
+ final GetStorage storage = GetStorage();
+ // 1. 清理本地存储
+ await storage.remove('logintoken');
+ await storage.remove('user_info');
+ // 2. 清理内存中的单例
+ _instance = null;
+ Logger.i("User", "用户会话数据已安全清理。");
+ }
+
// 最终属性(不可变)
String name;
String uid;
@@ -72,7 +90,7 @@ class User {
// 修复1:安全获取嵌套的 user 字段
final userData = json['user'] as Map?; // 允许空值
final devicesData = json['devices'] as List?; // 允许空值
- print("数据获取fronTokenJson${userData} ${devicesData}");
+ print("数据获取fronTokenJson$userData $devicesData");
// 更新单例实例
return _instance = User._(
token: '',
diff --git a/lib/data/services/ble_manager.dart b/lib/data/services/ble_manager.dart
index ae0bf3ea5..f181dcc77 100644
--- a/lib/data/services/ble_manager.dart
+++ b/lib/data/services/ble_manager.dart
@@ -9,6 +9,7 @@ import 'package:get_storage/get_storage.dart';
import 'dart:convert';
import 'package:agent_service/agent_service.dart';
import 'bluetooth_manager.dart';
+import 'language_manager.dart';
// 重新导出BLE连接状态常量,让外部可以通过BleManager访问
export 'package:ble_service/ble_service.dart' show BleConnectionState;
@@ -556,26 +557,44 @@ class BleManager extends GetxService {
// 根据APP语言动态配置语音识别语言
List supportedLanguages = [];
try {
- // 使用 GetX 获取 APP 当前语言环境 (Locale)
- final appLocale = Get.locale;
-
- if (appLocale != null) {
- final lang = appLocale.languageCode; // e.g., 'en'
- final country = appLocale.countryCode; // e.g., 'US'
-
- // 构造 Azure SDK 需要的格式 (e.g., 'en-US')
- if (country != null && country.isNotEmpty) {
- supportedLanguages.add('$lang-$country');
- } else {
- supportedLanguages.add(lang); // fallback only language code
+ final storage = GetStorage();
+ final languageManager = Get.find();
+
+ // 从存储中读取用户设置的语言
+ String? savedLanguage = storage.read('language');
+
+ if (savedLanguage != null && savedLanguage != 'system') {
+ // 用户设置了特定语言
+ final parts = savedLanguage.split('_');
+ if (parts.length == 2) {
+ final asrCode = languageManager.getAsrCodeByShortCode(parts[0]);
+ if (asrCode != null) {
+ supportedLanguages.add(asrCode);
+ } else {
+ // 直接使用语言-国家格式
+ supportedLanguages.add('${parts[0]}-${parts[1]}');
+ }
+ }
+ } else {
+ // 跟随系统语言或未设置
+ final appLocale = Get.locale;
+ if (appLocale != null) {
+ final lang = appLocale.languageCode;
+ final country = appLocale.countryCode;
+
+ // 尝试从语言管理器获取对应的ASR代码
+ final asrCode = languageManager.getAsrCodeByShortCode(lang);
+ if (asrCode != null) {
+ supportedLanguages.add(asrCode);
+ } else if (country != null && country.isNotEmpty) {
+ // 构造 Azure SDK 需要的格式 (e.g., 'en-US')
+ supportedLanguages.add('$lang-$country');
+ } else {
+ supportedLanguages.add('en-US'); // 默认英语
+ }
}
}
- // 添加 en-US 作为备选
- // if (!supportedLanguages.contains('en-US')) {
- // supportedLanguages.add('en-US');
- // }
-
// 最终兜底
if (supportedLanguages.isEmpty) {
supportedLanguages.add('en-US');
diff --git a/lib/data/services/novice_guide_service.dart b/lib/data/services/novice_guide_service.dart
new file mode 100644
index 000000000..d32be2368
--- /dev/null
+++ b/lib/data/services/novice_guide_service.dart
@@ -0,0 +1,104 @@
+import 'dart:async';
+
+import 'package:ble_service/ble_service.dart';
+import 'package:flutter_intro/flutter_intro.dart';
+import 'package:get/get.dart';
+import 'package:get_storage/get_storage.dart';
+
+class NoviceGuideService extends GetxService {
+ final GetStorage _storage = GetStorage();
+
+ final BleService _bleService = BleService();
+ StreamSubscription? _statusSubscription;
+
+ bool isHomeGuide = true;
+ bool isAIGuide = true;
+
+ RxBool isWakeup = false.obs;
+
+ int aiStep = 0;
+
+ List aiStepsList = [
+ {
+ 'start': '对小言说:“今天天气怎么样”',
+ 'input': '今天天气怎么样',
+ 'send': '小言会为你实时为你播报天气',
+ 'end': '小言能实时为你播报天气,试试其他指令吧',
+ },
+ {
+ 'start': '对小言说:“请播放一组摇滚乐”',
+ 'input': '请播放一组摇滚乐',
+ 'send': '小言会为你搜寻到一组音乐',
+ 'end': '小言已成功为您搜寻到一组音乐,试试其他指令吧',
+ },
+ {
+ 'start': '对小言说:“导航到深圳机场”',
+ 'input': '导航到深圳机场',
+ 'send': '小言会为你规划路线',
+ 'end': '小言已成功为您导航到深圳机场,试试其他指令吧',
+ },
+ {
+ 'start': '对小言说:“明天下午3点创建会议提醒”',
+ 'input': '明天下午3点创建会议提醒',
+ 'send': '小言会为你创建会议提醒',
+ 'end': '小言已成功为您创建会议提醒,试试其他指令吧',
+ },
+ {
+ 'start': '对小言说:“拨打妈妈的电话”',
+ 'input': '拨打妈妈的电话',
+ 'send': '小言会为你拨打电话',
+ 'end': '小言已成功为您拨打电话,试试其他指令吧',
+ },
+ {
+ 'start': '对小言说:“给爸爸发短信说晚上回家吃饭’”',
+ 'input': '给爸爸发短信说晚上回家吃饭’',
+ 'send': '小言会为你发送短信',
+ 'end': '短信已发送,语音操控更便捷',
+ },
+ ];
+
+ @override
+ void onInit() {
+ super.onInit();
+ // isHomeGuide = _storage.read('home_guide') ?? false;
+ // isAIGuide = _storage.read('ai_guide') ?? false;
+ }
+
+ @override
+ void onClose() {
+ closeWakeupListen();
+ super.onClose();
+ }
+
+ void startHomeGuide() async {
+ if (!isHomeGuide) {
+ await Future.delayed(const Duration(milliseconds: 200));
+ isHomeGuide = true;
+ _storage.write('home_guide', true);
+ Intro.of(Get.context!).start(group: 'home');
+ }
+ }
+
+ void startAIGuide() async {
+ if (!isAIGuide) {
+ await Future.delayed(const Duration(milliseconds: 200));
+ isAIGuide = true;
+ _storage.write('ai_guide', true);
+ Intro.of(Get.context!).start(group: 'ai');
+ }
+ }
+
+ void startWakeupListen() {
+ _bleService.setWakeup(false);
+ _statusSubscription = _bleService.onStatusUpdated.listen((event) {
+ if (event is Map && event['type'] == 'wakeup') {
+ isWakeup.value = true;
+ }
+ });
+ }
+
+ void closeWakeupListen() {
+ _bleService.setWakeup(true);
+ _statusSubscription?.cancel();
+ }
+}
diff --git a/lib/data/services/version_update_service.dart b/lib/data/services/version_update_service.dart
index f2696b5b8..4c408e107 100644
--- a/lib/data/services/version_update_service.dart
+++ b/lib/data/services/version_update_service.dart
@@ -5,6 +5,7 @@ import 'package:package_info_plus/package_info_plus.dart';
import 'package:url_launcher/url_launcher.dart';
import 'package:logger/logger.dart';
import 'package:device_info_plus/device_info_plus.dart';
+import 'package:flutter_dotenv/flutter_dotenv.dart';
import '../models/channel_app_model.dart';
import 'network/api.dart';
import 'dart:math' as math;
@@ -13,15 +14,62 @@ import 'dart:math' as math;
class VersionUpdateService extends GetxService {
static VersionUpdateService get to => Get.find();
- final Logger _logger = Logger();
+ // 静态应用信息(内存缓存)
+ static String? _appVersion = ""; //应用版本号
+ static String? _buildNumber = ""; //构建号
+ static String? _platform = ""; //平台类型
+ static String? _deviceBrand = ""; //设备品牌
+ static UserChannel? _channel; //渠道
+ static String? _latestVersion = ""; //最新版本号+构建号
+ static bool? _hasUpdate = false; //是否有更新
+ static bool? _isForceUpdate = false; //是否强制更新
+ static String? _updateUrl = ""; //更新链接
+ static bool? _isShowTourists = false; //是否显示游客登录
+ static DateTime? _lastCheckTime; //上次检查时间
+ static const int _cacheIntervalMinutes = 10; // 可配置化
+
+ // 添加 getter 方法来访问缓存的静态数据
+ /// 获取缓存的应用版本
+ String get cachedAppVersion => _appVersion ?? '';
+
+ /// 获取缓存的构建号
+ String get cachedBuildNumber => _buildNumber ?? '';
+
+ /// 获取缓存的平台信息
+ String get cachedPlatform => _platform ?? '';
+
+ /// 获取缓存的设备品牌
+ String get cachedDeviceBrand => _deviceBrand ?? '';
+
+ /// 获取缓存的渠道信息
+ UserChannel get cachedChannel => _channel ?? UserChannel.UNKNOWN;
+
+ /// 获取缓存的最新版本
+ String get cachedLatestVersion => _latestVersion ?? '';
+
+ /// 获取缓存的是否有更新
+ bool get cachedHasUpdate => _hasUpdate ?? false;
+
+ /// 获取缓存的是否强制更新
+ bool get cachedIsForceUpdate => _isForceUpdate ?? false;
+
+ /// 获取缓存的更新地址
+ String get cachedUpdateUrl => _updateUrl ?? '';
+
+ /// 获取缓存的游客登录状态
+ bool get cachedShowTourists => _isShowTourists ?? false;
- // 游客登录显示状态
- final isShowTourists = false.obs;
+ /// 获取上次检查时间
+ DateTime? get lastCheckTime => _lastCheckTime;
+
+ final Logger _logger = Logger();
/// 获取当前应用版本信息
Future getCurrentVersion() async {
try {
final packageInfo = await PackageInfo.fromPlatform();
+ _appVersion = packageInfo.version;
+ _buildNumber = packageInfo.buildNumber;
return '${packageInfo.version}+${packageInfo.buildNumber}';
} catch (e) {
_logger.e('获取当前版本信息失败: $e');
@@ -31,14 +79,6 @@ class VersionUpdateService extends GetxService {
/// 获取当前平台信息
Future getCurrentPlatformInfo() async {
- if (kIsWeb) {
- return const PlatformInfo(
- platform: 'web',
- storeType: AppStoreType.official,
- channel: UserChannel.OFFICIAL,
- );
- }
-
if (Platform.isIOS) {
return const PlatformInfo(
platform: 'ios',
@@ -66,10 +106,14 @@ class VersionUpdateService extends GetxService {
final androidInfo = await deviceInfo.androidInfo;
final brand = androidInfo.brand.toLowerCase();
- return const PlatformInfo(
+ _deviceBrand = brand;
+ String channelFromEnv = dotenv.env['Channel'] ?? 'OFFICIAL';
+ _channel = UserChannel.fromName(channelFromEnv);
+
+ return PlatformInfo(
platform: 'android',
storeType: AppStoreType.official,
- channel: UserChannel.OFFICIAL,
+ channel: _channel ?? UserChannel.OFFICIAL,
);
// switch (brand) {
@@ -121,10 +165,27 @@ class VersionUpdateService extends GetxService {
/// 检查版本更新
Future checkForUpdate() async {
+ if (_lastCheckTime != null &&
+ DateTime.now().difference(_lastCheckTime!).inMinutes <
+ _cacheIntervalMinutes) {
+ return VersionUpdateInfo(
+ hasUpdate: _hasUpdate ?? false,
+ currentVersion: _appVersion ?? '1.0.0',
+ latestVersion: (_latestVersion ?? '1.0.0').split('+')[0],
+ updateType: _hasUpdate!
+ ? (_isForceUpdate! ? UpdateType.force : UpdateType.optional)
+ : UpdateType.none,
+ );
+ }
+
try {
final currentVersion = await getCurrentVersion();
final platformInfo = await getCurrentPlatformInfo();
+ _platform = platformInfo.platform;
+ _channel = platformInfo.channel;
+ _lastCheckTime = DateTime.now();
+
// 调用后端接口获取最新版本信息
final response =
await Api.getChannelApp({'channel': platformInfo.channel.value});
@@ -144,9 +205,11 @@ class VersionUpdateService extends GetxService {
// 直接从response中获取app信息
final appInfo = AppInfo.fromJson(response['app']);
_logger.d('获取到的最新版本信息: ${appInfo.toJson()}');
+ _latestVersion = appInfo.version;
+ _updateUrl = appInfo.address;
// 设置游客登录显示状态
- isShowTourists.value = appInfo.tourists;
+ _isShowTourists = appInfo.tourists;
_logger.d('从服务器获取游客登录配置: ${appInfo.tourists}');
// if (channelAppResponse.code != 0 || channelAppResponse.data == null) {
@@ -160,18 +223,17 @@ class VersionUpdateService extends GetxService {
// }
// final appInfo = channelAppResponse.data!.app;
- final hasUpdate = _compareVersions(currentVersion, appInfo.version);
+ _hasUpdate = _compareVersions(currentVersion, appInfo.version);
// 判断更新类型:如果主版本号不同则强制更新
UpdateType updateType = UpdateType.none;
- if (hasUpdate) {
- final isForceUpdate =
- _shouldForceUpdate(currentVersion, appInfo.version);
- updateType = isForceUpdate ? UpdateType.force : UpdateType.optional;
+ if (_hasUpdate!) {
+ _isForceUpdate = _shouldForceUpdate(currentVersion, appInfo.version);
+ updateType = _isForceUpdate! ? UpdateType.force : UpdateType.optional;
}
return VersionUpdateInfo(
- hasUpdate: hasUpdate,
+ hasUpdate: _hasUpdate!,
currentVersion: currentVersion.split('+')[0],
latestVersion: appInfo.version.split('+')[0],
appInfo: appInfo,
@@ -180,7 +242,7 @@ class VersionUpdateService extends GetxService {
} catch (e) {
_logger.e('检查版本更新失败: $e');
// 发生错误时,保持默认值 false
- isShowTourists.value = false;
+ _isShowTourists = false;
final currentVersion = await getCurrentVersion();
return VersionUpdateInfo(
hasUpdate: false,
@@ -270,84 +332,16 @@ class VersionUpdateService extends GetxService {
return false;
}
- final platformInfo = await getCurrentPlatformInfo();
-
try {
- switch (platformInfo.storeType) {
- case AppStoreType.appStore:
- return await _openAppStore();
- case AppStoreType.googlePlay:
- return await _openGooglePlay();
- case AppStoreType.huawei:
- return await _openHuaweiStore();
- case AppStoreType.xiaomi:
- return await _openXiaomiStore();
- case AppStoreType.oppo:
- return await _openOppoStore();
- case AppStoreType.vivo:
- return await _openVivoStore();
- case AppStoreType.honor:
- return await _openHonorStore();
- case AppStoreType.official:
- return await _openOfficialDownload(updateInfo.appInfo!.address!);
- default:
- return await _openOfficialDownload(updateInfo.appInfo!.address!);
- }
+ return await _openDownloadUrl(updateInfo.appInfo!.address!);
} catch (e) {
_logger.e('执行更新失败: $e');
return false;
}
}
- /// 打开iOS App Store
- Future _openAppStore() async {
- // 这里需要替换为实际的App Store ID
- const appStoreUrl = 'https://apps.apple.com/app/id你的应用ID';
- return await _launchUrl(appStoreUrl);
- }
-
- /// 打开Google Play
- Future _openGooglePlay() async {
- // 这里需要替换为实际的包名
- const packageName = 'com.yourcompany.deepvoice';
- const playStoreUrl =
- 'https://play.google.com/store/apps/details?id=$packageName';
- return await _launchUrl(playStoreUrl);
- }
-
- /// 打开华为应用市场
- Future _openHuaweiStore() async {
- const huaweiStoreUrl = 'appmarket://details?id=com.yourcompany.deepvoice';
- return await _launchUrl(huaweiStoreUrl);
- }
-
- /// 打开小米应用商店
- Future _openXiaomiStore() async {
- const xiaomiStoreUrl = 'mimarket://details?id=com.yourcompany.deepvoice';
- return await _launchUrl(xiaomiStoreUrl);
- }
-
- /// 打开OPPO软件商店
- Future _openOppoStore() async {
- const oppoStoreUrl =
- 'oppomarket://details?packagename=com.yourcompany.deepvoice';
- return await _launchUrl(oppoStoreUrl);
- }
-
- /// 打开vivo应用商店
- Future _openVivoStore() async {
- const vivoStoreUrl = 'vivomarket://details?id=com.yourcompany.deepvoice';
- return await _launchUrl(vivoStoreUrl);
- }
-
- /// 打开荣耀应用市场
- Future _openHonorStore() async {
- const honorStoreUrl = 'honormarket://details?id=com.yourcompany.deepvoice';
- return await _launchUrl(honorStoreUrl);
- }
-
- /// 打开官网下载
- Future _openOfficialDownload(String downloadUrl) async {
+ /// 打开下载链接
+ Future _openDownloadUrl(String downloadUrl) async {
return await _launchUrl(downloadUrl);
}
diff --git a/lib/main.dart b/lib/main.dart
index 7c5818e36..6a976e86e 100644
--- a/lib/main.dart
+++ b/lib/main.dart
@@ -1,5 +1,7 @@
import 'package:flutter/material.dart';
+import 'package:flutter/services.dart';
import 'package:flutter_easyloading/flutter_easyloading.dart';
+import 'package:flutter_intro/flutter_intro.dart';
import 'package:flutter_localizations/flutter_localizations.dart';
import 'package:get/get.dart';
import 'package:flutter_dotenv/flutter_dotenv.dart';
@@ -8,8 +10,6 @@ import 'package:flutter_screenutil/flutter_screenutil.dart';
import 'core/bindings/initial_binding.dart';
import 'routes/app_pages.dart';
import 'routes/app_routes.dart';
-import 'dart:io';
-import 'package:window_size/window_size.dart';
import 'core/translations/app_translations.dart';
import 'core/themes/app_theme.dart';
import 'core/themes/theme_service.dart';
@@ -19,6 +19,8 @@ void main() async {
// 确保Flutter绑定初始化
WidgetsFlutterBinding.ensureInitialized();
+ await SystemChrome.setPreferredOrientations([DeviceOrientation.portraitUp]);
+
// 初始化存储 - 在创建应用之前初始化
await GetStorage.init();
@@ -28,28 +30,6 @@ void main() async {
// 初始化登录服务(包含Firebase初始化)
LoginService();
- // 设置窗口大小(仅在 macOS 下生效)
- if (Platform.isMacOS) {
- const double width = 375.0;
- const double height = 812.0;
-
- setWindowTitle('Voitrans');
- setWindowMinSize(const Size(width, height));
- setWindowMaxSize(const Size(width, height));
-
- // 居中显示窗口
- final screen = await getCurrentScreen();
- if (screen != null) {
- final screenFrame = screen.frame;
- final windowFrame = Rect.fromCenter(
- center: screenFrame.center,
- width: width,
- height: height,
- );
- setWindowFrame(windowFrame);
- }
- }
-
// 运行应用
runApp(const MainApp());
}
@@ -92,7 +72,10 @@ class MainApp extends StatelessWidget {
data: MediaQuery.of(context).copyWith(
textScaler: TextScaler.noScaling,
),
- child: widget!,
+ child: Intro(
+ padding: EdgeInsets.zero,
+ child: widget!,
+ ),
);
},
),
diff --git a/lib/modules/agent/controllers/agent_controller.dart b/lib/modules/agent/controllers/agent_controller.dart
index dda9ed2c4..82e7696e1 100644
--- a/lib/modules/agent/controllers/agent_controller.dart
+++ b/lib/modules/agent/controllers/agent_controller.dart
@@ -3,6 +3,9 @@ import 'dart:convert';
import 'dart:ffi';
import 'dart:io';
import 'package:amap_service/amap_service.dart';
+import '../../../data/services/ble_manager.dart';
+import '../../../data/services/bluetooth_manager.dart';
+import '../../../data/services/novice_guide_service.dart';
import '/core/utils/upload_oss.dart';
import '/data/models/user_Info.dart';
import '/data/services/music_manager.dart';
@@ -39,6 +42,11 @@ class AgentController extends GetxController {
final TextEditingController textController = TextEditingController();
final ScrollController scrollController = ScrollController();
+ final noviceGuideService = Get.find();
+ // 确保服务已初始化
+ // ========== 依赖服务 ==========
+ final BluetoothManager _bluetoothManager = Get.find();
+ final BleManager _bleManager = Get.find();
//音乐播放管理
final _musiceManager = Get.find();
@@ -118,7 +126,7 @@ class AgentController extends GetxController {
String _currentResponseId = '';
final TAG = 'AgentController';
-
+ var AppDir = "".obs;
// 会话ID
final String sessionId = 'default_agent';
@@ -145,6 +153,7 @@ class AgentController extends GetxController {
@override
void onInit() {
super.onInit();
+ _initAppDir();
_loadTtsPreference(); //加载TTS偏好设置
_setupScrollListener(); //设置滚动监听器
_loadChatHistory(); //加载聊天历史记录
@@ -153,11 +162,13 @@ class AgentController extends GetxController {
_setupMediaSessionCallbacks(); //设置媒体会话回调
_setupTextInputListener(); //设置文本输入监听器
-
-
startVoiceInput(); //开启语音输入
}
+ Future _initAppDir() async {
+ AppDir.value = (await getApplicationDocumentsDirectory()).path;
+ }
+
// 初始化音乐播放状态
Future _initMusicStatus() async {
try {
@@ -285,7 +296,7 @@ class AgentController extends GetxController {
page: 1, //从第一页开始
pageSize: pageSize, //每页20条记录
);
-
+ Logger.i(TAG, '加载聊天历史记录: $result');
if (result.messages.isNotEmpty) {
// 转换并添加消息到列表
final historyMessages = _convertChatMessagesToMessages(result.messages);
@@ -297,7 +308,8 @@ class AgentController extends GetxController {
// 更新分页状态
currentPage.value = 1;
hasMoreMessages.value = result.totalPages > 1;
-
+ print(
+ 'liwei-------------读取历史记录 ${AppDir.value}${historyMessages[0].imagePath}');
Logger.i(TAG,
'成功加载 ${historyMessages.length} 条聊天记录,总页数: ${result.totalPages}');
_logMessageDetails(historyMessages);
@@ -415,7 +427,7 @@ class AgentController extends GetxController {
'meta: ${msg.meta != null ? "有" : "无"}'
'}');
}
- Logger.d(TAG, '===== 历史消息详情结束 =====');
+ Logger.d(TAG, '===== 历史消息详情结束 ==lishi===');
}
// 设置滚动监听器
@@ -557,16 +569,16 @@ class AgentController extends GetxController {
break;
case AgentServiceEventType.ttsStarted:
- // isSpeaking.value = true;
- // Logger.i(TAG, 'TTS开始播放');
+ isSpeaking.value = true;
+ print('TTS开始播放');
break;
case AgentServiceEventType.ttsCompleted:
case AgentServiceEventType.ttsStopped:
case AgentServiceEventType.ttsCanceled:
- // isSpeaking.value = false;
+ isSpeaking.value = false;
- // Logger.i(TAG, 'TTS停止播放, $event.type');
+ Logger.i(TAG, 'TTS停止播放, $event.type');
break;
case AgentServiceEventType.playbackStarted:
@@ -897,10 +909,14 @@ class AgentController extends GetxController {
}
// 复制图片到应用永久存储目录
- final permanentImagePath = await _copyImageToPermanentStorage(imagePath);
+ // 复制图片到应用永久存储目录
+ final imagePathResult = await _copyImageToPermanentStorage(imagePath);
+ final permanentImagePath = imagePathResult['fullPath']!;
+ final relativeImagePath = imagePathResult['relativePath']!;
var ImageUrl = await UploadOss.upload(
- filepath: imagePath, rootDir: 'User/${User.instance.uid}/ChatImages');
- Logger.i(TAG, '图片上传: $imagePath URL: $ImageUrl');
+ filepath: permanentImagePath,
+ rootDir: 'User/${User.instance.uid}/ChatImages');
+ Logger.i(TAG, '图片上传: $permanentImagePath URL: $ImageUrl');
// 显示的文本,如果没有提供则使用默认值
final displayText = text?.isNotEmpty == true ? text! : '[图片]';
@@ -909,7 +925,7 @@ class AgentController extends GetxController {
isUser: true,
text: displayText,
hasImage: true,
- imagePath: permanentImagePath,
+ imagePath: relativeImagePath,
);
messages.add(message);
@@ -929,7 +945,7 @@ class AgentController extends GetxController {
// 调用Agent Service处理图片,考虑TTS状态
await AgentService.processImageInput(
- permanentImagePath,
+ relativeImagePath,
ImageUrl,
text: text ?? '',
speakResponse: isTtsEnabled.value,
@@ -942,12 +958,16 @@ class AgentController extends GetxController {
}
// 将图片复制到应用的永久存储目录
- Future _copyImageToPermanentStorage(String sourcePath) async {
+ Future