diff --git a/packages/smooth_app/assets/animations/stars.json b/packages/smooth_app/assets/animations/stars.json
new file mode 100644
index 00000000000..4c572e68355
--- /dev/null
+++ b/packages/smooth_app/assets/animations/stars.json
@@ -0,0 +1 @@
+{"v":"5.1.1","fr":12,"ip":0,"op":28,"w":400,"h":400,"nm":"star_final","ddd":0,"assets":[{"id":"comp_54","layers":[{"ddd":0,"ind":1,"ty":4,"nm":"star_highlight 2","parent":3,"sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[6.175,-32.519,0],"ix":2},"a":{"a":0,"k":[6.5,-34,0],"ix":1},"s":{"a":0,"k":[95,95,100],"ix":6}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0,0],[0,0]],"o":[[0,0],[0,0]],"v":[[3,-41],[10,-27]],"c":false},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"st","c":{"a":0,"k":[1,0.854901969433,0.407843142748,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":5,"ix":5},"lc":2,"lj":1,"ml":10,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","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":"Transform"}],"nm":"null","np":2,"cix":2,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":36,"st":0,"bm":0},{"ddd":0,"ind":2,"ty":4,"nm":"star_highlight","parent":5,"sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[6.175,-32.519,0],"ix":2},"a":{"a":0,"k":[6.5,-34,0],"ix":1},"s":{"a":0,"k":[95,95,100],"ix":6}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0,0],[0,0]],"o":[[0,0],[0,0]],"v":[[3,-41],[10,-27]],"c":false},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"st","c":{"a":0,"k":[1,0.854901969433,0.407843142748,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":5,"ix":5},"lc":2,"lj":1,"ml":10,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","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":"Transform"}],"nm":"null","np":2,"cix":2,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":36,"st":0,"bm":0},{"ddd":0,"ind":3,"ty":4,"nm":"star_front 2","sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":1,"k":[{"i":{"x":0.667,"y":1},"o":{"x":0.333,"y":0},"n":"0p667_1_0p333_0","t":8,"s":[221,195.627,0],"e":[200,195.627,0],"to":[0,0,0],"ti":[0,0,0]},{"t":11}],"ix":2},"a":{"a":0,"k":[0,-4.373,0],"ix":1},"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]},"n":["0p667_1_0p333_0","0p667_1_0p333_0","0p667_1_0p333_0"],"t":8,"s":[0,100,100],"e":[100,100,100]},{"t":11}],"ix":6}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[-1.825,-3.698],[0,0],[-1.621,-0.235],[0,0],[2.953,-2.878],[0,0],[-0.277,-1.614],[0,0],[3.65,1.919],[0,0],[1.449,-0.762],[0,0],[-0.697,4.064],[0,0],[1.173,1.143],[0,0],[-4.081,0.593],[0,0],[-0.725,1.468],[0,0]],"o":[[0,0],[0.725,1.468],[0,0],[4.081,0.593],[0,0],[-1.173,1.143],[0,0],[0.697,4.064],[0,0],[-1.449,-0.762],[0,0],[-3.65,1.919],[0,0],[0.277,-1.614],[0,0],[-2.953,-2.878],[0,0],[1.621,-0.235],[0,0],[1.825,-3.698]],"v":[[4.462,-51.216],[16.649,-26.521],[20.396,-23.8],[47.648,-19.839],[50.406,-11.353],[30.685,7.869],[29.254,12.273],[33.91,39.416],[26.691,44.661],[2.315,31.846],[-2.315,31.846],[-26.691,44.661],[-33.91,39.416],[-29.254,12.273],[-30.685,7.869],[-50.406,-11.353],[-47.648,-19.839],[-20.396,-23.8],[-16.649,-26.521],[-4.462,-51.216]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"fl","c":{"a":0,"k":[1,0.698039233685,0,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[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":"Transform"}],"nm":"null","np":2,"cix":2,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":36,"st":0,"bm":0},{"ddd":0,"ind":4,"ty":4,"nm":"star_back 2","sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":1,"k":[{"i":{"x":0.667,"y":1},"o":{"x":0.333,"y":0},"n":"0p667_1_0p333_0","t":8,"s":[179,195.627,0],"e":[200,195.627,0],"to":[0,0,0],"ti":[0,0,0]},{"t":11}],"ix":2},"a":{"a":0,"k":[0,-4.373,0],"ix":1},"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]},"n":["0p667_1_0p333_0","0p667_1_0p333_0","0p667_1_0p333_0"],"t":8,"s":[0,100,100],"e":[100,100,100]},{"t":11}],"ix":6}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[-1.825,-3.698],[0,0],[-1.621,-0.235],[0,0],[2.953,-2.878],[0,0],[-0.277,-1.614],[0,0],[3.65,1.919],[0,0],[1.449,-0.762],[0,0],[-0.697,4.064],[0,0],[1.173,1.143],[0,0],[-4.081,0.593],[0,0],[-0.725,1.468],[0,0]],"o":[[0,0],[0.725,1.468],[0,0],[4.081,0.593],[0,0],[-1.173,1.143],[0,0],[0.697,4.064],[0,0],[-1.449,-0.762],[0,0],[-3.65,1.919],[0,0],[0.277,-1.614],[0,0],[-2.953,-2.878],[0,0],[1.621,-0.235],[0,0],[1.825,-3.698]],"v":[[4.462,-51.216],[16.649,-26.521],[20.396,-23.8],[47.648,-19.839],[50.406,-11.353],[30.685,7.869],[29.254,12.273],[33.91,39.416],[26.691,44.661],[2.315,31.846],[-2.315,31.846],[-26.691,44.661],[-33.91,39.416],[-29.254,12.273],[-30.685,7.869],[-50.406,-11.353],[-47.648,-19.839],[-20.396,-23.8],[-16.649,-26.521],[-4.462,-51.216]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"fl","c":{"a":0,"k":[0.964705884457,0.623529434204,0,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[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":"Transform"}],"nm":"null","np":2,"cix":2,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":36,"st":0,"bm":0},{"ddd":0,"ind":5,"ty":4,"nm":"star_front","sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":1,"k":[{"i":{"x":0.667,"y":1},"o":{"x":0.333,"y":0},"n":"0p667_1_0p333_0","t":6,"s":[200,195.627,0],"e":[179,195.627,0],"to":[0,0,0],"ti":[0,0,0]},{"t":9}],"ix":2},"a":{"a":0,"k":[0,-4.373,0],"ix":1},"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]},"n":["0p667_1_0p333_0","0p667_1_0p333_0","0p667_1_0p333_0"],"t":6,"s":[100,100,100],"e":[0,100,100]},{"t":9}],"ix":6}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[-1.825,-3.698],[0,0],[-1.621,-0.235],[0,0],[2.953,-2.878],[0,0],[-0.277,-1.614],[0,0],[3.65,1.919],[0,0],[1.449,-0.762],[0,0],[-0.697,4.064],[0,0],[1.173,1.143],[0,0],[-4.081,0.593],[0,0],[-0.725,1.468],[0,0]],"o":[[0,0],[0.725,1.468],[0,0],[4.081,0.593],[0,0],[-1.173,1.143],[0,0],[0.697,4.064],[0,0],[-1.449,-0.762],[0,0],[-3.65,1.919],[0,0],[0.277,-1.614],[0,0],[-2.953,-2.878],[0,0],[1.621,-0.235],[0,0],[1.825,-3.698]],"v":[[4.462,-51.216],[16.649,-26.521],[20.396,-23.8],[47.648,-19.839],[50.406,-11.353],[30.685,7.869],[29.254,12.273],[33.91,39.416],[26.691,44.661],[2.315,31.846],[-2.315,31.846],[-26.691,44.661],[-33.91,39.416],[-29.254,12.273],[-30.685,7.869],[-50.406,-11.353],[-47.648,-19.839],[-20.396,-23.8],[-16.649,-26.521],[-4.462,-51.216]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"fl","c":{"a":0,"k":[1,0.698039233685,0,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[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":"Transform"}],"nm":"null","np":2,"cix":2,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":36,"st":0,"bm":0},{"ddd":0,"ind":6,"ty":4,"nm":"star_back","sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":1,"k":[{"i":{"x":0.667,"y":1},"o":{"x":0.333,"y":0},"n":"0p667_1_0p333_0","t":6,"s":[200,195.627,0],"e":[221,195.627,0],"to":[0,0,0],"ti":[0,0,0]},{"t":9}],"ix":2},"a":{"a":0,"k":[0,-4.373,0],"ix":1},"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]},"n":["0p667_1_0p333_0","0p667_1_0p333_0","0p667_1_0p333_0"],"t":6,"s":[100,100,100],"e":[0,100,100]},{"t":9}],"ix":6}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[-1.825,-3.698],[0,0],[-1.621,-0.235],[0,0],[2.953,-2.878],[0,0],[-0.277,-1.614],[0,0],[3.65,1.919],[0,0],[1.449,-0.762],[0,0],[-0.697,4.064],[0,0],[1.173,1.143],[0,0],[-4.081,0.593],[0,0],[-0.725,1.468],[0,0]],"o":[[0,0],[0.725,1.468],[0,0],[4.081,0.593],[0,0],[-1.173,1.143],[0,0],[0.697,4.064],[0,0],[-1.449,-0.762],[0,0],[-3.65,1.919],[0,0],[0.277,-1.614],[0,0],[-2.953,-2.878],[0,0],[1.621,-0.235],[0,0],[1.825,-3.698]],"v":[[4.462,-51.216],[16.649,-26.521],[20.396,-23.8],[47.648,-19.839],[50.406,-11.353],[30.685,7.869],[29.254,12.273],[33.91,39.416],[26.691,44.661],[2.315,31.846],[-2.315,31.846],[-26.691,44.661],[-33.91,39.416],[-29.254,12.273],[-30.685,7.869],[-50.406,-11.353],[-47.648,-19.839],[-20.396,-23.8],[-16.649,-26.521],[-4.462,-51.216]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"fl","c":{"a":0,"k":[0.964705884457,0.623529434204,0,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[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":"Transform"}],"nm":"null","np":2,"cix":2,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":36,"st":0,"bm":0},{"ddd":0,"ind":7,"ty":4,"nm":"star_side 3","sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"s":true,"x":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"n":["0p833_0p833_0p167_0p167"],"t":6,"s":[224],"e":[226.765]},{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"n":["0p833_0p833_0p167_0p167"],"t":6.2,"s":[226.765],"e":[224.296]},{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"n":["0p833_0p833_0p167_0p167"],"t":6.6,"s":[224.296],"e":[223.062]},{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"n":["0p833_0p833_0p167_0p167"],"t":6.8,"s":[223.062],"e":[220.827]},{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"n":["0p833_0p833_0p167_0p167"],"t":7,"s":[220.827],"e":[215.501]},{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"n":["0p833_0p833_0p167_0p167"],"t":7.4,"s":[215.501],"e":[212.512]},{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"n":["0p833_0p833_0p167_0p167"],"t":8,"s":[212.512],"e":[211.952]},{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"n":["0p833_0p833_0p167_0p167"],"t":8.8,"s":[211.952],"e":[213.25]},{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"n":["0p833_0p833_0p167_0p167"],"t":9,"s":[213.25],"e":[217.296]},{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"n":["0p833_0p833_0p167_0p167"],"t":9.6,"s":[217.296],"e":[220.827]},{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"n":["0p833_0p833_0p167_0p167"],"t":10,"s":[220.827],"e":[225.596]},{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"n":["0p833_0p833_0p167_0p167"],"t":10.4,"s":[225.596],"e":[227.25]},{"t":11}],"ix":3},"y":{"a":0,"k":230.25,"ix":4}},"a":{"a":0,"k":[-7.286,-4.499,0],"ix":1},"s":{"a":1,"k":[{"i":{"x":[0.833,0.833,0.833],"y":[0.833,0.833,0.833]},"o":{"x":[0.167,0.167,0.167],"y":[0.167,0.167,0.167]},"n":["0p833_0p833_0p167_0p167","0p833_0p833_0p167_0p167","0p833_0p833_0p167_0p167"],"t":6,"s":[0,30,100],"e":[65,30,100]},{"i":{"x":[0.833,0.833,0.833],"y":[0.833,0.833,0.833]},"o":{"x":[0.167,0.167,0.167],"y":[0.167,0.167,0.167]},"n":["0p833_0p833_0p167_0p167","0p833_0p833_0p167_0p167","0p833_0p833_0p167_0p167"],"t":6.6,"s":[65,30,100],"e":[83.5,30,100]},{"i":{"x":[0.833,0.833,0.833],"y":[0.833,0.833,0.833]},"o":{"x":[0.167,0.167,0.167],"y":[0.167,0.167,0.167]},"n":["0p833_0p833_0p167_0p167","0p833_0p833_0p167_0p167","0p833_0p833_0p167_0p167"],"t":6.8,"s":[83.5,30,100],"e":[103,30,100]},{"i":{"x":[0.833,0.833,0.833],"y":[0.833,0.833,0.833]},"o":{"x":[0.167,0.167,0.167],"y":[0.167,0.167,0.167]},"n":["0p833_0p833_0p167_0p167","0p833_0p833_0p167_0p167","0p833_0p833_0p167_0p167"],"t":7,"s":[103,30,100],"e":[143.2,30,100]},{"i":{"x":[0.833,0.833,0.833],"y":[0.833,0.833,0.833]},"o":{"x":[0.167,0.167,0.167],"y":[0.167,0.167,0.167]},"n":["0p833_0p833_0p167_0p167","0p833_0p833_0p167_0p167","0p833_0p833_0p167_0p167"],"t":7.4,"s":[143.2,30,100],"e":[176,30,100]},{"i":{"x":[0.833,0.833,0.833],"y":[0.833,0.833,0.833]},"o":{"x":[0.167,0.167,0.167],"y":[0.167,0.167,0.167]},"n":["0p833_0p833_0p167_0p167","0p833_0p833_0p167_0p167","0p833_0p833_0p167_0p167"],"t":8,"s":[176,30,100],"e":[163,30,100]},{"i":{"x":[0.833,0.833,0.833],"y":[0.833,0.833,0.833]},"o":{"x":[0.167,0.167,0.167],"y":[0.167,0.167,0.167]},"n":["0p833_0p833_0p167_0p167","0p833_0p833_0p167_0p167","0p833_0p833_0p167_0p167"],"t":9,"s":[163,30,100],"e":[111,30,100]},{"i":{"x":[0.833,0.833,0.833],"y":[0.833,0.833,0.833]},"o":{"x":[0.167,0.167,0.167],"y":[0.167,0.167,0.167]},"n":["0p833_0p833_0p167_0p167","0p833_0p833_0p167_0p167","0p833_0p833_0p167_0p167"],"t":10,"s":[111,30,100],"e":[61.8,30,100]},{"i":{"x":[0.833,0.833,0.833],"y":[0.833,0.833,0.833]},"o":{"x":[0.167,0.167,0.167],"y":[0.167,0.167,0.167]},"n":["0p833_0p833_0p167_0p167","0p833_0p833_0p167_0p167","0p833_0p833_0p167_0p167"],"t":10.4,"s":[61.8,30,100],"e":[0,30,100]},{"t":11}],"ix":6}},"ao":0,"shapes":[{"ty":"gr","it":[{"ty":"rc","d":1,"s":{"a":0,"k":[12,99.002],"ix":2},"p":{"a":0,"k":[0,0],"ix":3},"r":{"a":0,"k":0,"ix":4},"nm":"Rectangle Path 1","mn":"ADBE Vector Shape - Rect","hd":false},{"ty":"st","c":{"a":0,"k":[0.099248998305,0.042310000401,0.256403006759,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":0,"ix":5},"lc":1,"lj":1,"ml":4,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"fl","c":{"a":0,"k":[0.964705882353,0.623529411765,0,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[-7.286,-4.499],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Rectangle 1","np":3,"cix":2,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":36,"st":0,"bm":0},{"ddd":0,"ind":8,"ty":4,"nm":"star_side 2","sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"s":true,"x":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"n":["0p833_0p833_0p167_0p167"],"t":6,"s":[171.25],"e":[171.296]},{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"n":["0p833_0p833_0p167_0p167"],"t":6.2,"s":[171.296],"e":[175.387]},{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"n":["0p833_0p833_0p167_0p167"],"t":6.6,"s":[175.387],"e":[177.432]},{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"n":["0p833_0p833_0p167_0p167"],"t":6.8,"s":[177.432],"e":[179.477]},{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"n":["0p833_0p833_0p167_0p167"],"t":7,"s":[179.477],"e":[183.867]},{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"n":["0p833_0p833_0p167_0p167"],"t":7.4,"s":[183.867],"e":[187.95]},{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"n":["0p833_0p833_0p167_0p167"],"t":8,"s":[187.95],"e":[187.89]},{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"n":["0p833_0p833_0p167_0p167"],"t":8.8,"s":[187.89],"e":[187]},{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"n":["0p833_0p833_0p167_0p167"],"t":9,"s":[187],"e":[182.837]},{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"n":["0p833_0p833_0p167_0p167"],"t":9.6,"s":[182.837],"e":[179.727]},{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"n":["0p833_0p833_0p167_0p167"],"t":10,"s":[179.727],"e":[175.436]},{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"n":["0p833_0p833_0p167_0p167"],"t":10.4,"s":[175.436],"e":[170]},{"t":11}],"ix":3},"y":{"a":0,"k":230,"ix":4}},"a":{"a":0,"k":[-7.286,-4.499,0],"ix":1},"s":{"a":1,"k":[{"i":{"x":[0.833,0.833,0.833],"y":[0.833,0.833,0.833]},"o":{"x":[0.167,0.167,0.167],"y":[0.167,0.167,0.167]},"n":["0p833_0p833_0p167_0p167","0p833_0p833_0p167_0p167","0p833_0p833_0p167_0p167"],"t":6,"s":[0,30,100],"e":[65.299,30,100]},{"i":{"x":[0.833,0.833,0.833],"y":[0.833,0.833,0.833]},"o":{"x":[0.167,0.167,0.167],"y":[0.167,0.167,0.167]},"n":["0p833_0p833_0p167_0p167","0p833_0p833_0p167_0p167","0p833_0p833_0p167_0p167"],"t":6.6,"s":[65.299,30,100],"e":[80.066,30,100]},{"i":{"x":[0.833,0.833,0.833],"y":[0.833,0.833,0.833]},"o":{"x":[0.167,0.167,0.167],"y":[0.167,0.167,0.167]},"n":["0p833_0p833_0p167_0p167","0p833_0p833_0p167_0p167","0p833_0p833_0p167_0p167"],"t":6.8,"s":[80.066,30,100],"e":[106.832,30,100]},{"i":{"x":[0.833,0.833,0.833],"y":[0.833,0.833,0.833]},"o":{"x":[0.167,0.167,0.167],"y":[0.167,0.167,0.167]},"n":["0p833_0p833_0p167_0p167","0p833_0p833_0p167_0p167","0p833_0p833_0p167_0p167"],"t":7,"s":[106.832,30,100],"e":[147.666,30,100]},{"i":{"x":[0.833,0.833,0.833],"y":[0.833,0.833,0.833]},"o":{"x":[0.167,0.167,0.167],"y":[0.167,0.167,0.167]},"n":["0p833_0p833_0p167_0p167","0p833_0p833_0p167_0p167","0p833_0p833_0p167_0p167"],"t":7.4,"s":[147.666,30,100],"e":[176.416,30,100]},{"i":{"x":[0.833,0.833,0.833],"y":[0.833,0.833,0.833]},"o":{"x":[0.167,0.167,0.167],"y":[0.167,0.167,0.167]},"n":["0p833_0p833_0p167_0p167","0p833_0p833_0p167_0p167","0p833_0p833_0p167_0p167"],"t":8,"s":[176.416,30,100],"e":[158,30,100]},{"i":{"x":[0.833,0.833,0.833],"y":[0.833,0.833,0.833]},"o":{"x":[0.167,0.167,0.167],"y":[0.167,0.167,0.167]},"n":["0p833_0p833_0p167_0p167","0p833_0p833_0p167_0p167","0p833_0p833_0p167_0p167"],"t":9,"s":[158,30,100],"e":[109.832,30,100]},{"i":{"x":[0.833,0.833,0.833],"y":[0.833,0.833,0.833]},"o":{"x":[0.167,0.167,0.167],"y":[0.167,0.167,0.167]},"n":["0p833_0p833_0p167_0p167","0p833_0p833_0p167_0p167","0p833_0p833_0p167_0p167"],"t":10,"s":[109.832,30,100],"e":[66.299,30,100]},{"i":{"x":[0.833,0.833,0.833],"y":[0.833,0.833,0.833]},"o":{"x":[0.167,0.167,0.167],"y":[0.167,0.167,0.167]},"n":["0p833_0p833_0p167_0p167","0p833_0p833_0p167_0p167","0p833_0p833_0p167_0p167"],"t":10.4,"s":[66.299,30,100],"e":[0,30,100]},{"t":11}],"ix":6}},"ao":0,"shapes":[{"ty":"gr","it":[{"ty":"rc","d":1,"s":{"a":0,"k":[12,99.002],"ix":2},"p":{"a":0,"k":[0,0],"ix":3},"r":{"a":0,"k":0,"ix":4},"nm":"Rectangle Path 1","mn":"ADBE Vector Shape - Rect","hd":false},{"ty":"st","c":{"a":0,"k":[0.099248998305,0.042310000401,0.256403006759,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":0,"ix":5},"lc":1,"lj":1,"ml":4,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"fl","c":{"a":0,"k":[0.964705882353,0.623529411765,0,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[-7.286,-4.499],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Rectangle 1","np":3,"cix":2,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":36,"st":0,"bm":0},{"ddd":0,"ind":9,"ty":4,"nm":"star_side","sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[200,146.031,0],"ix":2},"a":{"a":0,"k":[-7.286,-53.969,0],"ix":1},"s":{"a":1,"k":[{"i":{"x":[0.833,0.833,0.833],"y":[0.833,0.833,0.833]},"o":{"x":[0.167,0.167,0.167],"y":[0.167,0.167,0.167]},"n":["0p833_0p833_0p167_0p167","0p833_0p833_0p167_0p167","0p833_0p833_0p167_0p167"],"t":6,"s":[0,60,100],"e":[109.656,90,100]},{"i":{"x":[0.833,0.833,0.833],"y":[0.833,0.833,0.833]},"o":{"x":[0.167,0.167,0.167],"y":[0.167,0.167,0.167]},"n":["0p833_0p833_0p167_0p167","0p833_0p833_0p167_0p167","0p833_0p833_0p167_0p167"],"t":7,"s":[109.656,90,100],"e":[149.733,90.333,100]},{"i":{"x":[0.833,0.833,0.833],"y":[0.833,0.833,0.833]},"o":{"x":[0.167,0.167,0.167],"y":[0.167,0.167,0.167]},"n":["0p833_0p833_0p167_0p167","0p833_0p833_0p167_0p167","0p833_0p833_0p167_0p167"],"t":7.2,"s":[149.733,90.333,100],"e":[229.886,100,100]},{"i":{"x":[0.833,0.833,0.833],"y":[0.833,0.833,0.833]},"o":{"x":[0.167,0.167,0.167],"y":[0.167,0.167,0.167]},"n":["0p833_0p833_0p167_0p167","0p833_0p833_0p167_0p167","0p833_0p833_0p167_0p167"],"t":7.6,"s":[229.886,100,100],"e":[328.804,100,100]},{"i":{"x":[0.833,0.833,0.833],"y":[0.833,0.833,0.833]},"o":{"x":[0.167,0.167,0.167],"y":[0.167,0.167,0.167]},"n":["0p833_0p833_0p167_0p167","0p833_0p833_0p167_0p167","0p833_0p833_0p167_0p167"],"t":8.2,"s":[328.804,100,100],"e":[333.558,100,100]},{"i":{"x":[0.833,0.833,0.833],"y":[0.833,0.833,0.833]},"o":{"x":[0.167,0.167,0.167],"y":[0.167,0.167,0.167]},"n":["0p833_0p833_0p167_0p167","0p833_0p833_0p167_0p167","0p833_0p833_0p167_0p167"],"t":8.8,"s":[333.558,100,100],"e":[265.747,100,100]},{"i":{"x":[0.833,0.833,0.833],"y":[0.833,0.833,0.833]},"o":{"x":[0.167,0.167,0.167],"y":[0.167,0.167,0.167]},"n":["0p833_0p833_0p167_0p167","0p833_0p833_0p167_0p167","0p833_0p833_0p167_0p167"],"t":9,"s":[265.747,100,100],"e":[194.312,100,100]},{"i":{"x":[0.833,0.833,0.833],"y":[0.833,0.833,0.833]},"o":{"x":[0.167,0.167,0.167],"y":[0.167,0.167,0.167]},"n":["0p833_0p833_0p167_0p167","0p833_0p833_0p167_0p167","0p833_0p833_0p167_0p167"],"t":9.6,"s":[194.312,100,100],"e":[153.984,91,100]},{"i":{"x":[0.833,0.833,0.833],"y":[0.833,0.833,0.833]},"o":{"x":[0.167,0.167,0.167],"y":[0.167,0.167,0.167]},"n":["0p833_0p833_0p167_0p167","0p833_0p833_0p167_0p167","0p833_0p833_0p167_0p167"],"t":9.8,"s":[153.984,91,100],"e":[113.656,90,100]},{"i":{"x":[0.833,0.833,0.833],"y":[0.833,0.833,0.833]},"o":{"x":[0.167,0.167,0.167],"y":[0.167,0.167,0.167]},"n":["0p833_0p833_0p167_0p167","0p833_0p833_0p167_0p167","0p833_0p833_0p167_0p167"],"t":10,"s":[113.656,90,100],"e":[0,60,100]},{"t":11}],"ix":6}},"ao":0,"shapes":[{"ty":"gr","it":[{"ty":"rc","d":1,"s":{"a":0,"k":[12,99.002],"ix":2},"p":{"a":0,"k":[0,0],"ix":3},"r":{"a":0,"k":0,"ix":4},"nm":"Rectangle Path 1","mn":"ADBE Vector Shape - Rect","hd":false},{"ty":"st","c":{"a":0,"k":[0.099248998305,0.042310000401,0.256403006759,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":0,"ix":5},"lc":1,"lj":1,"ml":4,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"fl","c":{"a":0,"k":[0.964705882353,0.623529411765,0,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[-7.286,-4.499],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Rectangle 1","np":3,"cix":2,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":36,"st":0,"bm":0}]}],"layers":[{"ddd":0,"ind":1,"ty":4,"nm":"sparkle 4","sr":1,"ks":{"o":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"n":["0p833_0p833_0p167_0p167"],"t":10,"s":[100],"e":[0]},{"t":13}],"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[169.378,165.244,0],"ix":2},"a":{"a":0,"k":[35.5,28.5,0],"ix":1},"s":{"a":1,"k":[{"i":{"x":[0.667,0.667,0.667],"y":[1,1,1]},"o":{"x":[0.778,0.778,0.333],"y":[0,0,0]},"n":["0p667_1_0p778_0","0p667_1_0p778_0","0p667_1_0p333_0"],"t":8,"s":[0,0,100],"e":[80,80,100]},{"t":13}],"ix":6}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[1.519,-5.107],[10.475,10.309],[4.348,0.814],[-9.99,10.138],[-1.141,4.733],[-9.663,-9.806],[-4.721,-1.139],[9.479,-9.353]],"o":[[-0.807,-4.309],[-10.134,-9.974],[4.721,-1.139],[9.663,-9.806],[1.141,4.733],[9.99,10.138],[-5.092,1.515],[-9.8,9.67]],"v":[[35.5,68.5],[21.415,42.021],[-4.5,28.5],[21.415,13.852],[35.5,-11.5],[49.585,13.852],[75.5,28.5],[50.711,43.148]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"st","c":{"a":0,"k":[1,1,1,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"n":["0p833_0p833_0p167_0p167"],"t":8,"s":[5],"e":[9]},{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"n":["0p833_0p833_0p167_0p167"],"t":11,"s":[9],"e":[0]},{"t":13}],"ix":5},"lc":2,"lj":2,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"fl","c":{"a":0,"k":[1,1,1,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[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":"Transform"}],"nm":"null","np":3,"cix":2,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":5,"op":14,"st":-12,"bm":0},{"ddd":0,"ind":2,"ty":4,"nm":"sparkle 3","sr":1,"ks":{"o":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"n":["0p833_0p833_0p167_0p167"],"t":9,"s":[100],"e":[0]},{"t":12}],"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[235.5,228.5,0],"ix":2},"a":{"a":0,"k":[35.5,28.5,0],"ix":1},"s":{"a":1,"k":[{"i":{"x":[0.667,0.667,0.667],"y":[1,1,1]},"o":{"x":[0.778,0.778,0.333],"y":[0,0,0]},"n":["0p667_1_0p778_0","0p667_1_0p778_0","0p667_1_0p333_0"],"t":6,"s":[0,0,100],"e":[100,100,100]},{"t":11}],"ix":6}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[1.519,-5.107],[10.475,10.309],[4.348,0.814],[-9.99,10.138],[-1.141,4.733],[-9.663,-9.806],[-4.721,-1.139],[9.479,-9.353]],"o":[[-0.807,-4.309],[-10.134,-9.974],[4.721,-1.139],[9.663,-9.806],[1.141,4.733],[9.99,10.138],[-5.092,1.515],[-9.8,9.67]],"v":[[35.5,68.5],[21.415,42.021],[-4.5,28.5],[21.415,13.852],[35.5,-11.5],[49.585,13.852],[75.5,28.5],[50.711,43.148]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"fl","c":{"a":0,"k":[1,1,1,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"st","c":{"a":0,"k":[1,1,1,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"n":["0p833_0p833_0p167_0p167"],"t":6,"s":[5],"e":[9]},{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"n":["0p833_0p833_0p167_0p167"],"t":9,"s":[9],"e":[0]},{"t":11}],"ix":5},"lc":2,"lj":2,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","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":"Transform"}],"nm":"null","np":3,"cix":2,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":5,"op":14,"st":-12,"bm":0},{"ddd":0,"ind":3,"ty":0,"nm":"star_flip_4_final","refId":"comp_54","sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[200,200,0],"ix":2},"a":{"a":0,"k":[200,200,0],"ix":1},"s":{"a":1,"k":[{"i":{"x":[0.392,0.392,0.667],"y":[2.481,2.481,1]},"o":{"x":[0.597,0.597,0.333],"y":[0,0,0]},"n":["0p392_2p481_0p597_0","0p392_2p481_0p597_0","0p667_1_0p333_0"],"t":1,"s":[100,100,100],"e":[95,95,100]},{"i":{"x":[0.833,0.833,0.833],"y":[1,1,1]},"o":{"x":[0.179,0.179,0.333],"y":[0.095,0.095,0]},"n":["0p833_1_0p179_0p095","0p833_1_0p179_0p095","0p833_1_0p333_0"],"t":4,"s":[95,95,100],"e":[118,118,100]},{"i":{"x":[0.825,0.825,0.825],"y":[1,1,1]},"o":{"x":[0.161,0.161,0.161],"y":[0,0,0]},"n":["0p825_1_0p161_0","0p825_1_0p161_0","0p825_1_0p161_0"],"t":7,"s":[118,118,100],"e":[125,125,100]},{"i":{"x":[0.833,0.833,0.833],"y":[1,1,1]},"o":{"x":[0.167,0.167,0.167],"y":[0,0,0]},"n":["0p833_1_0p167_0","0p833_1_0p167_0","0p833_1_0p167_0"],"t":11,"s":[125,125,100],"e":[85,85,100]},{"i":{"x":[0.833,0.833,0.833],"y":[1,1,1]},"o":{"x":[0.167,0.167,0.167],"y":[0,0,0]},"n":["0p833_1_0p167_0","0p833_1_0p167_0","0p833_1_0p167_0"],"t":14,"s":[85,85,100],"e":[105,105,100]},{"i":{"x":[0.833,0.833,0.833],"y":[1,1,1]},"o":{"x":[0.167,0.167,0.167],"y":[0,0,0]},"n":["0p833_1_0p167_0","0p833_1_0p167_0","0p833_1_0p167_0"],"t":17,"s":[105,105,100],"e":[100,100,100]},{"t":20}],"ix":6}},"ao":0,"w":400,"h":400,"ip":0,"op":36,"st":0,"bm":0},{"ddd":0,"ind":4,"ty":4,"nm":"icon_circle","sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[200.826,199.69,0],"ix":2},"a":{"a":0,"k":[0.826,-0.31,0],"ix":1},"s":{"a":0,"k":[100,100,100],"ix":6}},"ao":0,"shapes":[{"ty":"gr","it":[{"ty":"gr","it":[{"ty":"gr","it":[{"ty":"gr","it":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0,-46.01],[45.9,-0.14],[0,0],[0.99,0.04],[0,45.01],[-46.01,0]],"o":[[0,45.93],[0,0],[-1,0],[-44.63,-1.57],[0,-46.01],[46.01,0]],"v":[[84.136,-0.31],[1.076,83],[0.826,83],[-2.164,82.94],[-82.484,-0.31],[0.826,-83.62]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"fl","c":{"a":0,"k":[1,0.956862747669,0.670588254929,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[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":"Transform"}],"nm":"null","np":2,"cix":2,"ix":1,"mn":"ADBE Vector Group","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":"Transform"}],"nm":"null","np":1,"cix":2,"ix":1,"mn":"ADBE Vector Group","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":"Transform"}],"nm":"null","np":1,"cix":2,"ix":1,"mn":"ADBE Vector Group","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":"Transform"}],"nm":"null","np":1,"cix":2,"ix":1,"mn":"ADBE Vector Group","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":"Transform"}],"nm":"null","np":1,"cix":2,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":36,"st":0,"bm":0}],"markers":[]}
\ No newline at end of file
diff --git a/packages/smooth_app/assets/icons/medal.svg b/packages/smooth_app/assets/icons/medal.svg
new file mode 100644
index 00000000000..eb9eb4e218f
--- /dev/null
+++ b/packages/smooth_app/assets/icons/medal.svg
@@ -0,0 +1,243 @@
+
+
diff --git a/packages/smooth_app/lib/data_models/user_preferences.dart b/packages/smooth_app/lib/data_models/user_preferences.dart
index 3cc4f9ef493..015f8f56e73 100644
--- a/packages/smooth_app/lib/data_models/user_preferences.dart
+++ b/packages/smooth_app/lib/data_models/user_preferences.dart
@@ -1,3 +1,5 @@
+import 'dart:math';
+
import 'package:flutter/material.dart';
import 'package:openfoodfacts/openfoodfacts.dart';
import 'package:shared_preferences/shared_preferences.dart';
@@ -57,7 +59,7 @@ class UserPreferences extends ChangeNotifier {
/// The current version of preferences (1)
static const String _TAG_VERSION = 'prefs_version';
- static const int _PREFS_CURRENT_VERSION = 1;
+ static const int _PREFS_CURRENT_VERSION = 2;
static const String _TAG_PREFIX_IMPORTANCE = 'IMPORTANCE_AS_STRING';
static const String _TAG_CURRENT_THEME_MODE = 'currentThemeMode';
static const String _TAG_CURRENT_COLOR_SCHEME = 'currentColorScheme';
@@ -70,6 +72,7 @@ class UserPreferences extends ChangeNotifier {
static const String _TAG_USER_TRACKING = 'user_tracking';
static const String _TAG_CRASH_REPORTS = 'crash_reports';
static const String _TAG_EXCLUDED_ATTRIBUTE_IDS = 'excluded_attributes';
+ static const String _TAG_USER_GROUP = '_user_group';
/// Camera preferences
// Detect if a first successful scan was achieved (condition to show the
@@ -108,6 +111,9 @@ class UserPreferences extends ChangeNotifier {
/// Allow to migrate between versions
Future _onMigrate() async {
final int? currentVersion = _sharedPreferences.getInt(_TAG_VERSION);
+ if (currentVersion == _PREFS_CURRENT_VERSION) {
+ return;
+ }
/// With version == null (or 0), [_TAG_USER_TRACKING] didn't exist
if (currentVersion == null) {
@@ -116,10 +122,20 @@ class UserPreferences extends ChangeNotifier {
if (crashReporting != null) {
await setUserTracking(crashReporting);
}
+ }
+ /// With version == null and 1, [_TAG_USER_GROUP] is missing
+ if (_sharedPreferences.getInt(_TAG_USER_GROUP) == null) {
await _sharedPreferences.setInt(
- _TAG_VERSION, UserPreferences._PREFS_CURRENT_VERSION);
+ _TAG_USER_GROUP,
+ Random().nextInt(10),
+ );
}
+
+ await _sharedPreferences.setInt(
+ _TAG_VERSION,
+ UserPreferences._PREFS_CURRENT_VERSION,
+ );
}
String _getImportanceTag(final String variable) =>
@@ -163,6 +179,9 @@ class UserPreferences extends ChangeNotifier {
bool get userTracking =>
_sharedPreferences.getBool(_TAG_USER_TRACKING) ?? false;
+ /// A random int between 0 and 10 (a naive implementation to allow A/B testing)
+ int get userGroup => _sharedPreferences.getInt(_TAG_USER_GROUP)!;
+
Future setCrashReports(final bool state) async {
await _sharedPreferences.setBool(_TAG_CRASH_REPORTS, state);
onCrashReportingChanged.value = state;
diff --git a/packages/smooth_app/lib/generic_lib/widgets/smooth_card.dart b/packages/smooth_app/lib/generic_lib/widgets/smooth_card.dart
index d7928bbdf67..735f74f6480 100644
--- a/packages/smooth_app/lib/generic_lib/widgets/smooth_card.dart
+++ b/packages/smooth_app/lib/generic_lib/widgets/smooth_card.dart
@@ -19,6 +19,7 @@ class SmoothCard extends StatelessWidget {
this.padding = const EdgeInsets.all(5.0),
this.elevation = 8,
this.borderRadius,
+ this.ignoreDefaultSemantics = false,
});
const SmoothCard.angular({
@@ -30,6 +31,7 @@ class SmoothCard extends StatelessWidget {
),
this.padding = const EdgeInsets.all(5.0),
this.elevation = 8,
+ this.ignoreDefaultSemantics = false,
}) : borderRadius = ANGULAR_BORDER_RADIUS;
const SmoothCard.flat({
@@ -44,6 +46,7 @@ class SmoothCard extends StatelessWidget {
this.padding = const EdgeInsets.all(5.0),
this.elevation = 0,
this.borderRadius,
+ this.ignoreDefaultSemantics = false,
});
final Widget child;
@@ -52,10 +55,24 @@ class SmoothCard extends StatelessWidget {
final EdgeInsetsGeometry? padding;
final BorderRadiusGeometry? borderRadius;
final double elevation;
+ final bool ignoreDefaultSemantics;
@override
Widget build(BuildContext context) {
- final Widget result = Material(
+ Widget result = Container(
+ padding: padding,
+ child: child,
+ );
+
+ if (ignoreDefaultSemantics) {
+ result = Semantics(
+ container: false,
+ explicitChildNodes: true,
+ child: child,
+ );
+ }
+
+ result = Material(
elevation: elevation,
shadowColor: const Color.fromARGB(25, 0, 0, 0),
borderRadius: borderRadius ?? ROUNDED_BORDER_RADIUS,
@@ -63,11 +80,9 @@ class SmoothCard extends StatelessWidget {
(Theme.of(context).brightness == Brightness.light
? Colors.white
: Colors.black),
- child: Container(
- padding: padding,
- child: child,
- ),
+ child: result,
);
+
return margin == null
? result
: Padding(
diff --git a/packages/smooth_app/lib/helpers/analytics_helper.dart b/packages/smooth_app/lib/helpers/analytics_helper.dart
index 20f45fb04ee..0b60e40b471 100644
--- a/packages/smooth_app/lib/helpers/analytics_helper.dart
+++ b/packages/smooth_app/lib/helpers/analytics_helper.dart
@@ -18,6 +18,7 @@ enum AnalyticsCategory {
productEdit(tag: 'product edit'),
productFastTrackEdit(tag: 'product fast track edit'),
newProduct(tag: 'new product'),
+ robotoff(tag: 'robotoff'),
list(tag: 'list'),
deepLink(tag: 'deep link');
@@ -105,12 +106,30 @@ enum AnalyticsEvent {
tag: 'closed new product page without any input',
category: AnalyticsCategory.newProduct,
),
- shareList(tag: 'shared a list', category: AnalyticsCategory.list),
- openListWeb(tag: 'open a list in wbe', category: AnalyticsCategory.list),
+ shareList(
+ tag: 'shared a list',
+ category: AnalyticsCategory.list,
+ ),
+ openListWeb(
+ tag: 'open a list in wbe',
+ category: AnalyticsCategory.list,
+ ),
productDeepLink(
- tag: 'open a product from an URL', category: AnalyticsCategory.deepLink),
+ tag: 'open a product from an URL',
+ category: AnalyticsCategory.deepLink,
+ ),
genericDeepLink(
- tag: 'generic deep link', category: AnalyticsCategory.deepLink);
+ tag: 'generic deep link',
+ category: AnalyticsCategory.deepLink,
+ ),
+ questionVisible(
+ tag: 'question visible',
+ category: AnalyticsCategory.robotoff,
+ ),
+ questionClicked(
+ tag: 'question clicked',
+ category: AnalyticsCategory.robotoff,
+ );
const AnalyticsEvent({required this.tag, required this.category});
diff --git a/packages/smooth_app/lib/l10n/app_en.arb b/packages/smooth_app/lib/l10n/app_en.arb
index 349a366a0cd..864d25ddbc0 100644
--- a/packages/smooth_app/lib/l10n/app_en.arb
+++ b/packages/smooth_app/lib/l10n/app_en.arb
@@ -121,13 +121,13 @@
"@Introduction screen": {},
"welcomeToOpenFoodFacts": "Welcome to Open Food Facts",
"@welcomeToOpenFoodFacts": {},
- "whatIsOff": "Open Food Facts is a global non-profit powered by local communities.",
+ "whatIsOff": "Open Food Facts is a global non-profit powered by local communities.",
"@whatIsOff": {
- "description": "Description of Open Food Facts organization."
+ "description": "Description of Open Food Facts organization."
},
"offUtility": "Choose food that is good for you and the planet.",
"@offUtility": {
- "description": "Description of what a user can use Open Food Facts for."
+ "description": "Description of what a user can use Open Food Facts for."
},
"productDataUtility": "See the food data relevant to your preferences.",
"@productDataUtility": {
@@ -142,7 +142,7 @@
"description": "Description of what a user can use the Eco data in a product for."
},
"@user_management": {},
- "sign_in_text": "Sign in to your Open Food Facts account to save your contributions",
+ "sign_in_text": "Sign in to your Open Food Facts account to save your contributions",
"incorrect_credentials": "Incorrect username or password.",
"login": "Login",
"@login": {
@@ -233,9 +233,9 @@
"sign_up_page_confirm_password_hint": "Confirm Password",
"sign_up_page_confirm_password_error_empty": "Please confirm the password",
"sign_up_page_confirm_password_error_invalid": "Passwords don't match",
- "sign_up_page_agree_text": "I agree to the Open Food Facts",
+ "sign_up_page_agree_text": "I agree to the Open Food Facts",
"@sign_up_page_agree_text": {
- "description": "I agree to the Open Food Facts is followed by sign_up_page_terms_text"
+ "description": "I agree to the Open Food Facts is followed by sign_up_page_terms_text"
},
"sign_up_page_terms_text": "terms of use and contribution",
"@sign_up_page_terms_text": {
@@ -243,7 +243,7 @@
},
"sign_up_page_agree_url": "https://world-en.openfoodfacts.org/terms-of-use",
"@sign_up_page_agree_url": {
- "description": "Please insert the right url here. Go to the Open Food Facts homepage, switch to your country and then on the bottom left footer is Terms of use from which the url should be taken"
+ "description": "Please insert the right url here. Go to the Open Food Facts homepage, switch to your country and then on the bottom left footer is Terms of use from which the url should be taken"
},
"donate_url": "https://donate.openfoodfacts.org/",
"@donate_url": {
@@ -256,7 +256,7 @@
"sign_up_page_producer_checkbox": "I am a food producer",
"sign_up_page_producer_hint": "Producer/brand",
"sign_up_page_producer_error_empty": "Please enter a producer or a brand name",
- "sign_up_page_subscribe_checkbox": "I'd like to subscribe to the Open Food Facts newsletter (You can unsubscribe from it at any time)",
+ "sign_up_page_subscribe_checkbox": "I'd like to subscribe to the Open Food Facts newsletter (You can unsubscribe from it at any time)",
"sign_up_page_user_name_already_used": "The user name already exists, please choose another username.",
"sign_up_page_email_already_exists": "already exists, login to the account or try with another email.",
"sign_up_page_provide_valid_email": "Please provide a valid email address.",
@@ -281,7 +281,7 @@
"@darkmode_system_default": {
"description": "Indicator inside the darkmode switch (system default)"
},
- "thanks_for_contributing": "Thanks for contributing",
+ "thanks_for_contributing": "Thanks for contributing!",
"@contributors": {
"description": "Button label: Opens a pop up window where all contributors of this app are shown"
},
@@ -310,13 +310,13 @@
"@contribute_sw_development": {
"description": "Button label + page title: Ways to help"
},
- "contribute_develop_text": "The code for every Open Food Facts product is available on GitHub. You are welcome to reuse the code (it's open source) and help us improve it, for everyone, on all the planet.",
+ "contribute_develop_text": "The code for every Open Food Facts product is available on GitHub. You are welcome to reuse the code (it's open source) and help us improve it, for everyone, on all the planet.",
"@contribute_develop_text": {},
- "contribute_develop_text_2": "You can join the Open Food Facts Slack chatroom which is the preferred way to ask questions.",
+ "contribute_develop_text_2": "You can join the Open Food Facts Slack chatroom which is the preferred way to ask questions.",
"@contribute_develop_text_2": {},
"contribute_develop_dev_mode_title": "DEV Mode?",
"contribute_develop_dev_mode_subtitle": "Activate the DEV Mode",
- "contribute_donate_header": "Donate to Open Food Facts",
+ "contribute_donate_header": "Donate to Open Food Facts",
"@contribute_donate_header": {},
"contribute_improve_ProductsToBeCompleted": "Products to be completed",
"@contribute_improve_ProductsToBeCompleted": {
@@ -326,7 +326,7 @@
"@contribute_improve_header": {
"description": "Button label + page title: Ways to improve the database"
},
- "contribute_improve_text": "The database is the core of the project. It's easy and very quick to help. You can download the mobile app for your phone, and start adding or improving products.\n\nOn the other hand, Open Food Facts website offers many ways to contribute: ",
+ "contribute_improve_text": "The database is the core of the project. It's easy and very quick to help. You can download the mobile app for your phone, and start adding or improving products.\n\nOn the other hand, Open Food Facts website offers many ways to contribute: ",
"@contribute_improve_text": {},
"contribute_translate_header": "Translate",
"@contribute_translate_header": {
@@ -336,19 +336,19 @@
"@contribute_translate_link_text": {
"description": "Button label: Opens the Crowdin translation portal"
},
- "contribute_translate_text": "Open Food Facts is a global project, containing products from more than 160 countries. Open Food Facts is translated into dozens of languages, with constantly evolving content.",
+ "contribute_translate_text": "Open Food Facts is a global project, containing products from more than 160 countries. Open Food Facts is translated into dozens of languages, with constantly evolving content.",
"@contribute_translate_text": {},
"contribute_translate_text_2": "Translations is one of the key tasks of the project",
"@contribute_translate_text_2": {},
- "contribute_share_header": "Share Open Food Facts with your friends",
+ "contribute_share_header": "Share Open Food Facts with your friends",
"@contribute_share_header": {},
- "contribute_share_content": "I wanted to let you know about the app I've been using, Open Food Facts, which allows you to get the health and environmental impacts of your food, in a personalized way. It works by scanning the barcodes on the packaging. Finally it's free, does not require registration, and you can even help increase the number of products decyphered. Here's the link to get it for your phone: https://openfoodfacts.app",
+ "contribute_share_content": "I wanted to let you know about the app I've been using, Open Food Facts, which allows you to get the health and environmental impacts of your food, in a personalized way. It works by scanning the barcodes on the packaging. Finally it's free, does not require registration, and you can even help increase the number of products decyphered. Here's the link to get it for your phone: https://openfoodfacts.app",
"@contribute_share_content": {
"description": "Content that will be shared, don't forget to include the URL"
},
"tap_to_answer": "Tap here to answer questions",
"@tap_to_answer": {
- "description": "Button label shown on a product, clicking the button opens a card with unanswered product questions, users can answer these to contribute to Open food facts and gain rewards."
+ "description": "Button label shown on a product, clicking the button opens a card with unanswered product questions, users can answer these to contribute to Open Food Facts and gain rewards."
},
"tap_to_answer_hint": "Tap here to answer questions about this product",
"@tap_to_answer_hint": {
@@ -364,9 +364,9 @@
},
"contribute_to_get_rewards": "Help improve food transparency and get rewards",
"@contribute_to_get_rewards": {
- "description": "Button description shown on a product, clicking the button opens a card with unanswered product questions, users can answer these to contribute to Open food facts and gain rewards."
+ "description": "Button description shown on a product, clicking the button opens a card with unanswered product questions, users can answer these to contribute to Open Food Facts and gain rewards."
},
- "question_sign_in_text": "Sign in to your Open Food Facts account to get credit for your contributions",
+ "question_sign_in_text": "Sign in to your Open Food Facts account to get credit for your contributions",
"question_yes_button_accessibility_value": "Answer with yes",
"question_no_button_accessibility_value": "Answer with no",
"question_skip_button_accessibility_value": "Skip this question",
@@ -376,7 +376,7 @@
"@myPreferences": {
"description": "Page title: Page where the ranking preferences can be changed"
},
- "account_create_message": "Create your account and join the Open Food Facts community to help build food knowledge all over the world!",
+ "account_create_message": "Create your account and join the Open Food Facts community to help build food knowledge all over the world!",
"@account_create_message": {
"description": "The Message to be displayed if the user does not have an account and wants to contribute"
},
@@ -385,12 +385,12 @@
"description": "Join which is actually Signup"
},
"myPreferences_profile_title": "Your Profile",
- "myPreferences_profile_subtitle": "Manage your Open Food Facts contributor account.",
+ "myPreferences_profile_subtitle": "Manage your Open Food Facts contributor account.",
"myPreferences_settings_title": "App Settings",
"myPreferences_settings_subtitle": "Dark mode, Analytics…",
"myPreferences_food_title": "Food Preferences",
"myPreferences_food_subtitle": "Choose what information about food matters most to you.",
- "myPreferences_food_comment": "Choose what information about food matters most to you, in order to rank food according to your preferences, see the information you care about first, and get a compatibility summary. Those food preferences stay on your device, and are not associated with your Open Food Facts contributor account if you have one.",
+ "myPreferences_food_comment": "Choose what information about food matters most to you, in order to rank food according to your preferences, see the information you care about first, and get a compatibility summary. Those food preferences stay on your device, and are not associated with your Open Food Facts contributor account if you have one.",
"confirmResetPreferences": "Reset your food preferences?",
"@confirmResetPreferences": {
"description": "Pop up title: Reassuring if the food preferences should really be reset"
@@ -498,7 +498,7 @@
"@packaging_information_photo": {},
"missing_product": "You found a new product!",
"@missing_product": {},
- "add_product_take_photos": "Take photos of the packaging to add this product to Open Food Facts",
+ "add_product_take_photos": "Take photos of the packaging to add this product to Open Food Facts",
"@add_product_take_photos": {},
"add_product_take_photos_descriptive": "Please take some photos first. You may always complete the product at a later time.",
"@add_product_take_photos_descriptive": {},
@@ -622,27 +622,27 @@
"@uploading_image": {
"description": "Message when a new picture is uploading to the server"
},
- "uploading_image_type_front": "Uploading front image to Open Food Facts",
+ "uploading_image_type_front": "Uploading front image to Open Food Facts",
"@uploading_image_type_front": {
"description": "Message when a new front picture is being uploaded to the server"
},
- "uploading_image_type_ingredients": "Uploading ingredients image to Open Food Facts",
+ "uploading_image_type_ingredients": "Uploading ingredients image to Open Food Facts",
"@uploading_image_type_ingredients": {
"description": "Message when a new ingredients picture is being uploaded to the server"
},
- "uploading_image_type_nutrition": "Uploading nutrition image to Open Food Facts",
+ "uploading_image_type_nutrition": "Uploading nutrition image to Open Food Facts",
"@uploading_image_type_nutrition": {
"description": "Message when a new nutrition picture is being uploaded to the server"
},
- "uploading_image_type_packaging": "Uploading packaging image to Open Food Facts",
+ "uploading_image_type_packaging": "Uploading packaging image to Open Food Facts",
"@uploading_image_type_packaging": {
"description": "Message when a new packaging picture is being uploaded to the server"
},
- "uploading_image_type_other": "Uploading other image to Open Food Facts",
+ "uploading_image_type_other": "Uploading other image to Open Food Facts",
"@uploading_image_type_other": {
"description": "Message when a new other picture is being uploaded to the server"
},
- "uploading_image_type_generic": "Uploading image to Open Food Facts",
+ "uploading_image_type_generic": "Uploading image to Open Food Facts",
"@uploading_image_type_generic": {
"description": "Message when a new picture is being uploaded to the server"
},
@@ -752,7 +752,7 @@
"@native_app_settings": {
"description": "Native App Settings in app settings"
},
- "native_app_description": "Open system settings for Open Food Facts",
+ "native_app_description": "Open systems settings for Open Food Facts",
"@native_app_description": {
"description": "Native App description in app settings"
},
@@ -857,7 +857,7 @@
"@consent_analytics_title": {
"description": "Title for the consent analytics UI Page"
},
- "consent_analytics_body1": "Help the Open Food Facts volunteers to improve the app. You decide whether to submit anonymous analytics.",
+ "consent_analytics_body1": "Help the Open Food Facts volunteers to improve the app. You decide whether to submit anonymous analytics.",
"@conset_analytics_body1": {
"description": "first paragraph for the consent analytics UI Page"
},
@@ -1042,8 +1042,8 @@
"@user_profile_title_guest": {
"description": "When the user is not connected"
},
- "user_profile_subtitle_guest": "Sign-in or sign-up to join the Open Food Facts community",
- "user_profile_title_id_email": "Open Food Facts login: {email}",
+ "user_profile_subtitle_guest": "Sign-in or sign-up to join the Open Food Facts community",
+ "user_profile_title_id_email": "Open Food Facts login: {email}",
"@user_profile_title_id_email": {
"description": "User login (when it's an email)",
"placeholders": {
@@ -1065,7 +1065,7 @@
"@email_subject_account_deletion": {
"description": "Email subject for an account deletion"
},
- "email_body_account_deletion": "Hi there, please delete my Open Food Facts account: {userId}",
+ "email_body_account_deletion": "Hi there, please delete my Open Food Facts account: {userId}",
"@email_body_account_deletion": {
"description": "Email body for an account deletion",
"placeholders": {
@@ -1102,7 +1102,7 @@
"@crash_reporting_toggle_title": {
"description": "Title for the Crash reporting toggle"
},
- "crash_reporting_toggle_subtitle": "When enabled, crash reports are automatically submitted to Open Food Facts' error tracking system, so that bugs can be fixed and thus improve the app.",
+ "crash_reporting_toggle_subtitle": "When enabled, crash reports are automatically submitted to Open Food Facts' error tracking system, so that bugs can be fixed and thus improve the app.",
"@crash_reporting_toggle_subtitle": {
"description": "SubTitle for the Crash reporting toggle"
},
@@ -1110,7 +1110,7 @@
"@send_anonymous_toggle_title": {
"description": "Title for the Send anonymous data toggle"
},
- "send_anonymous_data_toggle_subtitle": "When enabled, some anonymous information regarding app usage will be sent to the Open Food Facts servers, so that we can understand how and how much features are used in order to improve them.",
+ "send_anonymous_data_toggle_subtitle": "When enabled, some anonymous information regarding app usage will be sent to the Open Food Facts servers, so that we can understand how and how much features are used in order to improve them.",
"@send_anonymous_toggle_subtitle": {
"description": "SubTitle for the Send anonymous data toggle"
},
@@ -1285,7 +1285,7 @@
"@edit_product_form_item_categories_explainer_1": {
"description": "Product edition - Categories - input explainer, part 1"
},
- "edit_product_form_item_categories_explainer_2": "In case a category is not available in autocomplete, feel free to add it anyway, that will help us improve Open Food Facts in your country.",
+ "edit_product_form_item_categories_explainer_2": "In case a category is not available in autocomplete, feel free to add it anyway, that will help us improve Open Food Facts in your country.",
"@edit_product_form_item_categories_explainer_2": {
"description": "Product edition - Categories - input explainer, part 2"
},
@@ -1652,7 +1652,7 @@
"@edit_product_action_confirm": {
"description": "Product edition - FAB actions - confirm"
},
- "signup_page_terms_of_use_line1": "I agree to the Open Food Facts ",
+ "signup_page_terms_of_use_line1": "I agree to the Open Food Facts ",
"@signup_page_terms_of_use_line1": {
"description": "User consent for terms of use (line 1)"
},
@@ -1844,7 +1844,7 @@
"@share": {
"description": "Button label for sharing something on another app. For example sharing the link to a product via Email"
},
- "share_product_text": "Have a look at this product on Open Food Facts: {url}",
+ "share_product_text": "Have a look at this product on Open Food Facts: {url}",
"@share_product_text": {
"description": "The content which is send, when sharing a product",
"placeholders": {
@@ -1853,7 +1853,7 @@
}
}
},
- "share_product_list_text": "Have a look at my list of products on Open Food Facts: {url}",
+ "share_product_list_text": "Have a look at my list of products on Open Food Facts: {url}",
"@share_product_list_text": {
"description": "The content which is send, when sharing a product list",
"placeholders": {
@@ -2200,7 +2200,7 @@
},
"color_light_brown": "Default",
"@color_light_brown": {
- "description": "Color Light Brown, Default Open Food Facts Color"
+ "description": "Color Light Brown, Default Open Food Facts Color"
},
"color_magenta": "Magenta",
"@color_magenta": {
@@ -2304,7 +2304,7 @@
"@update_offline_data": {
"description": "List tile title for the update offline data page"
},
- "update_local_database_sub": "Update the local product database with the latest data from Open Food Facts",
+ "update_local_database_sub": "Update the local product database with the latest data from Open Food Facts",
"@update_local_database_sub": {
"description": "Update the local product database with the latest data from server"
},
diff --git a/packages/smooth_app/lib/pages/hunger_games/congrats.dart b/packages/smooth_app/lib/pages/hunger_games/congrats.dart
index a1f5cf15315..c2483874e36 100644
--- a/packages/smooth_app/lib/pages/hunger_games/congrats.dart
+++ b/packages/smooth_app/lib/pages/hunger_games/congrats.dart
@@ -1,12 +1,16 @@
+import 'dart:math' as math;
+
import 'package:flutter/material.dart';
import 'package:flutter_gen/gen_l10n/app_localizations.dart';
import 'package:openfoodfacts/openfoodfacts.dart';
import 'package:provider/provider.dart';
+import 'package:rive/rive.dart';
import 'package:smooth_app/data_models/user_management_provider.dart';
import 'package:smooth_app/generic_lib/buttons/smooth_simple_button.dart';
import 'package:smooth_app/generic_lib/design_constants.dart';
import 'package:smooth_app/generic_lib/dialogs/smooth_alert_dialog.dart';
import 'package:smooth_app/generic_lib/loading_dialog.dart';
+import 'package:smooth_app/generic_lib/widgets/smooth_card.dart';
import 'package:smooth_app/pages/user_management/login_page.dart';
class CongratsWidget extends StatelessWidget {
@@ -26,56 +30,53 @@ class CongratsWidget extends StatelessWidget {
final UserManagementProvider userManagementProvider =
context.watch();
- final Brightness brightness = Theme.of(context).brightness;
- final bool isDarkMode = brightness == Brightness.dark;
-
return Center(
- child: Column(
- mainAxisAlignment: MainAxisAlignment.center,
- children: [
- const Icon(
- Icons.grade,
- color: Colors.amber,
- size: 70,
- ),
- Padding(
- padding: const EdgeInsets.symmetric(vertical: MEDIUM_SPACE),
- child: Text(
- appLocalizations.thanks_for_contributing,
- style: Theme.of(context).textTheme.bodyLarge,
- ),
+ child: SmoothCard(
+ ignoreDefaultSemantics: true,
+ child: Padding(
+ padding: const EdgeInsetsDirectional.symmetric(
+ horizontal: MEDIUM_SPACE,
),
- FutureBuilder(
- future: userManagementProvider.credentialsInStorage(),
- builder: (BuildContext context, AsyncSnapshot snapshot) {
- if (!snapshot.hasData) {
- return EMPTY_WIDGET;
- }
- final bool isUserLoggedIn = snapshot.data!;
- if (isUserLoggedIn) {
- // TODO(jasmeet): Show leaderboard button.
- return EMPTY_WIDGET;
- } else {
- return _buildSignInButton(context, appLocalizations);
- }
- }),
- if (continueButtonLabel != null)
- SmoothSimpleButton(
- onPressed: onContinue,
- child: Text(continueButtonLabel!),
- )
- else
- EMPTY_WIDGET,
- TextButton(
- child: Text(
- appLocalizations.close,
- style: Theme.of(context).textTheme.bodyLarge!.apply(
- color: isDarkMode ? Colors.white : Colors.black,
- ),
- ),
- onPressed: () => Navigator.maybePop(context),
+ child: Column(
+ mainAxisAlignment: MainAxisAlignment.center,
+ mainAxisSize: MainAxisSize.min,
+ children: [
+ const Padding(
+ padding: EdgeInsetsDirectional.only(top: SMALL_SPACE),
+ child: _Header(),
+ ),
+ FractionallySizedBox(
+ child: FutureBuilder(
+ future: userManagementProvider.credentialsInStorage(),
+ builder:
+ (BuildContext context, AsyncSnapshot snapshot) {
+ if (!snapshot.hasData) {
+ return EMPTY_WIDGET;
+ }
+ final bool isUserLoggedIn = snapshot.data!;
+ if (isUserLoggedIn) {
+ // TODO(jasmeet): Show leaderboard button.
+ return EMPTY_WIDGET;
+ } else {
+ return _buildSignInButton(context, appLocalizations);
+ }
+ }),
+ ),
+ if (continueButtonLabel != null)
+ SmoothSimpleButton(
+ onPressed: onContinue,
+ child: Text(continueButtonLabel!),
+ ),
+ Align(
+ alignment: AlignmentDirectional.bottomEnd,
+ child: SmoothSimpleButton(
+ child: Text(appLocalizations.close),
+ onPressed: () => Navigator.maybePop(context),
+ ),
+ ),
+ ],
),
- ],
+ ),
),
);
}
@@ -86,37 +87,48 @@ class CongratsWidget extends StatelessWidget {
) {
return Column(
children: [
- SmoothActionButtonsBar.single(
- action: SmoothActionButton(
- text: appLocalizations.sign_in,
- onPressed: () async {
- await Navigator.push(
- context,
- MaterialPageRoute(
- builder: (_) => const LoginPage(),
- ),
- );
- if (OpenFoodAPIConfiguration.globalUser != null) {
- // ignore: use_build_context_synchronously
- LoadingDialog.run(
- context: context,
- title: appLocalizations.saving_answer,
- future: _postInsightAnnotations(
- anonymousAnnotationList,
- ),
- );
- }
- },
+ const SizedBox(height: MEDIUM_SPACE),
+ Semantics(
+ value: appLocalizations.question_sign_in_text,
+ button: true,
+ excludeSemantics: true,
+ container: true,
+ child: FractionallySizedBox(
+ widthFactor: 0.6,
+ child: SmoothActionButtonsBar.single(
+ action: SmoothActionButton(
+ text: appLocalizations.sign_in,
+ onPressed: () async {
+ await Navigator.push(
+ context,
+ MaterialPageRoute(
+ builder: (_) => const LoginPage(),
+ ),
+ );
+ if (OpenFoodAPIConfiguration.globalUser != null) {
+ // ignore: use_build_context_synchronously
+ LoadingDialog.run(
+ context: context,
+ title: appLocalizations.saving_answer,
+ future: _postInsightAnnotations(
+ anonymousAnnotationList,
+ ),
+ );
+ }
+ },
+ ),
+ ),
),
),
- Padding(
- padding: const EdgeInsets.symmetric(vertical: MEDIUM_SPACE),
+ const SizedBox(height: MEDIUM_SPACE),
+ ExcludeSemantics(
child: Text(
appLocalizations.question_sign_in_text,
style: Theme.of(context).textTheme.bodyMedium,
textAlign: TextAlign.center,
),
),
+ const SizedBox(height: MEDIUM_SPACE * 3),
],
);
}
@@ -140,3 +152,48 @@ class CongratsWidget extends StatelessWidget {
return results;
}
}
+
+class _Header extends StatelessWidget {
+ const _Header();
+
+ @override
+ Widget build(BuildContext context) {
+ final AppLocalizations appLocalizations = AppLocalizations.of(context);
+ final double multiplier =
+ math.min(350, MediaQuery.of(context).size.height * 0.3) / 235;
+
+ return Semantics(
+ enabled: true,
+ container: true,
+ value: appLocalizations.thanks_for_contributing,
+ excludeSemantics: true,
+ child: Column(
+ children: [
+ Padding(
+ padding: const EdgeInsetsDirectional.symmetric(
+ vertical: SMALL_SPACE,
+ ),
+ child: SizedBox(
+ width: 230 * multiplier,
+ height: 235 * multiplier,
+ child: const RiveAnimation.asset(
+ 'assets/animations/off.riv',
+ artboard: 'Success',
+ stateMachines: ['Animation'],
+ ),
+ ),
+ ),
+ Padding(
+ padding: const EdgeInsetsDirectional.symmetric(
+ vertical: MEDIUM_SPACE,
+ ),
+ child: Text(
+ appLocalizations.thanks_for_contributing,
+ style: Theme.of(context).textTheme.titleLarge,
+ ),
+ ),
+ ],
+ ),
+ );
+ }
+}
diff --git a/packages/smooth_app/lib/pages/preferences/user_preferences_dev_debug_info.dart b/packages/smooth_app/lib/pages/preferences/user_preferences_dev_debug_info.dart
index d944e77cfe9..484539ae51b 100644
--- a/packages/smooth_app/lib/pages/preferences/user_preferences_dev_debug_info.dart
+++ b/packages/smooth_app/lib/pages/preferences/user_preferences_dev_debug_info.dart
@@ -3,6 +3,8 @@ import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:openfoodfacts/openfoodfacts.dart';
import 'package:package_info_plus/package_info_plus.dart';
+import 'package:provider/provider.dart';
+import 'package:smooth_app/data_models/user_preferences.dart';
import 'package:smooth_app/helpers/analytics_helper.dart';
import 'package:smooth_app/helpers/global_vars.dart';
import 'package:smooth_app/query/product_query.dart';
@@ -32,6 +34,11 @@ class _UserPreferencesDebugInfoState extends State {
// TODO(m123): Add sentry id https://github.com/getsentry/sentry-dart/issues/1205
Future loadAsyncData() async {
+ infos.putIfAbsent(
+ 'User group',
+ () => context.read().userGroup,
+ );
+
final BaseDeviceInfo deviceInfo = await DeviceInfoPlugin().deviceInfo;
if (deviceInfo is AndroidDeviceInfo) {
diff --git a/packages/smooth_app/lib/pages/product/new_product_page.dart b/packages/smooth_app/lib/pages/product/new_product_page.dart
index 8e427417a65..e684827f575 100644
--- a/packages/smooth_app/lib/pages/product/new_product_page.dart
+++ b/packages/smooth_app/lib/pages/product/new_product_page.dart
@@ -11,6 +11,7 @@ import 'package:smooth_app/cards/product_cards/product_image_carousel.dart';
import 'package:smooth_app/data_models/product_list.dart';
import 'package:smooth_app/data_models/product_preferences.dart';
import 'package:smooth_app/data_models/up_to_date_mixin.dart';
+import 'package:smooth_app/data_models/user_preferences.dart';
import 'package:smooth_app/database/dao_product_list.dart';
import 'package:smooth_app/database/local_database.dart';
import 'package:smooth_app/generic_lib/design_constants.dart';
@@ -32,6 +33,7 @@ import 'package:smooth_app/pages/product_list_user_dialog_helper.dart';
import 'package:smooth_app/query/product_query.dart';
import 'package:smooth_app/themes/constant_icons.dart';
import 'package:smooth_app/widgets/smooth_scaffold.dart';
+import 'package:smooth_app/widgets/widget_height.dart';
class ProductPage extends StatefulWidget {
const ProductPage(
@@ -56,9 +58,11 @@ class _ProductPageState extends State
final ScrollController _carouselController = ScrollController();
late ProductPreferences _productPreferences;
+ late ProductQuestionsLayout questionsLayout;
bool _keepRobotoffQuestionsAlive = true;
bool scrollingUp = true;
+ double bottomPadding = 0.0;
@override
String get traceName => 'Opened product_page';
@@ -70,6 +74,7 @@ class _ProductPageState extends State
void initState() {
super.initState();
initUpToDate(widget.product, context.read());
+ questionsLayout = getUserQuestionsLayout(context.read());
WidgetsBinding.instance.addPostFrameCallback((_) {
_updateLocalDatabaseWithProductHistory(context);
});
@@ -85,47 +90,69 @@ class _ProductPageState extends State
context.watch();
refreshUpToDate();
- return SmoothScaffold(
- contentBehindStatusBar: true,
- spaceBehindStatusBar: false,
- statusBarBackgroundColor: SmoothScaffold.semiTranslucentStatusBar,
- body: Stack(
- children: [
- NotificationListener(
- onNotification: (UserScrollNotification notification) {
- if (notification.direction == ScrollDirection.forward) {
- if (!scrollingUp) {
- setState(() => scrollingUp = true);
- }
- } else if (notification.direction == ScrollDirection.reverse) {
- if (scrollingUp) {
- setState(() => scrollingUp = false);
+ return Provider.value(
+ value: upToDateProduct,
+ child: SmoothScaffold(
+ contentBehindStatusBar: true,
+ spaceBehindStatusBar: false,
+ statusBarBackgroundColor: SmoothScaffold.semiTranslucentStatusBar,
+ body: Stack(
+ children: [
+ NotificationListener(
+ onNotification: (UserScrollNotification notification) {
+ if (notification.direction == ScrollDirection.forward) {
+ if (!scrollingUp) {
+ setState(() => scrollingUp = true);
+ }
+ } else if (notification.direction == ScrollDirection.reverse) {
+ if (scrollingUp) {
+ setState(() => scrollingUp = false);
+ }
}
- }
- return true;
- },
- child: _buildProductBody(context),
- ),
- Padding(
- padding: const EdgeInsetsDirectional.only(start: SMALL_SPACE),
- child: SafeArea(
- child: AnimatedContainer(
- duration: SmoothAnimationsDuration.short,
- width: kToolbarHeight,
- height: kToolbarHeight,
- decoration: BoxDecoration(
- color:
- scrollingUp ? themeData.primaryColor : Colors.transparent,
- shape: BoxShape.circle,
- ),
- child: Offstage(
- offstage: !scrollingUp,
- child: const SmoothBackButton(iconColor: Colors.white),
+ return true;
+ },
+ child: _buildProductBody(context),
+ ),
+ Padding(
+ padding: const EdgeInsetsDirectional.only(start: SMALL_SPACE),
+ child: SafeArea(
+ child: AnimatedContainer(
+ duration: SmoothAnimationsDuration.short,
+ width: kToolbarHeight,
+ height: kToolbarHeight,
+ decoration: BoxDecoration(
+ color: scrollingUp
+ ? themeData.primaryColor
+ : Colors.transparent,
+ shape: BoxShape.circle,
+ ),
+ child: Offstage(
+ offstage: !scrollingUp,
+ child: const SmoothBackButton(iconColor: Colors.white),
+ ),
),
),
),
- )
- ],
+ if (questionsLayout == ProductQuestionsLayout.banner)
+ Positioned.directional(
+ start: 0.0,
+ end: 0.0,
+ bottom: 0.0,
+ textDirection: Directionality.of(context),
+ child: MeasureSize(
+ onChange: (Size size) {
+ if (size.height != bottomPadding) {
+ setState(() => bottomPadding = size.height);
+ }
+ },
+ child: ProductQuestionsWidget(
+ upToDateProduct,
+ layout: ProductQuestionsLayout.banner,
+ ),
+ ),
+ ),
+ ],
+ ),
),
);
}
@@ -214,61 +241,12 @@ class _ProductPageState extends State
_buildKnowledgePanelCards(),
if (upToDateProduct.website != null &&
upToDateProduct.website!.trim().isNotEmpty)
- _buildWebsiteWidget(upToDateProduct.website!.trim()),
+ const _WebsiteCard(),
],
),
);
}
- Widget _buildWebsiteWidget(String website) => buildProductSmoothCard(
- body: InkWell(
- onTap: () async {
- if (!website.startsWith('http')) {
- website = 'http://$website';
- }
- LaunchUrlHelper.launchURL(website, false);
- },
- borderRadius: ROUNDED_BORDER_RADIUS,
- child: Container(
- width: double.infinity,
- padding: const EdgeInsetsDirectional.only(
- start: LARGE_SPACE,
- top: LARGE_SPACE,
- bottom: LARGE_SPACE,
- // To be perfectly aligned with arrows
- end: 21.0,
- ),
- child: Row(
- children: [
- Expanded(
- child: Column(
- crossAxisAlignment: CrossAxisAlignment.start,
- mainAxisAlignment: MainAxisAlignment.start,
- children: [
- Text(
- AppLocalizations.of(context)
- .product_field_website_title,
- style: Theme.of(context).textTheme.displaySmall,
- ),
- const SizedBox(height: SMALL_SPACE),
- Text(
- website,
- overflow: TextOverflow.ellipsis,
- style: Theme.of(context)
- .textTheme
- .bodyMedium
- ?.copyWith(color: Colors.blue),
- ),
- ],
- ),
- ),
- const Icon(Icons.open_in_new),
- ],
- ),
- ),
- ),
- );
-
Widget _buildKnowledgePanelCards() {
final List knowledgePanelWidgets = [];
if (upToDateProduct.knowledgePanels != null) {
@@ -504,3 +482,70 @@ class _ProductPageState extends State
);
}
}
+
+class _WebsiteCard extends StatelessWidget {
+ const _WebsiteCard();
+
+ @override
+ Widget build(BuildContext context) {
+ final String website = _getWebsite(context);
+
+ return buildProductSmoothCard(
+ body: InkWell(
+ onTap: () => LaunchUrlHelper.launchURL(website, false),
+ borderRadius: ROUNDED_BORDER_RADIUS,
+ child: Container(
+ width: double.infinity,
+ padding: const EdgeInsetsDirectional.only(
+ start: LARGE_SPACE,
+ top: LARGE_SPACE,
+ bottom: LARGE_SPACE,
+ // To be perfectly aligned with arrows
+ end: 21.0,
+ ),
+ child: Row(
+ children: [
+ Expanded(
+ child: Column(
+ crossAxisAlignment: CrossAxisAlignment.start,
+ mainAxisAlignment: MainAxisAlignment.start,
+ children: [
+ Text(
+ AppLocalizations.of(context)
+ .product_field_website_title,
+ style: Theme.of(context).textTheme.displaySmall,
+ ),
+ const SizedBox(height: SMALL_SPACE),
+ Text(
+ website,
+ overflow: TextOverflow.ellipsis,
+ style: Theme.of(context)
+ .textTheme
+ .bodyMedium
+ ?.copyWith(color: Colors.blue),
+ ),
+ ],
+ ),
+ ),
+ const Icon(Icons.open_in_new),
+ ],
+ ),
+ ),
+ ),
+ margin: const EdgeInsets.only(
+ left: SMALL_SPACE,
+ right: SMALL_SPACE,
+ bottom: MEDIUM_SPACE,
+ ));
+ }
+
+ String _getWebsite(BuildContext context) {
+ String website = Provider.of(context).website!;
+
+ if (!website.startsWith('http')) {
+ website = 'http://$website';
+ }
+
+ return website;
+ }
+}
diff --git a/packages/smooth_app/lib/pages/product/product_questions_widget.dart b/packages/smooth_app/lib/pages/product/product_questions_widget.dart
index 9179081a751..e3ab6f2cef1 100644
--- a/packages/smooth_app/lib/pages/product/product_questions_widget.dart
+++ b/packages/smooth_app/lib/pages/product/product_questions_widget.dart
@@ -1,19 +1,26 @@
import 'package:flutter/material.dart';
import 'package:flutter_gen/gen_l10n/app_localizations.dart';
+import 'package:flutter_svg/flutter_svg.dart';
import 'package:openfoodfacts/openfoodfacts.dart';
import 'package:provider/provider.dart';
import 'package:shimmer/shimmer.dart';
+import 'package:smooth_app/data_models/user_preferences.dart';
import 'package:smooth_app/database/local_database.dart';
import 'package:smooth_app/generic_lib/design_constants.dart';
import 'package:smooth_app/generic_lib/duration_constants.dart';
+import 'package:smooth_app/helpers/analytics_helper.dart';
import 'package:smooth_app/helpers/robotoff_insight_helper.dart';
import 'package:smooth_app/pages/hunger_games/question_page.dart';
import 'package:smooth_app/query/product_questions_query.dart';
class ProductQuestionsWidget extends StatefulWidget {
- const ProductQuestionsWidget(this.product);
+ const ProductQuestionsWidget(
+ this.product, {
+ this.layout = ProductQuestionsLayout.button,
+ });
final Product product;
+ final ProductQuestionsLayout layout;
@override
State createState() => _ProductQuestionsWidgetState();
@@ -64,8 +71,126 @@ class _ProductQuestionsWidgetState extends State
// Mandatory to call with an [AutomaticKeepAliveClientMixin]
super.build(context);
+ return switch (widget.layout) {
+ ProductQuestionsLayout.button => _ProductQuestionButton(
+ state: _state,
+ openQuestionsCallback: _openQuestions,
+ ),
+ ProductQuestionsLayout.banner => _ProductQuestionBanner(
+ state: _state,
+ openQuestionsCallback: _openQuestions,
+ ),
+ };
+ }
+
+ void _openQuestions() {
+ _trackEvent(AnalyticsEvent.questionClicked);
+
+ openQuestionPage(
+ context,
+ product: widget.product,
+ questions: (_state as _ProductQuestionsWithQuestions).questions.toList(
+ growable: false,
+ ),
+ updateProductUponAnswers: _updateProductUponAnswers,
+ );
+ }
+
+ Future _reloadQuestions() async {
+ setState(() => _state = const _ProductQuestionsLoading());
+ final List? list = await _loadProductQuestions();
+
+ if (!mounted) {
+ return;
+ }
+
+ if (list?.isNotEmpty == true && !_annotationVoted) {
+ setState(() => _state = _ProductQuestionsWithQuestions(list!));
+ _trackEvent(AnalyticsEvent.questionVisible);
+ } else {
+ setState(() => _state = const _ProductQuestionsWithoutQuestions());
+ }
+ }
+
+ void _trackEvent(AnalyticsEvent event) => AnalyticsHelper.trackEvent(
+ event,
+ eventValue: switch (widget.layout) {
+ ProductQuestionsLayout.button => 0,
+ ProductQuestionsLayout.banner => 1,
+ },
+ );
+
+ Future?> _loadProductQuestions() async {
+ final LocalDatabase localDatabase = context.read();
+
+ try {
+ final List questions =
+ await ProductQuestionsQuery(widget.product.barcode!)
+ .getQuestions(localDatabase, 3);
+
+ if (!mounted) {
+ return null;
+ }
+
+ final RobotoffInsightHelper robotoffInsightHelper =
+ RobotoffInsightHelper(localDatabase);
+ _annotationVoted =
+ await robotoffInsightHelper.areQuestionsAlreadyVoted(questions);
+ return questions;
+ } catch (_) {
+ return null;
+ }
+ }
+
+ Future _updateProductUponAnswers() async {
+ // Reload the product questions, they might have been answered.
+ // Or the backend may have new ones.
+ final LocalDatabase localDatabase = context.read();
+ final List questions =
+ await _loadProductQuestions() ?? [];
+ if (!mounted) {
+ return;
+ }
+ final RobotoffInsightHelper robotoffInsightHelper =
+ RobotoffInsightHelper(localDatabase);
+ if (questions.isEmpty) {
+ await robotoffInsightHelper
+ .removeInsightAnnotationsSavedForProdcut(widget.product.barcode!);
+ }
+ _annotationVoted =
+ await robotoffInsightHelper.areQuestionsAlreadyVoted(questions);
+ }
+
+ @override
+ bool get wantKeepAlive => _keepWidgetAlive;
+}
+
+/// A naive implementation to have a half of the user base using a button and
+/// the other half, the banner
+ProductQuestionsLayout getUserQuestionsLayout(UserPreferences preferences) {
+ return preferences.userGroup.isEven
+ ? ProductQuestionsLayout.button
+ : ProductQuestionsLayout.banner;
+}
+
+enum ProductQuestionsLayout {
+ button,
+ banner,
+}
+
+class _ProductQuestionButton extends StatelessWidget {
+ const _ProductQuestionButton({
+ required this.state,
+ required this.openQuestionsCallback,
+ });
+
+ final _ProductQuestionsState state;
+ final VoidCallback openQuestionsCallback;
+
+ @override
+ Widget build(BuildContext context) {
return AnimatedCrossFade(
- crossFadeState: _state is _ProductQuestionsWithoutQuestions
+ crossFadeState: state is _ProductQuestionsWithoutQuestions
? CrossFadeState.showFirst
: CrossFadeState.showSecond,
duration: SmoothAnimationsDuration.long,
@@ -78,22 +203,14 @@ class _ProductQuestionsWidgetState extends State
// [Shimmer] doesn't support [Ink]
final Color backgroundColor = Theme.of(context).colorScheme.primary;
- if (_state is _ProductQuestionsWithQuestions) {
+ if (state is _ProductQuestionsWithQuestions) {
return Semantics(
value: appLocalizations.tap_to_answer_hint,
button: true,
excludeSemantics: true,
child: InkWell(
borderRadius: ANGULAR_BORDER_RADIUS,
- onTap: () => openQuestionPage(
- context,
- product: widget.product,
- questions:
- (_state as _ProductQuestionsWithQuestions).questions.toList(
- growable: false,
- ),
- updateProductUponAnswers: _updateProductUponAnswers,
- ),
+ onTap: openQuestionsCallback,
child: Ink(
decoration: BoxDecoration(
color: backgroundColor,
@@ -132,91 +249,163 @@ class _ProductQuestionsWidgetState extends State
}
Widget _buildContent(
- BuildContext context, AppLocalizations appLocalizations) {
+ BuildContext context,
+ AppLocalizations appLocalizations,
+ ) {
final bool isDarkMode = Theme.of(context).brightness == Brightness.dark;
return SizedBox(
width: double.infinity,
child: Column(
children: [
- // TODO(jasmeet): Use Material icon or SVG (after consulting UX).
- Text(
- '🏅 ${appLocalizations.tap_to_answer}',
- style: Theme.of(context).primaryTextTheme.bodyLarge!.copyWith(
- color: isDarkMode ? Colors.black : WHITE_COLOR,
- ),
- ),
- Padding(
- padding: const EdgeInsetsDirectional.only(
- top: SMALL_SPACE,
- ),
- child: Text(
- appLocalizations.contribute_to_get_rewards,
- style: Theme.of(context).primaryTextTheme.bodyMedium!.copyWith(
- color: isDarkMode ? Colors.black : WHITE_COLOR,
+ Row(
+ children: [
+ const _ProductQuestionIcon(),
+ Expanded(
+ child: RichText(
+ text: TextSpan(
+ text: '${appLocalizations.tap_to_answer}\n',
+ style:
+ Theme.of(context).primaryTextTheme.bodyLarge!.copyWith(
+ color: isDarkMode ? Colors.black : WHITE_COLOR,
+ height: 1.5,
+ ),
+ children: [
+ TextSpan(
+ text: appLocalizations.contribute_to_get_rewards,
+ style: Theme.of(context)
+ .primaryTextTheme
+ .bodyMedium!
+ .copyWith(
+ color: isDarkMode ? Colors.black : WHITE_COLOR,
+ ),
+ ),
+ ],
),
- ),
+ ),
+ ),
+ ],
),
],
),
);
}
+}
- Future _reloadQuestions() async {
- setState(() => _state = const _ProductQuestionsLoading());
- final List? list = await _loadProductQuestions();
+class _ProductQuestionBanner extends StatelessWidget {
+ const _ProductQuestionBanner({
+ required this.state,
+ required this.openQuestionsCallback,
+ });
- if (!mounted) {
- return;
- }
+ final _ProductQuestionsState state;
+ final VoidCallback openQuestionsCallback;
- if (list?.isNotEmpty == true && !_annotationVoted) {
- setState(() => _state = _ProductQuestionsWithQuestions(list!));
+ @override
+ Widget build(BuildContext context) {
+ final AppLocalizations appLocalizations = AppLocalizations.of(context);
+ final bool isDarkMode = Theme.of(context).brightness == Brightness.dark;
+ final Color contentColor = isDarkMode ? Colors.black : WHITE_COLOR;
+
+ // We need to differentiate with / without a Shimmer, because
+ // [Shimmer] doesn't support [Ink]
+ final Color backgroundColor = Theme.of(context).colorScheme.primary;
+
+ final Widget child;
+ if (state is! _ProductQuestionsWithQuestions) {
+ child = const BlockSemantics(
+ blocking: true,
+ child: EMPTY_WIDGET,
+ );
} else {
- setState(() => _state = const _ProductQuestionsWithoutQuestions());
+ child = Semantics(
+ value: appLocalizations.tap_to_answer_hint,
+ button: true,
+ excludeSemantics: true,
+ child: Material(
+ type: MaterialType.transparency,
+ child: InkWell(
+ onTap: openQuestionsCallback,
+ child: Ink(
+ width: double.infinity,
+ color: backgroundColor,
+ padding: const EdgeInsets.symmetric(
+ vertical: SMALL_SPACE,
+ horizontal: VERY_LARGE_SPACE,
+ ),
+ child: Row(
+ children: [
+ const _ProductQuestionIcon(),
+ Expanded(
+ child: RichText(
+ text: TextSpan(
+ text: '${appLocalizations.tap_to_answer}\n',
+ style: Theme.of(context)
+ .primaryTextTheme
+ .bodyLarge!
+ .copyWith(
+ color: contentColor,
+ height: 1.5,
+ ),
+ children: [
+ TextSpan(
+ text: appLocalizations.contribute_to_get_rewards,
+ style: Theme.of(context)
+ .primaryTextTheme
+ .bodyMedium!
+ .copyWith(
+ color: contentColor,
+ ),
+ ),
+ ],
+ ),
+ ),
+ ),
+ Icon(
+ Icons.arrow_circle_right_outlined,
+ color: contentColor,
+ size: 20.0,
+ )
+ ],
+ ),
+ ),
+ ),
+ ),
+ );
}
- }
- Future?> _loadProductQuestions() async {
- final LocalDatabase localDatabase = context.read();
- final List questions;
- try {
- questions = await ProductQuestionsQuery(widget.product.barcode!)
- .getQuestions(localDatabase, 3);
- } catch (e) {
- return null;
- }
- if (!mounted) {
- return null;
- }
- final RobotoffInsightHelper robotoffInsightHelper =
- RobotoffInsightHelper(localDatabase);
- _annotationVoted =
- await robotoffInsightHelper.areQuestionsAlreadyVoted(questions);
- return questions;
+ return AnimatedOpacity(
+ duration: SmoothAnimationsDuration.long,
+ opacity: state is _ProductQuestionsWithQuestions ? 1.0 : 0.0,
+ child: child,
+ );
}
+}
- Future _updateProductUponAnswers() async {
- // Reload the product questions, they might have been answered.
- // Or the backend may have new ones.
- final LocalDatabase localDatabase = context.read();
- final List questions =
- await _loadProductQuestions() ?? [];
- if (!mounted) {
- return;
- }
- final RobotoffInsightHelper robotoffInsightHelper =
- RobotoffInsightHelper(localDatabase);
- if (questions.isEmpty) {
- await robotoffInsightHelper
- .removeInsightAnnotationsSavedForProdcut(widget.product.barcode!);
- }
- _annotationVoted =
- await robotoffInsightHelper.areQuestionsAlreadyVoted(questions);
- }
+class _ProductQuestionIcon extends StatelessWidget {
+ const _ProductQuestionIcon();
@override
- bool get wantKeepAlive => _keepWidgetAlive;
+ Widget build(BuildContext context) {
+ final double size =
+ (DefaultTextStyle.of(context).style.fontSize ?? 15.0) * 1.5;
+
+ return BlockSemantics(
+ blocking: true,
+ child: Padding(
+ padding: const EdgeInsetsDirectional.only(
+ end: SMALL_SPACE,
+ top: SMALL_SPACE,
+ bottom: SMALL_SPACE,
+ ),
+ child: SvgPicture.asset(
+ 'assets/icons/medal.svg',
+ width: size,
+ height: size,
+ ),
+ ),
+ );
+ }
}
// Widget State
@@ -261,3 +450,5 @@ class KeepQuestionWidgetAlive extends InheritedWidget {
return oldWidget.keepWidgetAlive != keepWidgetAlive;
}
}
+
+typedef OnQuestionVisible = Function(double height);
diff --git a/packages/smooth_app/lib/pages/product/summary_card.dart b/packages/smooth_app/lib/pages/product/summary_card.dart
index 18c271d155d..0a1b4db0e03 100644
--- a/packages/smooth_app/lib/pages/product/summary_card.dart
+++ b/packages/smooth_app/lib/pages/product/summary_card.dart
@@ -85,11 +85,13 @@ class SummaryCard extends StatefulWidget {
class _SummaryCardState extends State with UpToDateMixin {
// For some reason, special case for "label" attributes
final Set _attributesToExcludeIfStatusIsUnknown = {};
+ late ProductQuestionsLayout _questionsLayout;
@override
void initState() {
super.initState();
initUpToDate(widget._product, context.read());
+ _questionsLayout = getUserQuestionsLayout(context.read());
if (ProductIncompleteCard.isProductIncomplete(initialProduct)) {
AnalyticsHelper.trackEvent(
AnalyticsEvent.showFastTrackProductEditCard,
@@ -357,7 +359,12 @@ class _SummaryCardState extends State with UpToDateMixin {
if (ProductIncompleteCard.isProductIncomplete(upToDateProduct))
ProductIncompleteCard(product: upToDateProduct),
..._getAttributes(scoreAttributes),
- if (widget.isFullVersion) ProductQuestionsWidget(upToDateProduct),
+ if (widget.isFullVersion &&
+ _questionsLayout == ProductQuestionsLayout.button)
+ ProductQuestionsWidget(
+ upToDateProduct,
+ layout: ProductQuestionsLayout.button,
+ ),
attributesContainer,
...summaryCardButtons,
],
diff --git a/packages/smooth_app/lib/widgets/widget_height.dart b/packages/smooth_app/lib/widgets/widget_height.dart
new file mode 100644
index 00000000000..68a9924d712
--- /dev/null
+++ b/packages/smooth_app/lib/widgets/widget_height.dart
@@ -0,0 +1,46 @@
+import 'package:flutter/rendering.dart';
+import 'package:flutter/widgets.dart';
+
+class MeasureSize extends SingleChildRenderObjectWidget {
+ const MeasureSize({
+ Key? key,
+ required this.onChange,
+ required Widget child,
+ }) : super(key: key, child: child);
+ final OnWidgetSizeChange onChange;
+
+ @override
+ RenderObject createRenderObject(BuildContext context) {
+ return MeasureSizeRenderObject(onChange);
+ }
+
+ @override
+ void updateRenderObject(
+ BuildContext context,
+ covariant MeasureSizeRenderObject renderObject,
+ ) {
+ renderObject.onChange = onChange;
+ }
+}
+
+class MeasureSizeRenderObject extends RenderProxyBox {
+ MeasureSizeRenderObject(this.onChange);
+
+ OnWidgetSizeChange onChange;
+ Size? oldSize;
+
+ @override
+ void performLayout() {
+ super.performLayout();
+
+ final Size newSize = child!.size;
+ if (oldSize != newSize) {
+ oldSize = newSize;
+ WidgetsBinding.instance.addPostFrameCallback((_) {
+ onChange(newSize);
+ });
+ }
+ }
+}
+
+typedef OnWidgetSizeChange = void Function(Size size);
diff --git a/packages/smooth_app/pubspec.yaml b/packages/smooth_app/pubspec.yaml
index 70b2802382a..b850be6b672 100644
--- a/packages/smooth_app/pubspec.yaml
+++ b/packages/smooth_app/pubspec.yaml
@@ -170,4 +170,4 @@ flutter:
- assets/packagings/
- assets/preferences/
- assets/product/
- - assets/icons/visor_icon.svg
+ - assets/icons/