diff --git a/assets/lottie/loader_and_checkmark.json b/assets/lottie/loader_and_checkmark.json
index 76a6944d5..406f52030 100644
--- a/assets/lottie/loader_and_checkmark.json
+++ b/assets/lottie/loader_and_checkmark.json
@@ -1 +1 @@
-{"v":"5.10.2","fr":30,"ip":0,"op":140,"w":24,"h":24,"nm":"Loader","ddd":0,"assets":[],"layers":[{"ddd":0,"ind":1,"ty":4,"nm":"Arrow","sr":1,"ks":{"o":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":0,"s":[0]},{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":116,"s":[0]},{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":118,"s":[80]},{"t":130,"s":[100]}],"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[12,12,0],"ix":2,"l":2},"a":{"a":0,"k":[0,0,0],"ix":1,"l":2},"s":{"a":1,"k":[{"i":{"x":[0.667,0.667,0.667],"y":[1,1,1]},"o":{"x":[0.333,0.333,0.333],"y":[0,0,0]},"t":118,"s":[30,30,100]},{"t":125,"s":[100,100,100]}],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0,0],[0,0],[0,0]],"o":[[0,0],[0,0],[0,0]],"v":[[4.75,-3],[-1.25,3],[-4.75,-0.5]],"c":false},"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":0,"k":2.5,"ix":5},"lc":2,"lj":2,"bm":0,"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":"Vector","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false},{"ty":"tm","s":{"a":0,"k":100,"ix":1},"e":{"a":1,"k":[{"i":{"x":[0.667],"y":[1]},"o":{"x":[0.333],"y":[0]},"t":118,"s":[100]},{"i":{"x":[0.667],"y":[1]},"o":{"x":[0.333],"y":[0]},"t":123,"s":[30]},{"t":130,"s":[0]}],"ix":2},"o":{"a":0,"k":0,"ix":3},"m":1,"ix":2,"nm":"Trim Paths 1","mn":"ADBE Vector Filter - Trim","hd":false}],"ip":0,"op":140,"st":0,"ct":1,"bm":0},{"ddd":0,"ind":2,"ty":4,"nm":"Fill","sr":1,"ks":{"o":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":0,"s":[0]},{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":116,"s":[0]},{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":118,"s":[30]},{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":123,"s":[70]},{"t":130,"s":[100]}],"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[12,12,0],"ix":2,"l":2},"a":{"a":0,"k":[0,0,0],"ix":1,"l":2},"s":{"a":1,"k":[{"i":{"x":[0.667,0.667,0.667],"y":[1,1,1]},"o":{"x":[0.333,0.333,0.333],"y":[0,0,0]},"t":118,"s":[241.821,241.821,100]},{"t":127,"s":[1511.821,1511.821,100]}],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"d":1,"ty":"el","s":{"a":0,"k":[24,24],"ix":2},"p":{"a":0,"k":[0,0],"ix":3},"nm":"Ellipse Path 1","mn":"ADBE Vector Shape - Ellipse","hd":false},{"ty":"fl","c":{"a":0,"k":[0,0.647058823529,0.470588235294,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"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":[6.243,6.243],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Ellipse 1","np":3,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":140,"st":0,"ct":1,"bm":0},{"ddd":0,"ind":3,"ty":4,"nm":"Сircle green","sr":1,"ks":{"o":{"a":1,"k":[{"i":{"x":[0.667],"y":[1]},"o":{"x":[0.167],"y":[0]},"t":0,"s":[0]},{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.333],"y":[0]},"t":73,"s":[0]},{"t":74,"s":[100]}],"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[12,12,0],"ix":2,"l":2},"a":{"a":0,"k":[0,-0.063,0],"ix":1,"l":2},"s":{"a":1,"k":[{"i":{"x":[0.667,0.667,0.667],"y":[1,1,1]},"o":{"x":[0.167,0.167,0.167],"y":[0.167,0.167,0.754]},"t":0,"s":[113.562,113.562,100]},{"i":{"x":[0.667,0.667,0.667],"y":[1,1,1]},"o":{"x":[0.333,0.333,0.333],"y":[0,0,0]},"t":112,"s":[113.562,113.562,100]},{"i":{"x":[0.667,0.667,0.667],"y":[1,1,1]},"o":{"x":[0.333,0.333,0.333],"y":[0,0,0]},"t":116,"s":[90,90,100]},{"t":130,"s":[113.562,113.562,100]}],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"d":1,"ty":"el","s":{"a":0,"k":[19.125,19.125],"ix":2},"p":{"a":0,"k":[0,0],"ix":3},"nm":"Ellipse Path 1","mn":"ADBE Vector Shape - Ellipse","hd":false},{"ty":"st","c":{"a":0,"k":[0,0.647058823529,0.470588235294,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":2,"ix":5},"lc":2,"lj":1,"ml":4,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"tr","p":{"a":0,"k":[0,-0.063],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Ellipse 1","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false},{"ty":"tm","s":{"a":1,"k":[{"i":{"x":[0.833],"y":[1]},"o":{"x":[0.333],"y":[0]},"t":74,"s":[0]},{"i":{"x":[0.667],"y":[1]},"o":{"x":[0.167],"y":[0]},"t":87,"s":[0]},{"t":112,"s":[0]}],"ix":1},"e":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.333],"y":[0]},"t":74,"s":[0]},{"i":{"x":[0.667],"y":[1]},"o":{"x":[0.167],"y":[0.167]},"t":87,"s":[55]},{"t":112,"s":[100]}],"ix":2},"o":{"a":0,"k":0,"ix":3},"m":1,"ix":2,"nm":"Trim Paths 1","mn":"ADBE Vector Filter - Trim","hd":false},{"ty":"st","c":{"a":0,"k":[0,0.647058823529,0.470588235294,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":2,"ix":5},"lc":1,"lj":1,"ml":4,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false}],"ip":0,"op":140,"st":0,"ct":1,"bm":0},{"ddd":0,"ind":4,"ty":4,"nm":"Сircle black 2 turn","sr":1,"ks":{"o":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":0,"s":[0]},{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":36,"s":[0]},{"i":{"x":[0.667],"y":[1]},"o":{"x":[0.167],"y":[0]},"t":37,"s":[100]},{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.333],"y":[0]},"t":74,"s":[100]},{"t":75,"s":[0]}],"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[12,12,0],"ix":2,"l":2},"a":{"a":0,"k":[0,-0.063,0],"ix":1,"l":2},"s":{"a":0,"k":[113.562,113.562,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"d":1,"ty":"el","s":{"a":0,"k":[19.125,19.125],"ix":2},"p":{"a":0,"k":[0,0],"ix":3},"nm":"Ellipse Path 1","mn":"ADBE Vector Shape - Ellipse","hd":false},{"ty":"st","c":{"a":0,"k":[0.137254901961,0.137254901961,0.137254901961,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":2,"ix":5},"lc":2,"lj":1,"ml":4,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"tr","p":{"a":0,"k":[0,-0.063],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Ellipse 1","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false},{"ty":"tm","s":{"a":1,"k":[{"i":{"x":[0.833],"y":[1]},"o":{"x":[0.333],"y":[0]},"t":37,"s":[0.2]},{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":50,"s":[0.2]},{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":57,"s":[27.5]},{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":72,"s":[99]},{"t":74,"s":[100]}],"ix":1},"e":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.333],"y":[0]},"t":37,"s":[0.1]},{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":50,"s":[55]},{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":57,"s":[73]},{"t":74,"s":[100]}],"ix":2},"o":{"a":0,"k":0,"ix":3},"m":1,"ix":2,"nm":"Trim Paths 1","mn":"ADBE Vector Filter - Trim","hd":false},{"ty":"st","c":{"a":0,"k":[0.137254908681,0.137254908681,0.137254908681,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":2,"ix":5},"lc":1,"lj":1,"ml":4,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false}],"ip":0,"op":140,"st":0,"ct":1,"bm":0},{"ddd":0,"ind":5,"ty":4,"nm":"Сircle black 1 turn","sr":1,"ks":{"o":{"a":1,"k":[{"i":{"x":[0.833],"y":[1]},"o":{"x":[0.333],"y":[0]},"t":0,"s":[100]},{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":37,"s":[100]},{"t":38,"s":[0]}],"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[12,12,0],"ix":2,"l":2},"a":{"a":0,"k":[0,-0.063,0],"ix":1,"l":2},"s":{"a":0,"k":[113.562,113.562,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"d":1,"ty":"el","s":{"a":0,"k":[19.125,19.125],"ix":2},"p":{"a":0,"k":[0,0],"ix":3},"nm":"Ellipse Path 1","mn":"ADBE Vector Shape - Ellipse","hd":false},{"ty":"st","c":{"a":0,"k":[0.137254901961,0.137254901961,0.137254901961,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":2,"ix":5},"lc":2,"lj":1,"ml":4,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"tr","p":{"a":0,"k":[0,-0.063],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Ellipse 1","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false},{"ty":"tm","s":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.333],"y":[0]},"t":0,"s":[0]},{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":13,"s":[0.2]},{"i":{"x":[0.667],"y":[1]},"o":{"x":[0.167],"y":[0.167]},"t":20,"s":[27.5]},{"t":37,"s":[100]}],"ix":1},"e":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.333],"y":[0]},"t":0,"s":[0]},{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":13,"s":[55]},{"i":{"x":[0.667],"y":[1]},"o":{"x":[0.167],"y":[0.167]},"t":20,"s":[73]},{"t":37,"s":[99]}],"ix":2},"o":{"a":0,"k":0,"ix":3},"m":1,"ix":2,"nm":"Trim Paths 1","mn":"ADBE Vector Filter - Trim","hd":false},{"ty":"st","c":{"a":0,"k":[0.137254908681,0.137254908681,0.137254908681,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":2,"ix":5},"lc":1,"lj":1,"ml":4,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false}],"ip":0,"op":140,"st":0,"ct":1,"bm":0}],"markers":[{"tm":0,"cm":"{\r\n\"name\":\"marker 1\"\r\n}","dr":0},{"tm":74,"cm":"{\r\n\"name\":\"marker 2\"\r\n}","dr":0},{"tm":140,"cm":"{\r\n\"name\":\"marker 3\"\r\n}","dr":0}]}
\ No newline at end of file
+{"v":"5.10.2","fr":30,"ip":0,"op":130,"w":24,"h":24,"nm":"Loader","ddd":0,"assets":[],"layers":[{"ddd":0,"ind":1,"ty":4,"nm":"Arrow","sr":1,"ks":{"o":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":0,"s":[0]},{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":115,"s":[0]},{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":117,"s":[80]},{"t":122,"s":[100]}],"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[12,12,0],"ix":2,"l":2},"a":{"a":0,"k":[0,0,0],"ix":1,"l":2},"s":{"a":1,"k":[{"i":{"x":[0.667,0.667,0.667],"y":[1,1,1]},"o":{"x":[0.333,0.333,0.333],"y":[0,0,0]},"t":117,"s":[30,30,100]},{"t":122,"s":[100,100,100]}],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0,0],[0,0],[0,0]],"o":[[0,0],[0,0],[0,0]],"v":[[4.75,-3],[-1.25,3],[-4.75,-0.5]],"c":false},"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":0,"k":2.5,"ix":5},"lc":2,"lj":2,"bm":0,"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":"Vector","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false},{"ty":"tm","s":{"a":0,"k":100,"ix":1},"e":{"a":1,"k":[{"i":{"x":[0.667],"y":[1]},"o":{"x":[0.333],"y":[0]},"t":117,"s":[100]},{"i":{"x":[0.667],"y":[1]},"o":{"x":[0.333],"y":[0]},"t":119,"s":[30]},{"t":122,"s":[0]}],"ix":2},"o":{"a":0,"k":0,"ix":3},"m":1,"ix":2,"nm":"Trim Paths 1","mn":"ADBE Vector Filter - Trim","hd":false}],"ip":0,"op":130,"st":0,"ct":1,"bm":0},{"ddd":0,"ind":2,"ty":4,"nm":"Fill","sr":1,"ks":{"o":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":0,"s":[0]},{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":115,"s":[0]},{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":117,"s":[30]},{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":119,"s":[70]},{"t":122,"s":[100]}],"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[12,12,0],"ix":2,"l":2},"a":{"a":0,"k":[0,0,0],"ix":1,"l":2},"s":{"a":1,"k":[{"i":{"x":[0.667,0.667,0.667],"y":[1,1,1]},"o":{"x":[0.333,0.333,0.333],"y":[0,0,0]},"t":117,"s":[241.821,241.821,100]},{"t":122,"s":[1511.821,1511.821,100]}],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"d":1,"ty":"el","s":{"a":0,"k":[24,24],"ix":2},"p":{"a":0,"k":[0,0],"ix":3},"nm":"Ellipse Path 1","mn":"ADBE Vector Shape - Ellipse","hd":false},{"ty":"fl","c":{"a":0,"k":[0,0.647058823529,0.470588235294,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"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":[6.243,6.243],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Ellipse 1","np":3,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":130,"st":0,"ct":1,"bm":0},{"ddd":0,"ind":3,"ty":4,"nm":"Сircle green","sr":1,"ks":{"o":{"a":1,"k":[{"i":{"x":[0.667],"y":[1]},"o":{"x":[0.167],"y":[0]},"t":0,"s":[0]},{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.333],"y":[0]},"t":73,"s":[0]},{"t":74,"s":[100]}],"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[12,12,0],"ix":2,"l":2},"a":{"a":0,"k":[0,-0.063,0],"ix":1,"l":2},"s":{"a":1,"k":[{"i":{"x":[0.667,0.667,0.667],"y":[1,1,1]},"o":{"x":[0.167,0.167,0.167],"y":[0.167,0.167,0.754]},"t":0,"s":[113.562,113.562,100]},{"i":{"x":[0.667,0.667,0.667],"y":[1,1,1]},"o":{"x":[0.333,0.333,0.333],"y":[0,0,0]},"t":111,"s":[113.562,113.562,100]},{"i":{"x":[0.667,0.667,0.667],"y":[1,1,1]},"o":{"x":[0.333,0.333,0.333],"y":[0,0,0]},"t":115,"s":[90,90,100]},{"t":122,"s":[113.562,113.562,100]}],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"d":1,"ty":"el","s":{"a":0,"k":[19.125,19.125],"ix":2},"p":{"a":0,"k":[0,0],"ix":3},"nm":"Ellipse Path 1","mn":"ADBE Vector Shape - Ellipse","hd":false},{"ty":"st","c":{"a":0,"k":[0,0.647058823529,0.470588235294,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":2,"ix":5},"lc":2,"lj":1,"ml":4,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"tr","p":{"a":0,"k":[0,-0.063],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Ellipse 1","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false},{"ty":"tm","s":{"a":1,"k":[{"i":{"x":[0.833],"y":[1]},"o":{"x":[0.333],"y":[0]},"t":74,"s":[0]},{"i":{"x":[0.667],"y":[1]},"o":{"x":[0.167],"y":[0]},"t":87,"s":[0]},{"t":111,"s":[0]}],"ix":1},"e":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.333],"y":[0]},"t":74,"s":[0]},{"i":{"x":[0.667],"y":[1]},"o":{"x":[0.167],"y":[0.167]},"t":87,"s":[55]},{"t":111,"s":[100]}],"ix":2},"o":{"a":0,"k":0,"ix":3},"m":1,"ix":2,"nm":"Trim Paths 1","mn":"ADBE Vector Filter - Trim","hd":false},{"ty":"st","c":{"a":0,"k":[0,0.647058823529,0.470588235294,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":2,"ix":5},"lc":1,"lj":1,"ml":4,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false}],"ip":0,"op":130,"st":0,"ct":1,"bm":0},{"ddd":0,"ind":4,"ty":4,"nm":"Сircle black 2 turn","sr":1,"ks":{"o":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":0,"s":[0]},{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":36,"s":[0]},{"i":{"x":[0.667],"y":[1]},"o":{"x":[0.167],"y":[0]},"t":37,"s":[100]},{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.333],"y":[0]},"t":74,"s":[100]},{"t":75,"s":[0]}],"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[12,12,0],"ix":2,"l":2},"a":{"a":0,"k":[0,-0.063,0],"ix":1,"l":2},"s":{"a":0,"k":[113.562,113.562,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"d":1,"ty":"el","s":{"a":0,"k":[19.125,19.125],"ix":2},"p":{"a":0,"k":[0,0],"ix":3},"nm":"Ellipse Path 1","mn":"ADBE Vector Shape - Ellipse","hd":false},{"ty":"st","c":{"a":0,"k":[0.137254901961,0.137254901961,0.137254901961,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":2,"ix":5},"lc":2,"lj":1,"ml":4,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"tr","p":{"a":0,"k":[0,-0.063],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Ellipse 1","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false},{"ty":"tm","s":{"a":1,"k":[{"i":{"x":[0.833],"y":[1]},"o":{"x":[0.333],"y":[0]},"t":37,"s":[0.2]},{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":50,"s":[0.2]},{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":57,"s":[27.5]},{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":72,"s":[99]},{"t":74,"s":[100]}],"ix":1},"e":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.333],"y":[0]},"t":37,"s":[0.1]},{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":50,"s":[55]},{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":57,"s":[73]},{"t":74,"s":[100]}],"ix":2},"o":{"a":0,"k":0,"ix":3},"m":1,"ix":2,"nm":"Trim Paths 1","mn":"ADBE Vector Filter - Trim","hd":false},{"ty":"st","c":{"a":0,"k":[0.137254908681,0.137254908681,0.137254908681,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":2,"ix":5},"lc":1,"lj":1,"ml":4,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false}],"ip":0,"op":130,"st":0,"ct":1,"bm":0},{"ddd":0,"ind":5,"ty":4,"nm":"Сircle black 1 turn","sr":1,"ks":{"o":{"a":1,"k":[{"i":{"x":[0.833],"y":[1]},"o":{"x":[0.333],"y":[0]},"t":0,"s":[100]},{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":37,"s":[100]},{"t":38,"s":[0]}],"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[12,12,0],"ix":2,"l":2},"a":{"a":0,"k":[0,-0.063,0],"ix":1,"l":2},"s":{"a":0,"k":[113.562,113.562,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"d":1,"ty":"el","s":{"a":0,"k":[19.125,19.125],"ix":2},"p":{"a":0,"k":[0,0],"ix":3},"nm":"Ellipse Path 1","mn":"ADBE Vector Shape - Ellipse","hd":false},{"ty":"st","c":{"a":0,"k":[0.137254901961,0.137254901961,0.137254901961,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":2,"ix":5},"lc":2,"lj":1,"ml":4,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"tr","p":{"a":0,"k":[0,-0.063],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Ellipse 1","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false},{"ty":"tm","s":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.333],"y":[0]},"t":0,"s":[0]},{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":13,"s":[0.2]},{"i":{"x":[0.667],"y":[1]},"o":{"x":[0.167],"y":[0.167]},"t":20,"s":[27.5]},{"t":37,"s":[100]}],"ix":1},"e":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.333],"y":[0]},"t":0,"s":[0]},{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":13,"s":[55]},{"i":{"x":[0.667],"y":[1]},"o":{"x":[0.167],"y":[0.167]},"t":20,"s":[73]},{"t":37,"s":[99]}],"ix":2},"o":{"a":0,"k":0,"ix":3},"m":1,"ix":2,"nm":"Trim Paths 1","mn":"ADBE Vector Filter - Trim","hd":false},{"ty":"st","c":{"a":0,"k":[0.137254908681,0.137254908681,0.137254908681,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":2,"ix":5},"lc":1,"lj":1,"ml":4,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false}],"ip":0,"op":130,"st":0,"ct":1,"bm":0}],"markers":[{"tm":0,"cm":"{\r\n\"name\":\"marker 1\"\r\n}","dr":0},{"tm":74,"cm":"{\r\n\"name\":\"marker 2\"\r\n}","dr":0},{"tm":130,"cm":"{\r\n\"name\":\"marker 3\"\r\n}","dr":0}]}
\ No newline at end of file
diff --git a/assets/svg/exchange_icons/trocador.svg b/assets/svg/exchange_icons/trocador.svg
new file mode 100644
index 000000000..b3d9171ff
--- /dev/null
+++ b/assets/svg/exchange_icons/trocador.svg
@@ -0,0 +1,3 @@
+
diff --git a/assets/svg/trocador_rating_a.svg b/assets/svg/trocador_rating_a.svg
new file mode 100644
index 000000000..1e75af73b
--- /dev/null
+++ b/assets/svg/trocador_rating_a.svg
@@ -0,0 +1,10 @@
+
diff --git a/assets/svg/trocador_rating_b.svg b/assets/svg/trocador_rating_b.svg
new file mode 100644
index 000000000..5d678305a
--- /dev/null
+++ b/assets/svg/trocador_rating_b.svg
@@ -0,0 +1,10 @@
+
diff --git a/assets/svg/trocador_rating_c.svg b/assets/svg/trocador_rating_c.svg
new file mode 100644
index 000000000..87ecf6b24
--- /dev/null
+++ b/assets/svg/trocador_rating_c.svg
@@ -0,0 +1,10 @@
+
diff --git a/assets/svg/trocador_rating_d.svg b/assets/svg/trocador_rating_d.svg
new file mode 100644
index 000000000..8973c7e65
--- /dev/null
+++ b/assets/svg/trocador_rating_d.svg
@@ -0,0 +1,10 @@
+
diff --git a/crypto_plugins/flutter_libepiccash b/crypto_plugins/flutter_libepiccash
index b8222a95a..398077d74 160000
--- a/crypto_plugins/flutter_libepiccash
+++ b/crypto_plugins/flutter_libepiccash
@@ -1 +1 @@
-Subproject commit b8222a95ad31885258ea341fabd9bc3a2608efce
+Subproject commit 398077d745bfb8e27c6fcb3fae971908566b2222
diff --git a/docs/building.md b/docs/building.md
index 879008f09..4409980d7 100644
--- a/docs/building.md
+++ b/docs/building.md
@@ -2,16 +2,36 @@
Here you will find instructions on how to install the necessary tools for building and running the app.
-### Prerequisites
+## Prerequisites
-- The OS'es supported for building is Ubuntu (20.04) and Fedora (37 - Work In Progress)
-- A machine with at least 100 GB of Storage
-
-The following prerequisites can be installed with the setup script [`scripts/setup.sh`](./../scripts/setup.sh) or manually as described below:
-
-- Flutter 3.7.11 [(install manually or with git, do not install with snap)](https://docs.flutter.dev/get-started/install)
-- Dart SDK Requirement (>=2.19.0, up until <3.0.0) (normally included with a flutter install)
+- The only OS supported for building is Ubuntu 20.04. Advanced users may also be able to build on other Debian-based distributions like Fedora 37.
- Android setup ([Android Studio](https://developer.android.com/studio) and subsequent dependencies)
+- 100 GB of storage
+
+Install Android Studio following the instructions below before proceeding, then the following prerequisites can be installed with the setup script [`scripts/setup.sh`](./../scripts/setup.sh) or manually as described below:
+
+- Flutter 3.7.12 [(install manually or with git, do not install with snap)](https://docs.flutter.dev/get-started/install)
+- Dart SDK Requirement (>=2.19.0, up until <3.0.0) (normally included with a flutter install)
+
+### Android Studio
+Android Studio is the recommended IDE for development, not just for launching on Android devices and emulators but also for Linux desktop development.
+
+Follow instructions here [https://developer.android.com/studio/install#linux](https://developer.android.com/studio/install#linux) or install via snap:
+```
+# setup android studio
+sudo apt install -y openjdk-11-jdk
+sudo snap install android-studio --classic
+```
+
+Use Tools > SDK Manager to install:
+ - SDK Tools > Android SDK (API 30)
+ - SDK Tools > NDK
+ - SDK Tools > Android SDK command line tools
+ - SDK Tools > CMake
+
+Then in File > Settings > Plugins, install the Flutter plugin and restart the IDE. In File > Settings > Languages & Frameworks > Flutter > Editor, enable auto format on save to match the project's code style. If you have problems with the Dart SDK, make sure to run `flutter` in a terminal to download it (use `source ~/.bashrc` to update your environment variables if you're still using the same terminal from which you ran `setup.sh`)
+
+Make a Pixel 4 (API 30) x86_64 emulator with 2GB of storage space for emulation
### Scripted setup
@@ -19,14 +39,7 @@ The following prerequisites can be installed with the setup script [`scripts/set
### Manual setup
-> If you have installed with script, skip to [running](#running)
-
-Please go to your Linux distribution's title below for instructions on how to manually setup:
-
-- [Ubuntu (20.04)](#ubuntu-2004)
-- [Fedora (37) (Work In Progress)](#fedora-37-work-in-progress)
-
-#### Ubuntu (20.04)
+> If you used the `setup.sh` script, skip to [running](#running)
Install basic dependencies
```
@@ -105,10 +118,6 @@ cd scripts/linux/
cd ../..
```
-#### Fedora (37) (Work In Progress)
-
-This is a work in progress, please use Ubuntu for now.
-
## Running
### Android
Plug in your android device or use the emulator available via Android Studio and then run the following commands:
@@ -125,19 +134,3 @@ Plug in your android device or use the emulator available via Android Studio and
flutter pub get Linux
flutter run linux
```
-
-## Android Studio
-Android Studio is the recommended IDE for development, not just for launching on Android devices and emulators but also for Linux desktop development.
-
-Follow instructions here [https://developer.android.com/studio/install#linux](https://developer.android.com/studio/install#linux) or install via snap:
-```
-# setup android studio
-sudo apt install -y openjdk-11-jdk
-sudo snap install android-studio --classic
-```
-
-Use Tools > SDK Manager to install the SDK Tools > Android SDK (API 30), SDK Tools > NDK, SDK Tools > Android SDK command line tools, and SDK Tools > CMake
-
-Then install the Flutter plugin and restart the IDE. In Android Studio's options for the Flutter language, enable auto format on save to match the project's code style. If you have problems with the Dart SDK, make sure to run `flutter` in a terminal to download it (use `source ~/.bashrc` to update your environment variables if you're still using the same terminal from which you ran `setup.sh`)
-
-Make a Pixel 4 (API 30) x86_64 emulator with 2GB of storage space for emulation
diff --git a/lib/main.dart b/lib/main.dart
index e08324572..10d1c4a12 100644
--- a/lib/main.dart
+++ b/lib/main.dart
@@ -299,7 +299,8 @@ class _MaterialAppWithThemeState extends ConsumerState
await ref.read(prefsChangeNotifierProvider).isExternalCallsSet()) {
if (Constants.enableExchange) {
await ExchangeDataLoadingService.instance.setCurrenciesIfEmpty(
- ref.read(exchangeFormStateProvider),
+ ref.read(efCurrencyPairProvider),
+ ref.read(efRateTypeProvider),
);
unawaited(ExchangeDataLoadingService.instance.loadAll());
}
diff --git a/lib/models/exchange/active_pair.dart b/lib/models/exchange/active_pair.dart
new file mode 100644
index 000000000..4a2e80eba
--- /dev/null
+++ b/lib/models/exchange/active_pair.dart
@@ -0,0 +1,35 @@
+import 'package:flutter/foundation.dart';
+import 'package:stackwallet/models/exchange/aggregate_currency.dart';
+
+class ActivePair extends ChangeNotifier {
+ AggregateCurrency? _send;
+ AggregateCurrency? _receive;
+
+ AggregateCurrency? get send => _send;
+ AggregateCurrency? get receive => _receive;
+
+ void setSend(
+ AggregateCurrency? newSend, {
+ bool notifyListeners = false,
+ }) {
+ _send = newSend;
+ if (notifyListeners) {
+ this.notifyListeners();
+ }
+ }
+
+ void setReceive(
+ AggregateCurrency? newReceive, {
+ bool notifyListeners = false,
+ }) {
+ _receive = newReceive;
+ if (notifyListeners) {
+ this.notifyListeners();
+ }
+ }
+
+ @override
+ String toString() {
+ return "ActivePair{ send: $send, receive: $receive }";
+ }
+}
diff --git a/lib/models/exchange/aggregate_currency.dart b/lib/models/exchange/aggregate_currency.dart
index 6cd1ef6cf..1bbc767a5 100644
--- a/lib/models/exchange/aggregate_currency.dart
+++ b/lib/models/exchange/aggregate_currency.dart
@@ -5,8 +5,9 @@ import 'package:tuple/tuple.dart';
class AggregateCurrency {
final Map _map = {};
- AggregateCurrency(
- {required List> exchangeCurrencyPairs}) {
+ AggregateCurrency({
+ required List> exchangeCurrencyPairs,
+ }) {
assert(exchangeCurrencyPairs.isNotEmpty);
for (final item in exchangeCurrencyPairs) {
diff --git a/lib/models/exchange/exchange_form_state.dart b/lib/models/exchange/exchange_form_state.dart
deleted file mode 100644
index 32578ebd9..000000000
--- a/lib/models/exchange/exchange_form_state.dart
+++ /dev/null
@@ -1,519 +0,0 @@
-import 'package:decimal/decimal.dart';
-import 'package:flutter/foundation.dart';
-import 'package:stackwallet/models/exchange/aggregate_currency.dart';
-import 'package:stackwallet/models/exchange/response_objects/estimate.dart';
-import 'package:stackwallet/services/exchange/change_now/change_now_exchange.dart';
-import 'package:stackwallet/services/exchange/exchange.dart';
-import 'package:stackwallet/services/exchange/majestic_bank/majestic_bank_exchange.dart';
-import 'package:stackwallet/utilities/enums/exchange_rate_type_enum.dart';
-import 'package:stackwallet/utilities/logger.dart';
-
-class ExchangeFormState extends ChangeNotifier {
- Exchange? _exchange;
- Exchange get exchange => _exchange ??= Exchange.defaultExchange;
-
- ExchangeRateType _exchangeRateType = ExchangeRateType.estimated;
- ExchangeRateType get exchangeRateType => _exchangeRateType;
- set exchangeRateType(ExchangeRateType exchangeRateType) {
- _exchangeRateType = exchangeRateType;
- //
- }
-
- Estimate? _estimate;
- Estimate? get estimate => _estimate;
-
- bool _reversed = false;
- bool get reversed => _reversed;
- set reversed(bool reversed) {
- _reversed = reversed;
- //
- }
-
- Decimal? _rate;
- Decimal? get rate => _rate;
- // set rate(Decimal? rate) {
- // _rate = rate;
- // //
- // }
-
- Decimal? _sendAmount;
- Decimal? get sendAmount => _sendAmount;
- // set sendAmount(Decimal? sendAmount) {
- // _sendAmount = sendAmount;
- // //
- // }
-
- Decimal? _receiveAmount;
- Decimal? get receiveAmount => _receiveAmount;
- set receiveAmount(Decimal? receiveAmount) {
- _receiveAmount = receiveAmount;
- //
- }
-
- AggregateCurrency? _sendCurrency;
- AggregateCurrency? get sendCurrency => _sendCurrency;
- // set sendCurrency(Currency? sendCurrency) {
- // _sendCurrency = sendCurrency;
- // //
- // }
-
- AggregateCurrency? _receiveCurrency;
- AggregateCurrency? get receiveCurrency => _receiveCurrency;
- // set receiveCurrency(Currency? receiveCurrency) {
- // _receiveCurrency = receiveCurrency;
- // //
- // }
-
- Decimal? _minSendAmount;
- Decimal? get minSendAmount => _minSendAmount;
- // set minSendAmount(Decimal? minSendAmount) {
- // _minSendAmount = minSendAmount;
- // //
- // }
-
- Decimal? _minReceiveAmount;
- Decimal? get minReceiveAmount => _minReceiveAmount;
- // set minReceiveAmount(Decimal? minReceiveAmount) {
- // _minReceiveAmount = minReceiveAmount;
- // //
- // }
-
- Decimal? _maxSendAmount;
- Decimal? get maxSendAmount => _maxSendAmount;
- // set maxSendAmount(Decimal? maxSendAmount) {
- // _maxSendAmount = maxSendAmount;
- // //
- // }
-
- Decimal? _maxReceiveAmount;
- Decimal? get maxReceiveAmount => _maxReceiveAmount;
- // set maxReceiveAmount(Decimal? maxReceiveAmount) {
- // _maxReceiveAmount = maxReceiveAmount;
- // //
- // }
-
- //============================================================================
- // computed properties
- //============================================================================
-
- String? get fromTicker => _sendCurrency?.ticker;
- String? get toTicker => _receiveCurrency?.ticker;
-
- String get fromAmountString => _sendAmount?.toStringAsFixed(8) ?? "";
- String get toAmountString => _receiveAmount?.toStringAsFixed(8) ?? "";
-
- bool get canExchange {
- return sendCurrency != null &&
- receiveCurrency != null &&
- sendAmount != null &&
- sendAmount! >= Decimal.zero &&
- receiveAmount != null &&
- rate != null &&
- rate! >= Decimal.zero &&
- sendCurrency!.forExchange(exchange.name) != null &&
- receiveCurrency!.forExchange(exchange.name) != null &&
- warning.isEmpty;
- }
-
- String get warning {
- if (reversed) {
- if (_receiveCurrency != null && _receiveAmount != null) {
- if (_minReceiveAmount != null &&
- _receiveAmount! < _minReceiveAmount! &&
- _receiveAmount! > Decimal.zero) {
- return "Min receive amount ${_minReceiveAmount!.toString()} ${_receiveCurrency!.ticker.toUpperCase()}";
- } else if (_maxReceiveAmount != null &&
- _receiveAmount! > _maxReceiveAmount!) {
- return "Max receive amount ${_maxReceiveAmount!.toString()} ${_receiveCurrency!.ticker.toUpperCase()}";
- }
- }
- } else {
- if (_sendCurrency != null && _sendAmount != null) {
- if (_minSendAmount != null &&
- _sendAmount! < _minSendAmount! &&
- _sendAmount! > Decimal.zero) {
- return "Min send amount ${_minSendAmount!.toString()} ${_sendCurrency!.ticker.toUpperCase()}";
- } else if (_maxSendAmount != null && _sendAmount! > _maxSendAmount!) {
- return "Max send amount ${_maxSendAmount!.toString()} ${_sendCurrency!.ticker.toUpperCase()}";
- }
- }
- }
-
- return "";
- }
-
- //============================================================================
- // public state updaters
- //============================================================================
-
- Future updateExchange({
- required Exchange exchange,
- required bool shouldUpdateData,
- required bool shouldNotifyListeners,
- }) async {
- _exchange = exchange;
- if (shouldUpdateData) {
- await _updateRangesAndEstimate(
- shouldNotifyListeners: false,
- );
- }
-
- if (shouldNotifyListeners) {
- _notify();
- }
- }
-
- void setCurrencies(AggregateCurrency? from, AggregateCurrency? to) {
- _sendCurrency = from;
- _receiveCurrency = to;
- }
-
- void reset({
- required bool shouldNotifyListeners,
- }) {
- _exchange = null;
- _reversed = false;
- _rate = null;
- _sendAmount = null;
- _receiveAmount = null;
- _sendCurrency = null;
- _receiveCurrency = null;
- _minSendAmount = null;
- _minReceiveAmount = null;
- _maxSendAmount = null;
- _maxReceiveAmount = null;
-
- if (shouldNotifyListeners) {
- _notify();
- }
- }
-
- Future setSendAmountAndCalculateReceiveAmount(
- Decimal? newSendAmount,
- bool shouldNotifyListeners,
- ) async {
- if (newSendAmount == null) {
- // todo: check if this breaks things and stuff
- _receiveAmount = null;
- _sendAmount = null;
- } else {
- if (newSendAmount <= Decimal.zero) {
- _receiveAmount = Decimal.zero;
- }
-
- _sendAmount = newSendAmount;
- _reversed = false;
-
- await _updateRangesAndEstimate(
- shouldNotifyListeners: false,
- );
- }
-
- if (shouldNotifyListeners) {
- _notify();
- }
- }
-
- Future setReceivingAmountAndCalculateSendAmount(
- Decimal? newReceiveAmount,
- bool shouldNotifyListeners,
- ) async {
- if (newReceiveAmount == null) {
- // todo: check if this breaks things and stuff
- _receiveAmount = null;
- _sendAmount = null;
- } else {
- if (newReceiveAmount <= Decimal.zero) {
- _sendAmount = Decimal.zero;
- }
-
- _receiveAmount = newReceiveAmount;
- _reversed = true;
-
- await _updateRangesAndEstimate(
- shouldNotifyListeners: false,
- );
- }
-
- if (shouldNotifyListeners) {
- _notify();
- }
- }
-
- Future updateSendCurrency(
- AggregateCurrency sendCurrency,
- bool shouldNotifyListeners,
- ) async {
- try {
- _sendCurrency = sendCurrency;
- _minSendAmount = null;
- _maxSendAmount = null;
-
- if (_receiveCurrency == null) {
- _rate = null;
- } else {
- await _updateRangesAndEstimate(
- shouldNotifyListeners: false,
- );
- }
- if (shouldNotifyListeners) {
- _notify();
- }
- } catch (e, s) {
- Logging.instance.log("$e\n$s", level: LogLevel.Error);
- }
- }
-
- Future updateReceivingCurrency(
- AggregateCurrency receiveCurrency,
- bool shouldNotifyListeners,
- ) async {
- try {
- _receiveCurrency = receiveCurrency;
- _minReceiveAmount = null;
- _maxReceiveAmount = null;
-
- if (_sendCurrency == null) {
- _rate = null;
- } else {
- await _updateRangesAndEstimate(
- shouldNotifyListeners: false,
- );
- }
- if (shouldNotifyListeners) {
- _notify();
- }
- } catch (e, s) {
- Logging.instance.log("$e\n$s", level: LogLevel.Error);
- }
- }
-
- Future swap({
- required bool shouldNotifyListeners,
- }) async {
- final Decimal? temp = sendAmount;
- _sendAmount = receiveAmount;
- _receiveAmount = temp;
-
- _minSendAmount = null;
- _maxSendAmount = null;
- _minReceiveAmount = null;
- _maxReceiveAmount = null;
-
- final AggregateCurrency? tmp = sendCurrency;
- _sendCurrency = receiveCurrency;
- _receiveCurrency = tmp;
-
- await _updateRangesAndEstimate(
- shouldNotifyListeners: false,
- );
-
- if (shouldNotifyListeners) {
- _notify();
- }
- }
-
- Future refresh() => _updateRangesAndEstimate(
- shouldNotifyListeners: true,
- );
-
- //============================================================================
- // private state updaters
- //============================================================================
-
- Future _updateRangesAndEstimate({
- required bool shouldNotifyListeners,
- }) async {
- try {
- switch (exchange.name) {
- case ChangeNowExchange.exchangeName:
- if (!_exchangeSupported(
- exchangeName: exchange.name,
- sendCurrency: sendCurrency,
- receiveCurrency: receiveCurrency,
- exchangeRateType: exchangeRateType,
- )) {
- _exchange = MajesticBankExchange.instance;
- }
- break;
- case MajesticBankExchange.exchangeName:
- if (!_exchangeSupported(
- exchangeName: exchange.name,
- sendCurrency: sendCurrency,
- receiveCurrency: receiveCurrency,
- exchangeRateType: exchangeRateType,
- )) {
- _exchange = ChangeNowExchange.instance;
- }
- break;
- }
-
- await _updateRanges(shouldNotifyListeners: false);
- await _updateEstimate(shouldNotifyListeners: false);
- if (shouldNotifyListeners) {
- _notify();
- }
- } catch (_) {
- //
- }
- }
-
- Future _updateRanges({
- required bool shouldNotifyListeners,
- }) async {
- // if (exchange?.name == SimpleSwapExchange.exchangeName) {
- // reversed = false;
- // }
- final _send = sendCurrency;
- final _receive = receiveCurrency;
- if (_send == null || _receive == null) {
- Logging.instance.log(
- "Tried to $runtimeType.updateRanges where ( $_send || $_receive) for: $exchange",
- level: LogLevel.Info,
- );
- return;
- }
- final response = await exchange.getRange(
- _send.ticker,
- _receive.ticker,
- exchangeRateType == ExchangeRateType.fixed,
- );
-
- if (response.value == null) {
- Logging.instance.log(
- "Tried to $runtimeType.updateRanges for: $exchange where response: $response",
- level: LogLevel.Info,
- );
- return;
- }
- final responseReversed = await exchange.getRange(
- _receive.ticker,
- _send.ticker,
- exchangeRateType == ExchangeRateType.fixed,
- );
-
- if (responseReversed.value == null) {
- Logging.instance.log(
- "Tried to $runtimeType.updateRanges for: $exchange where response: $responseReversed",
- level: LogLevel.Info,
- );
- return;
- }
-
- final range = response.value!;
- final rangeReversed = responseReversed.value!;
-
- _minSendAmount = range.min;
- _maxSendAmount = range.max;
- _minReceiveAmount = rangeReversed.min;
- _maxReceiveAmount = rangeReversed.max;
-
- //todo: check if print needed
- // debugPrint(
- // "updated range for: $exchange for $_fromTicker-$_toTicker: $range");
-
- if (shouldNotifyListeners) {
- _notify();
- }
- }
-
- Future _updateEstimate({
- required bool shouldNotifyListeners,
- }) async {
- // if (exchange?.name == SimpleSwapExchange.exchangeName) {
- // reversed = false;
- // }
- final amount = reversed ? receiveAmount : sendAmount;
- if (sendCurrency == null ||
- receiveCurrency == null ||
- amount == null ||
- amount <= Decimal.zero) {
- Logging.instance.log(
- "Tried to $runtimeType.updateEstimate for: $exchange where (from: $sendCurrency || to: $receiveCurrency || amount: $amount)",
- level: LogLevel.Info,
- );
- return;
- }
- final response = await exchange.getEstimate(
- sendCurrency!.ticker,
- receiveCurrency!.ticker,
- amount,
- exchangeRateType == ExchangeRateType.fixed,
- reversed,
- );
-
- if (response.value == null) {
- Logging.instance.log(
- "Tried to $runtimeType.updateEstimate for: $exchange where response: $response",
- level: LogLevel.Info,
- );
- return;
- }
-
- _estimate = response.value!;
-
- if (reversed) {
- _sendAmount = _estimate!.estimatedAmount;
- } else {
- _receiveAmount = _estimate!.estimatedAmount;
- }
-
- _rate =
- (receiveAmount! / sendAmount!).toDecimal(scaleOnInfinitePrecision: 12);
-
- //todo: check if print needed
- // debugPrint(
- // "updated estimate for: $exchange for $fromTicker-$toTicker: $estimate");
-
- if (shouldNotifyListeners) {
- _notify();
- }
- }
-
- //============================================================================
-
- void _notify() {
- debugPrint("ExFState NOTIFY: ${toString()}");
- notifyListeners();
- }
-
- bool _exchangeSupported({
- required String exchangeName,
- required AggregateCurrency? sendCurrency,
- required AggregateCurrency? receiveCurrency,
- required ExchangeRateType exchangeRateType,
- }) {
- final send = sendCurrency?.forExchange(exchangeName);
- if (send == null) return false;
-
- final rcv = receiveCurrency?.forExchange(exchangeName);
- if (rcv == null) return false;
-
- if (exchangeRateType == ExchangeRateType.fixed) {
- return send.supportsFixedRate && rcv.supportsFixedRate;
- } else {
- return send.supportsEstimatedRate && rcv.supportsEstimatedRate;
- }
- }
-
- @override
- String toString() {
- return "{"
- "\n\t exchange: $exchange,"
- "\n\t exchangeRateType: $exchangeRateType,"
- "\n\t sendCurrency: $sendCurrency,"
- "\n\t receiveCurrency: $receiveCurrency,"
- "\n\t rate: $rate,"
- "\n\t reversed: $reversed,"
- "\n\t sendAmount: $sendAmount,"
- "\n\t receiveAmount: $receiveAmount,"
- "\n\t estimate: $estimate,"
- "\n\t minSendAmount: $minSendAmount,"
- "\n\t maxSendAmount: $maxSendAmount,"
- "\n\t minReceiveAmount: $minReceiveAmount,"
- "\n\t maxReceiveAmount: $maxReceiveAmount,"
- "\n\t canExchange: $canExchange,"
- "\n\t warning: $warning,"
- "\n}";
- }
-}
diff --git a/lib/models/exchange/incomplete_exchange.dart b/lib/models/exchange/incomplete_exchange.dart
index 864a25490..2680b24e0 100644
--- a/lib/models/exchange/incomplete_exchange.dart
+++ b/lib/models/exchange/incomplete_exchange.dart
@@ -1,5 +1,6 @@
import 'package:decimal/decimal.dart';
import 'package:flutter/foundation.dart';
+import 'package:stackwallet/models/exchange/response_objects/estimate.dart';
import 'package:stackwallet/models/exchange/response_objects/trade.dart';
import 'package:stackwallet/utilities/enums/exchange_rate_type_enum.dart';
@@ -39,13 +40,13 @@ class IncompleteExchangeModel extends ChangeNotifier {
}
}
- String? _rateId;
+ Estimate? _estimate;
- String? get rateId => _rateId;
+ Estimate? get estimate => _estimate;
- set rateId(String? rateId) {
- if (_rateId != rateId) {
- _rateId = rateId;
+ set estimate(Estimate? estimate) {
+ if (_estimate != estimate) {
+ _estimate = estimate;
notifyListeners();
}
}
@@ -70,6 +71,6 @@ class IncompleteExchangeModel extends ChangeNotifier {
required this.rateType,
required this.reversed,
required this.walletInitiated,
- String? rateId,
- }) : _rateId = rateId;
+ Estimate? estimate,
+ }) : _estimate = estimate;
}
diff --git a/lib/models/exchange/response_objects/estimate.dart b/lib/models/exchange/response_objects/estimate.dart
index 7df490079..9284c8340 100644
--- a/lib/models/exchange/response_objects/estimate.dart
+++ b/lib/models/exchange/response_objects/estimate.dart
@@ -7,6 +7,8 @@ class Estimate {
final bool reversed;
final String? warningMessage;
final String? rateId;
+ final String exchangeProvider;
+ final String? kycRating;
Estimate({
required this.estimatedAmount,
@@ -14,9 +16,15 @@ class Estimate {
required this.reversed,
this.warningMessage,
this.rateId,
+ required this.exchangeProvider,
+ this.kycRating,
});
- factory Estimate.fromMap(Map map) {
+ factory Estimate.fromMap(
+ Map map, {
+ required String exchangeProvider,
+ String? kycRating,
+ }) {
try {
return Estimate(
estimatedAmount: Decimal.parse(map["estimatedAmount"] as String),
@@ -24,6 +32,8 @@ class Estimate {
reversed: map["reversed"] as bool,
warningMessage: map["warningMessage"] as String?,
rateId: map["rateId"] as String?,
+ exchangeProvider: exchangeProvider,
+ kycRating: kycRating,
);
} catch (e, s) {
Logging.instance.log("Estimate.fromMap(): $e\n$s", level: LogLevel.Error);
@@ -38,6 +48,8 @@ class Estimate {
"reversed": reversed,
"warningMessage": warningMessage,
"rateId": rateId,
+ "exchangeProvider": exchangeProvider,
+ "kycRating": kycRating,
};
}
diff --git a/lib/pages/exchange_view/exchange_form.dart b/lib/pages/exchange_view/exchange_form.dart
index 389bedd05..b9cd4023f 100644
--- a/lib/pages/exchange_view/exchange_form.dart
+++ b/lib/pages/exchange_view/exchange_form.dart
@@ -5,10 +5,11 @@ import 'package:flutter/material.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:flutter_svg/flutter_svg.dart';
import 'package:flutter_svg/svg.dart';
-import 'package:intl/intl.dart';
import 'package:isar/isar.dart';
import 'package:stackwallet/models/exchange/aggregate_currency.dart';
import 'package:stackwallet/models/exchange/incomplete_exchange.dart';
+import 'package:stackwallet/models/exchange/response_objects/estimate.dart';
+import 'package:stackwallet/models/exchange/response_objects/range.dart';
import 'package:stackwallet/models/isar/exchange_cache/currency.dart';
import 'package:stackwallet/models/isar/exchange_cache/pair.dart';
import 'package:stackwallet/models/isar/models/ethereum/eth_contract.dart';
@@ -21,6 +22,8 @@ import 'package:stackwallet/pages_desktop_specific/desktop_exchange/exchange_ste
import 'package:stackwallet/providers/providers.dart';
import 'package:stackwallet/services/exchange/change_now/change_now_exchange.dart';
import 'package:stackwallet/services/exchange/exchange_data_loading_service.dart';
+import 'package:stackwallet/services/exchange/majestic_bank/majestic_bank_exchange.dart';
+import 'package:stackwallet/services/exchange/trocador/trocador_exchange.dart';
import 'package:stackwallet/utilities/assets.dart';
import 'package:stackwallet/utilities/constants.dart';
import 'package:stackwallet/utilities/enums/coin_enum.dart';
@@ -39,6 +42,9 @@ import 'package:stackwallet/widgets/rounded_white_container.dart';
import 'package:stackwallet/widgets/stack_dialog.dart';
import 'package:stackwallet/widgets/textfields/exchange_textfield.dart';
import 'package:tuple/tuple.dart';
+import 'package:uuid/uuid.dart';
+
+import '../../services/exchange/exchange_response.dart';
class ExchangeForm extends ConsumerStatefulWidget {
const ExchangeForm({
@@ -61,6 +67,12 @@ class _ExchangeFormState extends ConsumerState {
late final Coin? coin;
late final bool walletInitiated;
+ final exchanges = [
+ MajesticBankExchange.instance,
+ ChangeNowExchange.instance,
+ TrocadorExchange.instance,
+ ];
+
late final TextEditingController _sendController;
late final TextEditingController _receiveController;
final isDesktop = Util.isDesktop;
@@ -70,7 +82,7 @@ class _ExchangeFormState extends ConsumerState {
bool _swapLock = false;
// todo: check and adjust this value?
- static const _valueCheckInterval = Duration(milliseconds: 300);
+ static const _valueCheckInterval = Duration(milliseconds: 1500);
Future showUpdatingExchangeRate({
required Future whileFuture,
@@ -105,16 +117,17 @@ class _ExchangeFormState extends ConsumerState {
}
Timer? _sendFieldOnChangedTimer;
- void sendFieldOnChanged(String value) async {
+ void sendFieldOnChanged(String value) {
if (_sendFocusNode.hasFocus) {
_sendFieldOnChangedTimer?.cancel();
_sendFieldOnChangedTimer = Timer(_valueCheckInterval, () async {
final newFromAmount = _localizedStringToNum(value);
- await ref
- .read(exchangeFormStateProvider)
- .setSendAmountAndCalculateReceiveAmount(newFromAmount, true);
+ ref.read(efSendAmountProvider.notifier).state = newFromAmount;
+ if (!_swapLock && !ref.read(efReversedProvider)) {
+ unawaited(update());
+ }
});
}
}
@@ -126,9 +139,10 @@ class _ExchangeFormState extends ConsumerState {
_receiveFieldOnChangedTimer = Timer(_valueCheckInterval, () async {
final newToAmount = _localizedStringToNum(value);
- await ref
- .read(exchangeFormStateProvider)
- .setReceivingAmountAndCalculateSendAmount(newToAmount, true);
+ ref.read(efReceiveAmountProvider.notifier).state = newToAmount;
+ if (!_swapLock && ref.read(efReversedProvider)) {
+ unawaited(update());
+ }
});
}
@@ -137,17 +151,29 @@ class _ExchangeFormState extends ConsumerState {
return null;
}
try {
- final numFromLocalised = NumberFormat.decimalPattern(
- ref.read(localeServiceChangeNotifierProvider).locale)
- .parse(value);
- return Decimal.tryParse(numFromLocalised.toString());
+ // wtf Dart?????
+ // This turns "99999999999999999999" into 100000000000000000000.0
+ // final numFromLocalised = NumberFormat.decimalPattern(
+ // ref.read(localeServiceChangeNotifierProvider).locale)
+ // .parse(value);
+ // return Decimal.tryParse(numFromLocalised.toString());
+
+ try {
+ return Decimal.parse(value);
+ } catch (_) {
+ try {
+ return Decimal.parse(value.replaceAll(",", "."));
+ } catch (_) {
+ rethrow;
+ }
+ }
} catch (_) {
return null;
}
}
Future _getAggregateCurrency(Currency currency) async {
- final rateType = ref.read(exchangeFormStateProvider).exchangeRateType;
+ final rateType = ref.read(efRateTypeProvider);
final currencies = await ExchangeDataLoadingService.instance.isar.currencies
.filter()
.group((q) => rateType == ExchangeRateType.fixed
@@ -178,8 +204,8 @@ class _ExchangeFormState extends ConsumerState {
}
void selectSendCurrency() async {
- final type = (ref.read(exchangeFormStateProvider).exchangeRateType);
- final fromTicker = ref.read(exchangeFormStateProvider).fromTicker ?? "";
+ final type = ref.read(efRateTypeProvider);
+ final fromTicker = ref.read(efCurrencyPairProvider).send?.ticker ?? "";
if (walletInitiated) {
if (widget.contract != null &&
@@ -194,24 +220,26 @@ class _ExchangeFormState extends ConsumerState {
}
final selectedCurrency = await _showCurrencySelectionSheet(
- willChange: ref.read(exchangeFormStateProvider).sendCurrency?.ticker,
+ willChange: ref.read(efCurrencyPairProvider).send?.ticker,
willChangeIsSend: true,
- paired: ref.read(exchangeFormStateProvider).receiveCurrency?.ticker,
+ paired: ref.read(efCurrencyPairProvider).receive?.ticker,
isFixedRate: type == ExchangeRateType.fixed,
);
if (selectedCurrency != null) {
await showUpdatingExchangeRate(
whileFuture: _getAggregateCurrency(selectedCurrency).then(
- (aggregateSelected) => ref
- .read(exchangeFormStateProvider)
- .updateSendCurrency(aggregateSelected, true)),
+ (aggregateSelected) => ref.read(efCurrencyPairProvider).setSend(
+ aggregateSelected,
+ notifyListeners: true,
+ ),
+ ),
);
}
}
void selectReceiveCurrency() async {
- final toTicker = ref.read(exchangeFormStateProvider).toTicker ?? "";
+ final toTicker = ref.read(efCurrencyPairProvider).receive?.ticker ?? "";
if (walletInitiated &&
toTicker.toLowerCase() == coin!.ticker.toLowerCase()) {
// do not allow changing away from wallet coin
@@ -219,19 +247,20 @@ class _ExchangeFormState extends ConsumerState {
}
final selectedCurrency = await _showCurrencySelectionSheet(
- willChange: ref.read(exchangeFormStateProvider).receiveCurrency?.ticker,
+ willChange: ref.read(efCurrencyPairProvider).receive?.ticker,
willChangeIsSend: false,
- paired: ref.read(exchangeFormStateProvider).sendCurrency?.ticker,
- isFixedRate: ref.read(exchangeFormStateProvider).exchangeRateType ==
- ExchangeRateType.fixed,
+ paired: ref.read(efCurrencyPairProvider).send?.ticker,
+ isFixedRate: ref.read(efRateTypeProvider) == ExchangeRateType.fixed,
);
if (selectedCurrency != null) {
await showUpdatingExchangeRate(
whileFuture: _getAggregateCurrency(selectedCurrency).then(
- (aggregateSelected) => ref
- .read(exchangeFormStateProvider)
- .updateReceivingCurrency(aggregateSelected, true)),
+ (aggregateSelected) => ref.read(efCurrencyPairProvider).setReceive(
+ aggregateSelected,
+ notifyListeners: true,
+ ),
+ ),
);
}
}
@@ -241,10 +270,25 @@ class _ExchangeFormState extends ConsumerState {
_sendFocusNode.unfocus();
_receiveFocusNode.unfocus();
- await showUpdatingExchangeRate(
- whileFuture:
- ref.read(exchangeFormStateProvider).swap(shouldNotifyListeners: true),
- );
+ final temp = ref.read(efCurrencyPairProvider).send;
+ ref.read(efCurrencyPairProvider).setSend(
+ ref.read(efCurrencyPairProvider).receive,
+ notifyListeners: true,
+ );
+ ref.read(efCurrencyPairProvider).setReceive(
+ temp,
+ notifyListeners: true,
+ );
+
+ // final reversed = ref.read(efReversedProvider);
+
+ final amount = ref.read(efSendAmountProvider);
+ ref.read(efSendAmountProvider.notifier).state =
+ ref.read(efReceiveAmountProvider);
+
+ ref.read(efReceiveAmountProvider.notifier).state = amount;
+
+ unawaited(update());
_swapLock = false;
}
@@ -331,85 +375,20 @@ class _ExchangeFormState extends ConsumerState {
}
}
- void onRateTypeChanged(ExchangeRateType newType) async {
+ void onRateTypeChanged(ExchangeRateType newType) {
_receiveFocusNode.unfocus();
_sendFocusNode.unfocus();
- await showUpdatingExchangeRate(
- whileFuture: _onRateTypeChangedFuture(newType),
- );
- }
-
- Future _onRateTypeChangedFuture(ExchangeRateType newType) async {
- ref.read(exchangeFormStateProvider).exchangeRateType = newType;
-
- final fromTicker = ref.read(exchangeFormStateProvider).fromTicker ?? "-";
- final toTicker = ref.read(exchangeFormStateProvider).toTicker ?? "-";
-
- ref.read(exchangeFormStateProvider).reversed = false;
-
- if (!(toTicker == "-" || fromTicker == "-")) {
- // final available = await ExchangeDataLoadingService.instance.isar.pairs
- // .where()
- // .exchangeNameEqualTo(
- // ref.read(currentExchangeNameStateProvider.state).state)
- // .filter()
- // .fromEqualTo(fromTicker)
- // .and()
- // .toEqualTo(toTicker)
- // .findAll();
- await ref.read(exchangeFormStateProvider).refresh();
-
- // if (available.isNotEmpty) {
- // final availableCurrencies = await ExchangeDataLoadingService
- // .instance.isar.currencies
- // .where()
- // .exchangeNameEqualTo(
- // ref.read(currentExchangeNameStateProvider.state).state)
- // .filter()
- // .tickerEqualTo(fromTicker)
- // .or()
- // .tickerEqualTo(toTicker)
- // .findAll();
- //
- // if (availableCurrencies.length > 1) {
- // final from =
- // availableCurrencies.firstWhere((e) => e.ticker == fromTicker);
- // final to =
- // availableCurrencies.firstWhere((e) => e.ticker == toTicker);
- //
- // final newFromAmount = Decimal.tryParse(_sendController.text);
- // ref.read(exchangeFormStateProvider).receiveAmount = newFromAmount;
- // if (newFromAmount == null) {
- // _receiveController.text = "";
- // }
- //
- // await ref
- // .read(exchangeFormStateProvider)
- // .updateReceivingCurrency(to, false);
- // await ref
- // .read(exchangeFormStateProvider)
- // .updateSendCurrency(from, true);
- //
- // _receiveController.text =
- // ref.read(exchangeFormStateProvider).toAmountString.isEmpty
- // ? "-"
- // : ref.read(exchangeFormStateProvider).toAmountString;
- // if (mounted) {
- // Navigator.of(context, rootNavigator: isDesktop).pop();
- // }
- // return;
- // }
- // }
- }
+ ref.read(efRateTypeProvider.notifier).state = newType;
+ update();
}
void onExchangePressed() async {
- final rateType = ref.read(exchangeFormStateProvider).exchangeRateType;
- final fromTicker = ref.read(exchangeFormStateProvider).fromTicker ?? "";
- final toTicker = ref.read(exchangeFormStateProvider).toTicker ?? "";
- final sendAmount = ref.read(exchangeFormStateProvider).sendAmount!;
- final estimate = ref.read(exchangeFormStateProvider).estimate!;
+ final rateType = ref.read(efRateTypeProvider);
+ final fromTicker = ref.read(efCurrencyPairProvider).send?.ticker ?? "";
+ final toTicker = ref.read(efCurrencyPairProvider).receive?.ticker ?? "";
+ final estimate = ref.read(efEstimateProvider)!;
+ final sendAmount = ref.read(efSendAmountProvider)!;
if (rateType == ExchangeRateType.fixed && toTicker.toUpperCase() == "WOW") {
await showDialog(
@@ -426,10 +405,16 @@ class _ExchangeFormState extends ConsumerState {
String rate;
+ final amountToSend =
+ estimate.reversed ? estimate.estimatedAmount : sendAmount;
+ final amountToReceive = estimate.reversed
+ ? ref.read(efReceiveAmountProvider)!
+ : estimate.estimatedAmount;
+
switch (rateType) {
case ExchangeRateType.estimated:
rate =
- "1 ${fromTicker.toUpperCase()} ~${(estimate.estimatedAmount / sendAmount).toDecimal(scaleOnInfinitePrecision: 8).toStringAsFixed(8)} ${toTicker.toUpperCase()}";
+ "1 ${fromTicker.toUpperCase()} ~${(amountToReceive / sendAmount).toDecimal(scaleOnInfinitePrecision: 8).toStringAsFixed(8)} ${toTicker.toUpperCase()}";
break;
case ExchangeRateType.fixed:
bool? shouldCancel;
@@ -541,7 +526,9 @@ class _ExchangeFormState extends ConsumerState {
return;
}
rate =
- "1 ${fromTicker.toUpperCase()} ~${ref.read(exchangeFormStateProvider).rate!.toStringAsFixed(8)} ${toTicker.toUpperCase()}";
+ "1 ${fromTicker.toUpperCase()} ~${(amountToReceive / amountToSend).toDecimal(
+ scaleOnInfinitePrecision: 12,
+ ).toStringAsFixed(8)} ${toTicker.toUpperCase()}";
break;
}
@@ -549,12 +536,10 @@ class _ExchangeFormState extends ConsumerState {
sendTicker: fromTicker.toUpperCase(),
receiveTicker: toTicker.toUpperCase(),
rateInfo: rate,
- sendAmount: estimate.reversed ? estimate.estimatedAmount : sendAmount,
- receiveAmount: estimate.reversed
- ? ref.read(exchangeFormStateProvider).receiveAmount!
- : estimate.estimatedAmount,
+ sendAmount: amountToSend,
+ receiveAmount: amountToReceive,
rateType: rateType,
- rateId: estimate.rateId,
+ estimate: estimate,
reversed: estimate.reversed,
walletInitiated: walletInitiated,
);
@@ -622,8 +607,8 @@ class _ExchangeFormState extends ConsumerState {
}
String? ticker = isSend
- ? ref.read(exchangeFormStateProvider).fromTicker
- : ref.read(exchangeFormStateProvider).toTicker;
+ ? ref.read(efCurrencyPairProvider).send?.ticker
+ : ref.read(efCurrencyPairProvider).receive?.ticker;
if (ticker == null) {
return false;
@@ -632,6 +617,97 @@ class _ExchangeFormState extends ConsumerState {
return coin.ticker.toUpperCase() == ticker.toUpperCase();
}
+ Future update() async {
+ final uuid = const Uuid().v1();
+ _latestUuid = uuid;
+ _addUpdate(uuid);
+ for (final exchange in exchanges) {
+ ref.read(efEstimatesListProvider(exchange.name).notifier).state = null;
+ }
+
+ final reversed = ref.read(efReversedProvider);
+ final amount = reversed
+ ? ref.read(efReceiveAmountProvider)
+ : ref.read(efSendAmountProvider);
+
+ final pair = ref.read(efCurrencyPairProvider);
+ if (amount == null ||
+ amount <= Decimal.zero ||
+ pair.send == null ||
+ pair.receive == null) {
+ _removeUpdate(uuid);
+ return;
+ }
+ final rateType = ref.read(efRateTypeProvider);
+ final Map>, Range?>>
+ results = {};
+
+ for (final exchange in exchanges) {
+ final sendCurrency = pair.send?.forExchange(exchange.name);
+ final receiveCurrency = pair.receive?.forExchange(exchange.name);
+
+ if (sendCurrency != null && receiveCurrency != null) {
+ final rangeResponse = await exchange.getRange(
+ reversed ? receiveCurrency.ticker : sendCurrency.ticker,
+ reversed ? sendCurrency.ticker : receiveCurrency.ticker,
+ rateType == ExchangeRateType.fixed,
+ );
+
+ final estimateResponse = await exchange.getEstimates(
+ sendCurrency.ticker,
+ receiveCurrency.ticker,
+ amount,
+ rateType == ExchangeRateType.fixed,
+ reversed,
+ );
+
+ results.addAll(
+ {
+ exchange.name: Tuple2(
+ estimateResponse,
+ rangeResponse.value,
+ ),
+ },
+ );
+ }
+ }
+
+ for (final exchange in exchanges) {
+ if (uuid == _latestUuid) {
+ ref.read(efEstimatesListProvider(exchange.name).notifier).state =
+ results[exchange.name];
+ }
+ }
+
+ _removeUpdate(uuid);
+ }
+
+ String? _latestUuid;
+ final Set _uuids = {};
+
+ void _addUpdate(String uuid) {
+ _uuids.add(uuid);
+ ref.read(efRefreshingProvider.notifier).state = true;
+ }
+
+ void _removeUpdate(String uuid) {
+ _uuids.remove(uuid);
+ if (_uuids.isEmpty) {
+ WidgetsBinding.instance.addPostFrameCallback((_) {
+ ref.read(efRefreshingProvider.notifier).state = false;
+ });
+ }
+ }
+
+ void updateSend(Estimate? estimate) {
+ ref.read(efSendAmountProvider.notifier).state = estimate?.estimatedAmount;
+ }
+
+ void updateReceive(Estimate? estimate) {
+ ref.read(efReceiveAmountProvider.notifier).state =
+ estimate?.estimatedAmount;
+ }
+
@override
void initState() {
_sendController = TextEditingController();
@@ -641,9 +717,40 @@ class _ExchangeFormState extends ConsumerState {
coin = widget.coin;
walletInitiated = walletId != null && coin != null;
+ _sendFocusNode.addListener(() {
+ if (_sendFocusNode.hasFocus) {
+ final reversed = ref.read(efReversedProvider);
+ WidgetsBinding.instance.addPostFrameCallback((_) {
+ ref.read(efReversedProvider.notifier).state = false;
+ if (reversed == true) {
+ update();
+ }
+ });
+ }
+ });
+ _receiveFocusNode.addListener(() {
+ if (_receiveFocusNode.hasFocus &&
+ ref.read(efExchangeProvider).name != ChangeNowExchange.exchangeName) {
+ final reversed = ref.read(efReversedProvider);
+ WidgetsBinding.instance.addPostFrameCallback((_) {
+ ref.read(efReversedProvider.notifier).state = true;
+ if (reversed != true) {
+ update();
+ }
+ });
+ }
+ });
+
if (walletInitiated) {
WidgetsBinding.instance.addPostFrameCallback((timeStamp) {
- ref.read(exchangeFormStateProvider).reset(shouldNotifyListeners: true);
+ ref.read(efSendAmountProvider.notifier).state = null;
+ ref.read(efReceiveAmountProvider.notifier).state = null;
+ ref.read(efReversedProvider.notifier).state = false;
+ ref.read(efRefreshingProvider.notifier).state = false;
+ ref.read(efCurrencyPairProvider).setSend(null, notifyListeners: true);
+ ref
+ .read(efCurrencyPairProvider)
+ .setReceive(null, notifyListeners: true);
ExchangeDataLoadingService.instance
.getAggregateCurrency(
widget.contract == null ? coin!.ticker : widget.contract!.symbol,
@@ -652,17 +759,17 @@ class _ExchangeFormState extends ConsumerState {
)
.then((value) {
if (value != null) {
- ref.read(exchangeFormStateProvider).updateSendCurrency(value, true);
+ ref.read(efCurrencyPairProvider).setSend(
+ value,
+ notifyListeners: true,
+ );
}
});
});
} else {
- _sendController.text =
- ref.read(exchangeFormStateProvider).fromAmountString;
- _receiveController.text =
- ref.read(exchangeFormStateProvider).toAmountString;
WidgetsBinding.instance.addPostFrameCallback((timeStamp) {
- ref.read(exchangeFormStateProvider).refresh();
+ _sendController.text = ref.read(efSendAmountStringProvider);
+ _receiveController.text = ref.read(efReceiveAmountStringProvider);
});
}
@@ -673,6 +780,8 @@ class _ExchangeFormState extends ConsumerState {
void dispose() {
_receiveController.dispose();
_sendController.dispose();
+ _receiveFocusNode.dispose();
+ _sendFocusNode.dispose();
super.dispose();
}
@@ -680,34 +789,42 @@ class _ExchangeFormState extends ConsumerState {
Widget build(BuildContext context) {
debugPrint("BUILD: $runtimeType");
- final rateType = ref.watch(
- exchangeFormStateProvider.select((value) => value.exchangeRateType));
+ final rateType = ref.watch(efRateTypeProvider);
final isEstimated = rateType == ExchangeRateType.estimated;
- ref.listen(
- exchangeFormStateProvider.select((value) => value.toAmountString),
- (previous, String next) {
+ ref.listen(efReceiveAmountStringProvider, (previous, String next) {
if (!_receiveFocusNode.hasFocus) {
_receiveController.text = isEstimated && next.isEmpty ? "-" : next;
- if (_swapLock) {
- _sendController.text =
- ref.read(exchangeFormStateProvider).fromAmountString;
- }
+ // if (_swapLock) {
+ _sendController.text = ref.read(efSendAmountStringProvider);
+ // }
}
});
- ref.listen(
- exchangeFormStateProvider.select((value) => value.fromAmountString),
- (previous, String next) {
+ ref.listen(efSendAmountStringProvider, (previous, String next) {
if (!_sendFocusNode.hasFocus) {
_sendController.text = next;
- if (_swapLock) {
- _receiveController.text = isEstimated
- ? ref.read(exchangeFormStateProvider).toAmountString.isEmpty
- ? "-"
- : ref.read(exchangeFormStateProvider).toAmountString
- : ref.read(exchangeFormStateProvider).toAmountString;
- }
+ // if (_swapLock) {
+ _receiveController.text =
+ isEstimated && ref.read(efReceiveAmountStringProvider).isEmpty
+ ? "-"
+ : ref.read(efReceiveAmountStringProvider);
+ // }
+ }
+ });
+
+ ref.listen(efEstimateProvider.notifier, (previous, next) {
+ final estimate = (next as StateController).state;
+ if (ref.read(efReversedProvider)) {
+ updateSend(estimate);
+ } else {
+ updateReceive(estimate);
+ }
+ });
+
+ ref.listen(efCurrencyPairProvider, (previous, next) {
+ if (!_swapLock) {
+ update();
}
});
@@ -725,8 +842,9 @@ class _ExchangeFormState extends ConsumerState {
height: isDesktop ? 10 : 4,
),
ExchangeTextField(
- key: Key(
- "exchangeTextFieldKeyFor_${Theme.of(context).extension()!.themeType.name}"),
+ key: Key("exchangeTextFieldKeyFor_"
+ "${Theme.of(context).extension()!.themeType.name}"
+ "${ref.watch(efCurrencyPairProvider.select((value) => value.send?.ticker))}"),
controller: _sendController,
focusNode: _sendFocusNode,
textStyle: STextStyles.smallMed14(context).copyWith(
@@ -745,8 +863,8 @@ class _ExchangeFormState extends ConsumerState {
onChanged: sendFieldOnChanged,
onButtonTap: selectSendCurrency,
isWalletCoin: isWalletCoin(coin, true),
- currency: ref.watch(
- exchangeFormStateProvider.select((value) => value.sendCurrency)),
+ currency:
+ ref.watch(efCurrencyPairProvider.select((value) => value.send)),
),
SizedBox(
height: isDesktop ? 10 : 4,
@@ -754,17 +872,6 @@ class _ExchangeFormState extends ConsumerState {
SizedBox(
height: isDesktop ? 10 : 4,
),
- if (ref
- .watch(
- exchangeFormStateProvider.select((value) => value.warning))
- .isNotEmpty &&
- !ref.watch(
- exchangeFormStateProvider.select((value) => value.reversed)))
- Text(
- ref.watch(
- exchangeFormStateProvider.select((value) => value.warning)),
- style: STextStyles.errorSmall(context),
- ),
Row(
crossAxisAlignment: CrossAxisAlignment.end,
mainAxisAlignment: MainAxisAlignment.spaceBetween,
@@ -809,7 +916,7 @@ class _ExchangeFormState extends ConsumerState {
),
),
),
- )
+ ),
),
],
),
@@ -829,34 +936,24 @@ class _ExchangeFormState extends ConsumerState {
borderRadius: Constants.size.circularBorderRadius,
background:
Theme.of(context).extension()!.textFieldDefaultBG,
- onTap: () {
- if (!(ref.read(exchangeFormStateProvider).exchangeRateType ==
- ExchangeRateType.estimated) &&
- _receiveController.text == "-") {
- _receiveController.text = "";
- }
- },
+ onTap: rateType == ExchangeRateType.estimated &&
+ ref.watch(efExchangeProvider).name ==
+ ChangeNowExchange.exchangeName
+ ? null
+ : () {
+ if (_sendController.text == "-") {
+ _sendController.text = "";
+ }
+ },
onChanged: receiveFieldOnChanged,
onButtonTap: selectReceiveCurrency,
isWalletCoin: isWalletCoin(coin, true),
- currency: ref.watch(exchangeFormStateProvider
- .select((value) => value.receiveCurrency)),
- readOnly: (rateType) == ExchangeRateType.estimated &&
- ref.watch(exchangeFormStateProvider
- .select((value) => value.exchange.name)) ==
+ currency: ref
+ .watch(efCurrencyPairProvider.select((value) => value.receive)),
+ readOnly: rateType == ExchangeRateType.estimated &&
+ ref.watch(efExchangeProvider).name ==
ChangeNowExchange.exchangeName,
),
- if (ref
- .watch(
- exchangeFormStateProvider.select((value) => value.warning))
- .isNotEmpty &&
- ref.watch(
- exchangeFormStateProvider.select((value) => value.reversed)))
- Text(
- ref.watch(
- exchangeFormStateProvider.select((value) => value.warning)),
- style: STextStyles.errorSmall(context),
- ),
SizedBox(
height: isDesktop ? 20 : 12,
),
@@ -867,27 +964,27 @@ class _ExchangeFormState extends ConsumerState {
onChanged: onRateTypeChanged,
),
),
- // these reads should be watch
- if (ref.watch(exchangeFormStateProvider).sendAmount != null &&
- ref.watch(exchangeFormStateProvider).sendAmount != Decimal.zero)
- SizedBox(
- height: isDesktop ? 20 : 12,
- ),
- // these reads should be watch
- if (ref.watch(exchangeFormStateProvider).sendAmount != null &&
- ref.watch(exchangeFormStateProvider).sendAmount != Decimal.zero)
- ExchangeProviderOptions(
- fixedRate: rateType == ExchangeRateType.fixed,
- reversed: ref.watch(
- exchangeFormStateProvider.select((value) => value.reversed)),
- ),
+ AnimatedSize(
+ duration: const Duration(milliseconds: 300),
+ child: ref.watch(efSendAmountProvider) == null &&
+ ref.watch(efReceiveAmountProvider) == null
+ ? const SizedBox(
+ height: 0,
+ )
+ : Padding(
+ padding: EdgeInsets.only(top: isDesktop ? 20 : 12),
+ child: ExchangeProviderOptions(
+ fixedRate: rateType == ExchangeRateType.fixed,
+ reversed: ref.watch(efReversedProvider),
+ ),
+ ),
+ ),
SizedBox(
height: isDesktop ? 20 : 12,
),
PrimaryButton(
buttonHeight: isDesktop ? ButtonHeight.l : null,
- enabled: ref.watch(
- exchangeFormStateProvider.select((value) => value.canExchange)),
+ enabled: ref.watch(efCanExchangeProvider),
onPressed: onExchangePressed,
label: "Swap",
)
diff --git a/lib/pages/exchange_view/exchange_step_views/step_2_view.dart b/lib/pages/exchange_view/exchange_step_views/step_2_view.dart
index a32df6e74..315228e13 100644
--- a/lib/pages/exchange_view/exchange_step_views/step_2_view.dart
+++ b/lib/pages/exchange_view/exchange_step_views/step_2_view.dart
@@ -124,9 +124,8 @@ class _Step2ViewState extends ConsumerState {
@override
Widget build(BuildContext context) {
- final supportsRefund = ref.watch(
- exchangeFormStateProvider.select((value) => value.exchange.name)) !=
- MajesticBankExchange.exchangeName;
+ final supportsRefund =
+ ref.watch(efExchangeProvider).name != MajesticBankExchange.exchangeName;
return Background(
child: Scaffold(
diff --git a/lib/pages/exchange_view/exchange_step_views/step_3_view.dart b/lib/pages/exchange_view/exchange_step_views/step_3_view.dart
index 22c356b5d..1079621e6 100644
--- a/lib/pages/exchange_view/exchange_step_views/step_3_view.dart
+++ b/lib/pages/exchange_view/exchange_step_views/step_3_view.dart
@@ -52,9 +52,8 @@ class _Step3ViewState extends ConsumerState {
@override
Widget build(BuildContext context) {
- final supportsRefund = ref.watch(
- exchangeFormStateProvider.select((value) => value.exchange.name)) !=
- MajesticBankExchange.exchangeName;
+ final supportsRefund =
+ ref.watch(efExchangeProvider).name != MajesticBankExchange.exchangeName;
return Background(
child: Scaffold(
@@ -254,8 +253,7 @@ class _Step3ViewState extends ConsumerState {
final ExchangeResponse response =
await ref
- .read(exchangeFormStateProvider)
- .exchange
+ .read(efExchangeProvider)
.createTrade(
from: model.sendTicker,
to: model.receiveTicker,
@@ -271,24 +269,26 @@ class _Step3ViewState extends ConsumerState {
? model.refundAddress!
: "",
refundExtraId: "",
- rateId: model.rateId,
+ estimate: model.estimate,
reversed: model.reversed,
);
if (response.value == null) {
if (mounted) {
Navigator.of(context).pop();
- }
- unawaited(showDialog(
- context: context,
- barrierDismissible: true,
- builder: (_) => StackDialog(
- title: "Failed to create trade",
- message:
- response.exception?.toString(),
- ),
- ));
+ unawaited(
+ showDialog(
+ context: context,
+ barrierDismissible: true,
+ builder: (_) => StackDialog(
+ title: "Failed to create trade",
+ message: response.exception
+ ?.toString(),
+ ),
+ ),
+ );
+ }
return;
}
diff --git a/lib/pages/exchange_view/exchange_step_views/step_4_view.dart b/lib/pages/exchange_view/exchange_step_views/step_4_view.dart
index b7bdf9e46..094e343d8 100644
--- a/lib/pages/exchange_view/exchange_step_views/step_4_view.dart
+++ b/lib/pages/exchange_view/exchange_step_views/step_4_view.dart
@@ -70,10 +70,8 @@ class _Step4ViewState extends ConsumerState {
}
Future _updateStatus() async {
- final statusResponse = await ref
- .read(exchangeFormStateProvider)
- .exchange
- .updateTrade(model.trade!);
+ final statusResponse =
+ await ref.read(efExchangeProvider).updateTrade(model.trade!);
String status = "Waiting";
if (statusResponse.value != null) {
status = statusResponse.value!.status;
diff --git a/lib/pages/exchange_view/exchange_view.dart b/lib/pages/exchange_view/exchange_view.dart
index 61cf2fe94..a17764b91 100644
--- a/lib/pages/exchange_view/exchange_view.dart
+++ b/lib/pages/exchange_view/exchange_view.dart
@@ -37,7 +37,8 @@ class _ExchangeViewState extends ConsumerState {
ExchangeDataLoadingService.instance.onLoadingComplete = () {
WidgetsBinding.instance.addPostFrameCallback((timeStamp) async {
await ExchangeDataLoadingService.instance.setCurrenciesIfEmpty(
- ref.read(exchangeFormStateProvider),
+ ref.read(efCurrencyPairProvider),
+ ref.read(efRateTypeProvider),
);
setState(() {
_initialCachePopulationUnderway = false;
@@ -53,7 +54,8 @@ class _ExchangeViewState extends ConsumerState {
ExchangeDataLoadingService.instance.onLoadingComplete = () {
WidgetsBinding.instance.addPostFrameCallback((timeStamp) async {
await ExchangeDataLoadingService.instance.setCurrenciesIfEmpty(
- ref.read(exchangeFormStateProvider),
+ ref.read(efCurrencyPairProvider),
+ ref.read(efRateTypeProvider),
);
setState(() {
_initialCachePopulationUnderway = false;
diff --git a/lib/pages/exchange_view/sub_widgets/exchange_provider_option.dart b/lib/pages/exchange_view/sub_widgets/exchange_provider_option.dart
new file mode 100644
index 000000000..1212caf49
--- /dev/null
+++ b/lib/pages/exchange_view/sub_widgets/exchange_provider_option.dart
@@ -0,0 +1,349 @@
+import 'package:decimal/decimal.dart';
+import 'package:flutter/material.dart';
+import 'package:flutter_riverpod/flutter_riverpod.dart';
+import 'package:flutter_svg/svg.dart';
+import 'package:stackwallet/models/exchange/response_objects/estimate.dart';
+import 'package:stackwallet/providers/exchange/exchange_form_state_provider.dart';
+import 'package:stackwallet/providers/global/locale_provider.dart';
+import 'package:stackwallet/services/exchange/exchange.dart';
+import 'package:stackwallet/utilities/amount/amount.dart';
+import 'package:stackwallet/utilities/assets.dart';
+import 'package:stackwallet/utilities/enums/coin_enum.dart';
+import 'package:stackwallet/utilities/enums/exchange_rate_type_enum.dart';
+import 'package:stackwallet/utilities/logger.dart';
+import 'package:stackwallet/utilities/text_styles.dart';
+import 'package:stackwallet/utilities/theme/stack_colors.dart';
+import 'package:stackwallet/utilities/util.dart';
+import 'package:stackwallet/widgets/animated_text.dart';
+import 'package:stackwallet/widgets/conditional_parent.dart';
+import 'package:stackwallet/widgets/exchange/trocador/trocador_kyc_info_button.dart';
+import 'package:stackwallet/widgets/exchange/trocador/trocador_rating_type_enum.dart';
+
+class ExchangeOption extends ConsumerStatefulWidget {
+ const ExchangeOption({
+ Key? key,
+ required this.exchange,
+ required this.fixedRate,
+ required this.reversed,
+ }) : super(key: key);
+
+ final Exchange exchange;
+ final bool fixedRate;
+ final bool reversed;
+
+ @override
+ ConsumerState createState() => _ExchangeOptionState();
+}
+
+class _ExchangeOptionState extends ConsumerState {
+ final isDesktop = Util.isDesktop;
+
+ @override
+ Widget build(BuildContext context) {
+ final sendCurrency =
+ ref.watch(efCurrencyPairProvider.select((value) => value.send));
+ final receivingCurrency =
+ ref.watch(efCurrencyPairProvider.select((value) => value.receive));
+ final reversed = ref.watch(efReversedProvider);
+ final amount = reversed
+ ? ref.watch(efReceiveAmountProvider)
+ : ref.watch(efSendAmountProvider);
+
+ final data = ref.watch(efEstimatesListProvider(widget.exchange.name));
+ final estimates = data?.item1.value;
+
+ return AnimatedSize(
+ duration: const Duration(milliseconds: 500),
+ curve: Curves.easeInOutCubicEmphasized,
+ child: Builder(
+ builder: (_) {
+ if (ref.watch(efRefreshingProvider)) {
+ // show loading
+ return _ProviderOption(
+ exchange: widget.exchange,
+ estimate: null,
+ rateString: "",
+ loadingString: true,
+ );
+ } else if (sendCurrency != null &&
+ receivingCurrency != null &&
+ amount != null &&
+ amount > Decimal.zero) {
+ if (estimates != null && estimates.isNotEmpty) {
+ return Column(
+ mainAxisSize: MainAxisSize.min,
+ children: [
+ for (int i = 0; i < estimates.length; i++)
+ Builder(
+ builder: (context) {
+ final e = estimates[i];
+
+ int decimals;
+ try {
+ decimals = coinFromTickerCaseInsensitive(
+ receivingCurrency.ticker)
+ .decimals;
+ } catch (_) {
+ decimals = 8; // some reasonable alternative
+ }
+ Amount rate;
+ if (e.reversed) {
+ rate = (amount / e.estimatedAmount)
+ .toDecimal(scaleOnInfinitePrecision: 18)
+ .toAmount(fractionDigits: decimals);
+ } else {
+ rate = (e.estimatedAmount / amount)
+ .toDecimal(scaleOnInfinitePrecision: 18)
+ .toAmount(fractionDigits: decimals);
+ }
+
+ final rateString =
+ "1 ${sendCurrency.ticker.toUpperCase()} ~ ${rate.localizedStringAsFixed(
+ locale: ref.watch(
+ localeServiceChangeNotifierProvider
+ .select((value) => value.locale),
+ ),
+ )} ${receivingCurrency.ticker.toUpperCase()}";
+
+ return ConditionalParent(
+ condition: i > 0,
+ builder: (child) => Column(
+ mainAxisSize: MainAxisSize.min,
+ children: [
+ isDesktop
+ ? Container(
+ height: 1,
+ color: Theme.of(context)
+ .extension()!
+ .background,
+ )
+ : const SizedBox(
+ height: 16,
+ ),
+ child,
+ ],
+ ),
+ child: _ProviderOption(
+ key: Key(widget.exchange.name + e.exchangeProvider),
+ exchange: widget.exchange,
+ estimate: e,
+ rateString: rateString,
+ kycRating: e.kycRating,
+ ),
+ );
+ },
+ ),
+ ],
+ );
+ } else {
+ Logging.instance.log(
+ "$runtimeType rate unavailable for ${widget.exchange.name}: $data",
+ level: LogLevel.Warning,
+ );
+
+ return Consumer(
+ builder: (_, ref, __) {
+ String? message;
+
+ final range = data?.item2;
+ if (range != null) {
+ if (range.min != null && amount < range.min!) {
+ message ??= "Amount too small";
+ } else if (range.max != null && amount > range.max!) {
+ message ??= "Amount too large";
+ }
+ } else if (data?.item1.value == null) {
+ final rateType = ref.watch(efRateTypeProvider) ==
+ ExchangeRateType.estimated
+ ? "estimated"
+ : "fixed";
+ message ??= "Pair unavailable on $rateType rate flow";
+ }
+
+ return _ProviderOption(
+ exchange: widget.exchange,
+ estimate: null,
+ rateString: message ?? "Failed to fetch rate",
+ rateColor:
+ Theme.of(context).extension()!.textError,
+ );
+ },
+ );
+ }
+ } else {
+ // show n/a
+ return _ProviderOption(
+ exchange: widget.exchange,
+ estimate: null,
+ rateString: "n/a",
+ );
+ }
+ },
+ ),
+ );
+ }
+}
+
+class _ProviderOption extends ConsumerStatefulWidget {
+ const _ProviderOption({
+ Key? key,
+ required this.exchange,
+ required this.estimate,
+ required this.rateString,
+ this.kycRating,
+ this.loadingString = false,
+ this.rateColor,
+ }) : super(key: key);
+
+ final Exchange exchange;
+ final Estimate? estimate;
+ final String rateString;
+ final String? kycRating;
+ final bool loadingString;
+ final Color? rateColor;
+
+ @override
+ ConsumerState<_ProviderOption> createState() => _ProviderOptionState();
+}
+
+class _ProviderOptionState extends ConsumerState<_ProviderOption> {
+ final isDesktop = Util.isDesktop;
+
+ late final String _id;
+
+ @override
+ void initState() {
+ _id =
+ "${widget.exchange.name} (${widget.estimate?.exchangeProvider ?? widget.exchange.name})";
+ super.initState();
+ }
+
+ @override
+ Widget build(BuildContext context) {
+ String groupValue = ref.watch(currentCombinedExchangeIdProvider);
+
+ if (ref.watch(efExchangeProvider).name ==
+ (widget.estimate?.exchangeProvider ?? widget.exchange.name)) {
+ groupValue = _id;
+ }
+
+ return ConditionalParent(
+ condition: isDesktop,
+ builder: (child) => MouseRegion(
+ cursor: SystemMouseCursors.click,
+ child: child,
+ ),
+ child: GestureDetector(
+ onTap: () {
+ ref.read(efExchangeProvider.notifier).state = widget.exchange;
+ ref.read(efExchangeProviderNameProvider.notifier).state =
+ widget.estimate?.exchangeProvider ?? widget.exchange.name;
+ },
+ child: Container(
+ color: Colors.transparent,
+ child: Padding(
+ padding:
+ isDesktop ? const EdgeInsets.all(16) : const EdgeInsets.all(0),
+ child: Row(
+ crossAxisAlignment: CrossAxisAlignment.start,
+ children: [
+ SizedBox(
+ width: 20,
+ height: 20,
+ child: Padding(
+ padding: EdgeInsets.only(top: isDesktop ? 20.0 : 15.0),
+ child: Radio(
+ activeColor: Theme.of(context)
+ .extension()!
+ .radioButtonIconEnabled,
+ value: _id,
+ groupValue: groupValue,
+ onChanged: (_) {
+ ref.read(efExchangeProvider.notifier).state =
+ widget.exchange;
+ ref
+ .read(efExchangeProviderNameProvider.notifier)
+ .state =
+ widget.estimate?.exchangeProvider ??
+ widget.exchange.name;
+ },
+ ),
+ ),
+ ),
+ const SizedBox(
+ width: 14,
+ ),
+ Padding(
+ padding: const EdgeInsets.only(top: 5.0),
+ child: SizedBox(
+ width: isDesktop ? 32 : 24,
+ height: isDesktop ? 32 : 24,
+ child: SvgPicture.asset(
+ Assets.exchange.getIconFor(
+ exchangeName: widget.exchange.name,
+ ),
+ width: isDesktop ? 32 : 24,
+ height: isDesktop ? 32 : 24,
+ ),
+ ),
+ ),
+ const SizedBox(
+ width: 10,
+ ),
+ Expanded(
+ child: Column(
+ mainAxisAlignment: MainAxisAlignment.start,
+ mainAxisSize: MainAxisSize.min,
+ crossAxisAlignment: CrossAxisAlignment.start,
+ children: [
+ Text(
+ widget.estimate?.exchangeProvider ??
+ widget.exchange.name,
+ style: STextStyles.titleBold12(context).copyWith(
+ color: Theme.of(context)
+ .extension()!
+ .textDark2,
+ ),
+ ),
+ widget.loadingString
+ ? AnimatedText(
+ stringsToLoopThrough: const [
+ "Loading",
+ "Loading.",
+ "Loading..",
+ "Loading...",
+ ],
+ style:
+ STextStyles.itemSubtitle12(context).copyWith(
+ color: Theme.of(context)
+ .extension()!
+ .textSubtitle1,
+ ),
+ )
+ : Text(
+ widget.rateString,
+ style:
+ STextStyles.itemSubtitle12(context).copyWith(
+ color: widget.rateColor ??
+ Theme.of(context)
+ .extension()!
+ .textSubtitle1,
+ ),
+ ),
+ ],
+ ),
+ ),
+ if (widget.kycRating != null)
+ TrocadorKYCInfoButton(
+ kycType: TrocadorKYCType.fromString(
+ widget.kycRating!,
+ ),
+ ),
+ ],
+ ),
+ ),
+ ),
+ ),
+ );
+ }
+}
diff --git a/lib/pages/exchange_view/sub_widgets/exchange_provider_options.dart b/lib/pages/exchange_view/sub_widgets/exchange_provider_options.dart
index f770278f3..a145d36a4 100644
--- a/lib/pages/exchange_view/sub_widgets/exchange_provider_options.dart
+++ b/lib/pages/exchange_view/sub_widgets/exchange_provider_options.dart
@@ -1,24 +1,13 @@
-import 'package:decimal/decimal.dart';
import 'package:flutter/material.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
-import 'package:flutter_svg/svg.dart';
-import 'package:stackwallet/exceptions/exchange/pair_unavailable_exception.dart';
import 'package:stackwallet/models/exchange/aggregate_currency.dart';
-import 'package:stackwallet/models/exchange/response_objects/estimate.dart';
+import 'package:stackwallet/pages/exchange_view/sub_widgets/exchange_provider_option.dart';
import 'package:stackwallet/providers/providers.dart';
import 'package:stackwallet/services/exchange/change_now/change_now_exchange.dart';
-import 'package:stackwallet/services/exchange/exchange_response.dart';
import 'package:stackwallet/services/exchange/majestic_bank/majestic_bank_exchange.dart';
-import 'package:stackwallet/utilities/amount/amount.dart';
-import 'package:stackwallet/utilities/assets.dart';
-import 'package:stackwallet/utilities/enums/coin_enum.dart';
-import 'package:stackwallet/utilities/logger.dart';
-import 'package:stackwallet/utilities/show_loading.dart';
-import 'package:stackwallet/utilities/text_styles.dart';
+import 'package:stackwallet/services/exchange/trocador/trocador_exchange.dart';
import 'package:stackwallet/utilities/theme/stack_colors.dart';
import 'package:stackwallet/utilities/util.dart';
-import 'package:stackwallet/widgets/animated_text.dart';
-import 'package:stackwallet/widgets/conditional_parent.dart';
import 'package:stackwallet/widgets/rounded_white_container.dart';
class ExchangeProviderOptions extends ConsumerStatefulWidget {
@@ -60,11 +49,10 @@ class _ExchangeProviderOptionsState
@override
Widget build(BuildContext context) {
- final sendCurrency = ref.watch(exchangeFormStateProvider).sendCurrency;
+ final sendCurrency =
+ ref.watch(efCurrencyPairProvider.select((value) => value.send));
final receivingCurrency =
- ref.watch(exchangeFormStateProvider).receiveCurrency;
- final fromAmount = ref.watch(exchangeFormStateProvider).sendAmount;
- final toAmount = ref.watch(exchangeFormStateProvider).receiveAmount;
+ ref.watch(efCurrencyPairProvider.select((value) => value.receive));
final showChangeNow = exchangeSupported(
exchangeName: ChangeNowExchange.exchangeName,
@@ -76,6 +64,11 @@ class _ExchangeProviderOptionsState
sendCurrency: sendCurrency,
receiveCurrency: receivingCurrency,
);
+ final showTrocador = exchangeSupported(
+ exchangeName: TrocadorExchange.exchangeName,
+ sendCurrency: sendCurrency,
+ receiveCurrency: receivingCurrency,
+ );
return RoundedWhiteContainer(
padding: isDesktop ? const EdgeInsets.all(0) : const EdgeInsets.all(12),
@@ -85,239 +78,11 @@ class _ExchangeProviderOptionsState
child: Column(
children: [
if (showChangeNow)
- ConditionalParent(
- condition: isDesktop,
- builder: (child) => MouseRegion(
- cursor: SystemMouseCursors.click,
- child: child,
- ),
- child: GestureDetector(
- onTap: () {
- if (ref.read(exchangeFormStateProvider).exchange.name !=
- ChangeNowExchange.exchangeName) {
- showLoading(
- whileFuture:
- ref.read(exchangeFormStateProvider).updateExchange(
- exchange: ChangeNowExchange.instance,
- shouldUpdateData: true,
- shouldNotifyListeners: true,
- ),
- context: context,
- message: "Updating rates",
- isDesktop: isDesktop,
- );
- }
- },
- child: Container(
- color: Colors.transparent,
- child: Padding(
- padding: isDesktop
- ? const EdgeInsets.all(16)
- : const EdgeInsets.all(0),
- child: Row(
- crossAxisAlignment: CrossAxisAlignment.start,
- children: [
- SizedBox(
- width: 20,
- height: 20,
- child: Padding(
- padding:
- EdgeInsets.only(top: isDesktop ? 20.0 : 15.0),
- child: Radio(
- activeColor: Theme.of(context)
- .extension()!
- .radioButtonIconEnabled,
- value: ChangeNowExchange.exchangeName,
- groupValue: ref.watch(exchangeFormStateProvider
- .select((value) => value.exchange.name)),
- onChanged: (_) {
- if (ref
- .read(exchangeFormStateProvider)
- .exchange
- .name !=
- ChangeNowExchange.exchangeName) {
- ref
- .read(exchangeFormStateProvider)
- .updateExchange(
- exchange: ChangeNowExchange.instance,
- shouldUpdateData: true,
- shouldNotifyListeners: true,
- );
- }
- },
- ),
- ),
- ),
- const SizedBox(
- width: 14,
- ),
- Padding(
- padding: const EdgeInsets.only(top: 5.0),
- child: SizedBox(
- width: isDesktop ? 32 : 24,
- height: isDesktop ? 32 : 24,
- child: SvgPicture.asset(
- Assets.exchange.changeNow,
- width: isDesktop ? 32 : 24,
- height: isDesktop ? 32 : 24,
- ),
- ),
- ),
- const SizedBox(
- width: 10,
- ),
- Expanded(
- child: Column(
- mainAxisAlignment: MainAxisAlignment.start,
- mainAxisSize: MainAxisSize.min,
- crossAxisAlignment: CrossAxisAlignment.start,
- children: [
- Text(
- ChangeNowExchange.exchangeName,
- style:
- STextStyles.titleBold12(context).copyWith(
- color: Theme.of(context)
- .extension()!
- .textDark2,
- ),
- ),
- if (sendCurrency != null &&
- receivingCurrency != null &&
- toAmount != null &&
- toAmount > Decimal.zero &&
- fromAmount != null &&
- fromAmount > Decimal.zero)
- FutureBuilder(
- future:
- ChangeNowExchange.instance.getEstimate(
- sendCurrency.ticker,
- receivingCurrency.ticker,
- widget.reversed ? toAmount : fromAmount,
- widget.fixedRate,
- widget.reversed,
- ),
- builder: (context,
- AsyncSnapshot>
- snapshot) {
- if (snapshot.connectionState ==
- ConnectionState.done &&
- snapshot.hasData) {
- final estimate = snapshot.data?.value;
- if (estimate != null) {
- Coin coin;
- try {
- coin = coinFromTickerCaseInsensitive(
- receivingCurrency.ticker);
- } catch (_) {
- coin = Coin.bitcoin;
- }
- Amount rate;
- if (estimate.reversed) {
- rate = (toAmount /
- estimate.estimatedAmount)
- .toDecimal(
- scaleOnInfinitePrecision: 18)
- .toAmount(
- fractionDigits:
- coin.decimals);
- } else {
- rate = (estimate.estimatedAmount /
- fromAmount)
- .toDecimal(
- scaleOnInfinitePrecision: 18)
- .toAmount(
- fractionDigits:
- coin.decimals);
- }
-
- return Text(
- "1 ${sendCurrency.ticker.toUpperCase()} ~ ${rate.localizedStringAsFixed(
- locale: ref.watch(
- localeServiceChangeNotifierProvider
- .select(
- (value) => value.locale),
- ),
- )} ${receivingCurrency.ticker.toUpperCase()}",
- style: STextStyles.itemSubtitle12(
- context)
- .copyWith(
- color: Theme.of(context)
- .extension()!
- .textSubtitle1,
- ),
- );
- } else if (snapshot.data?.exception
- is PairUnavailableException) {
- return Text(
- "Unsupported pair",
- style: STextStyles.itemSubtitle12(
- context)
- .copyWith(
- color: Theme.of(context)
- .extension()!
- .textSubtitle1,
- ),
- );
- } else {
- Logging.instance.log(
- "$runtimeType failed to fetch rate for ChangeNOW: ${snapshot.data}",
- level: LogLevel.Warning,
- );
- return Text(
- "Failed to fetch rate",
- style: STextStyles.itemSubtitle12(
- context)
- .copyWith(
- color: Theme.of(context)
- .extension()!
- .textSubtitle1,
- ),
- );
- }
- } else {
- return AnimatedText(
- stringsToLoopThrough: const [
- "Loading",
- "Loading.",
- "Loading..",
- "Loading...",
- ],
- style:
- STextStyles.itemSubtitle12(context)
- .copyWith(
- color: Theme.of(context)
- .extension()!
- .textSubtitle1,
- ),
- );
- }
- },
- ),
- if (!(sendCurrency != null &&
- receivingCurrency != null &&
- toAmount != null &&
- toAmount > Decimal.zero &&
- fromAmount != null &&
- fromAmount > Decimal.zero))
- Text(
- "n/a",
- style: STextStyles.itemSubtitle12(context)
- .copyWith(
- color: Theme.of(context)
- .extension()!
- .textSubtitle1,
- ),
- ),
- ],
- ),
- ),
- ],
- ),
- ),
- ),
- ),
+ ExchangeOption(
+ exchange: ChangeNowExchange.instance,
+ fixedRate: widget.fixedRate,
+ reversed: widget.reversed,
),
-
if (showChangeNow && showMajesticBank)
isDesktop
? Container(
@@ -328,446 +93,28 @@ class _ExchangeProviderOptionsState
: const SizedBox(
height: 16,
),
-
if (showMajesticBank)
- ConditionalParent(
- condition: isDesktop,
- builder: (child) => MouseRegion(
- cursor: SystemMouseCursors.click,
- child: child,
- ),
- child: GestureDetector(
- onTap: () {
- if (ref.read(exchangeFormStateProvider).exchange.name !=
- MajesticBankExchange.exchangeName) {
- showLoading(
- whileFuture:
- ref.read(exchangeFormStateProvider).updateExchange(
- exchange: MajesticBankExchange.instance,
- shouldUpdateData: true,
- shouldNotifyListeners: true,
- ),
- context: context,
- isDesktop: isDesktop,
- message: "Updating rates",
- );
- }
- },
- child: Container(
- color: Colors.transparent,
- child: Padding(
- padding: isDesktop
- ? const EdgeInsets.all(16)
- : const EdgeInsets.all(0),
- child: Row(
- crossAxisAlignment: CrossAxisAlignment.start,
- children: [
- SizedBox(
- width: 20,
- height: 20,
- child: Padding(
- padding:
- EdgeInsets.only(top: isDesktop ? 20.0 : 15.0),
- child: Radio(
- activeColor: Theme.of(context)
- .extension()!
- .radioButtonIconEnabled,
- value: MajesticBankExchange.exchangeName,
- groupValue: ref.watch(exchangeFormStateProvider
- .select((value) => value.exchange.name)),
- onChanged: (_) {
- if (ref
- .read(exchangeFormStateProvider)
- .exchange
- .name !=
- MajesticBankExchange.exchangeName) {
- ref
- .read(exchangeFormStateProvider)
- .updateExchange(
- exchange: MajesticBankExchange.instance,
- shouldUpdateData: true,
- shouldNotifyListeners: true,
- );
- }
- },
- ),
- ),
- ),
- const SizedBox(
- width: 14,
- ),
- Padding(
- padding: const EdgeInsets.only(top: 5.0),
- child: SizedBox(
- width: isDesktop ? 32 : 24,
- height: isDesktop ? 32 : 24,
- child: SvgPicture.asset(
- Assets.exchange.majesticBankBlue,
- width: isDesktop ? 32 : 24,
- height: isDesktop ? 32 : 24,
- ),
- ),
- ),
- const SizedBox(
- width: 10,
- ),
- Expanded(
- child: Column(
- mainAxisAlignment: MainAxisAlignment.start,
- mainAxisSize: MainAxisSize.min,
- crossAxisAlignment: CrossAxisAlignment.start,
- children: [
- Text(
- MajesticBankExchange.exchangeName,
- style:
- STextStyles.titleBold12(context).copyWith(
- color: Theme.of(context)
- .extension()!
- .textDark2,
- ),
- ),
- if (sendCurrency != null &&
- receivingCurrency != null &&
- toAmount != null &&
- toAmount > Decimal.zero &&
- fromAmount != null &&
- fromAmount > Decimal.zero)
- FutureBuilder(
- future:
- MajesticBankExchange.instance.getEstimate(
- sendCurrency.ticker,
- receivingCurrency.ticker,
- widget.reversed ? toAmount : fromAmount,
- widget.fixedRate,
- widget.reversed,
- ),
- builder: (context,
- AsyncSnapshot>
- snapshot) {
- if (snapshot.connectionState ==
- ConnectionState.done &&
- snapshot.hasData) {
- final estimate = snapshot.data?.value;
- if (estimate != null) {
- Coin coin;
- try {
- coin = coinFromTickerCaseInsensitive(
- receivingCurrency.ticker);
- } catch (_) {
- coin = Coin.bitcoin;
- }
- Amount rate;
- if (estimate.reversed) {
- rate = (toAmount /
- estimate.estimatedAmount)
- .toDecimal(
- scaleOnInfinitePrecision: 18)
- .toAmount(
- fractionDigits: coin.decimals,
- );
- } else {
- rate = (estimate.estimatedAmount /
- fromAmount)
- .toDecimal(
- scaleOnInfinitePrecision: 18)
- .toAmount(
- fractionDigits: coin.decimals,
- );
- }
-
- return Text(
- "1 ${sendCurrency.ticker.toUpperCase()} ~ ${rate.localizedStringAsFixed(
- locale: ref.watch(
- localeServiceChangeNotifierProvider
- .select(
- (value) => value.locale),
- ),
- )} ${receivingCurrency.ticker.toUpperCase()}",
- style: STextStyles.itemSubtitle12(
- context)
- .copyWith(
- color: Theme.of(context)
- .extension()!
- .textSubtitle1,
- ),
- );
- } else if (snapshot.data?.exception
- is PairUnavailableException) {
- return Text(
- "Unsupported pair",
- style: STextStyles.itemSubtitle12(
- context)
- .copyWith(
- color: Theme.of(context)
- .extension()!
- .textSubtitle1,
- ),
- );
- } else {
- Logging.instance.log(
- "$runtimeType failed to fetch rate for ChangeNOW: ${snapshot.data}",
- level: LogLevel.Warning,
- );
- return Text(
- "Failed to fetch rate",
- style: STextStyles.itemSubtitle12(
- context)
- .copyWith(
- color: Theme.of(context)
- .extension()!
- .textSubtitle1,
- ),
- );
- }
- } else {
- return AnimatedText(
- stringsToLoopThrough: const [
- "Loading",
- "Loading.",
- "Loading..",
- "Loading...",
- ],
- style:
- STextStyles.itemSubtitle12(context)
- .copyWith(
- color: Theme.of(context)
- .extension()!
- .textSubtitle1,
- ),
- );
- }
- },
- ),
- if (!(sendCurrency != null &&
- receivingCurrency != null &&
- toAmount != null &&
- toAmount > Decimal.zero &&
- fromAmount != null &&
- fromAmount > Decimal.zero))
- Text(
- "n/a",
- style: STextStyles.itemSubtitle12(context)
- .copyWith(
- color: Theme.of(context)
- .extension()!
- .textSubtitle1,
- ),
- ),
- ],
- ),
- ),
- ],
- ),
- ),
- ),
- ),
+ ExchangeOption(
+ exchange: MajesticBankExchange.instance,
+ fixedRate: widget.fixedRate,
+ reversed: widget.reversed,
+ ),
+ if ((showChangeNow || showMajesticBank) && showTrocador)
+ isDesktop
+ ? Container(
+ height: 1,
+ color:
+ Theme.of(context).extension()!.background,
+ )
+ : const SizedBox(
+ height: 16,
+ ),
+ if (showTrocador)
+ ExchangeOption(
+ fixedRate: widget.fixedRate,
+ reversed: widget.reversed,
+ exchange: TrocadorExchange.instance,
),
- // if (isDesktop)
- // Container(
- // height: 1,
- // color: Theme.of(context).extension()!.background,
- // ),
- // if (!isDesktop)
- // const SizedBox(
- // height: 16,
- // ),
- // ConditionalParent(
- // condition: isDesktop,
- // builder: (child) => MouseRegion(
- // cursor: SystemMouseCursors.click,
- // child: child,
- // ),
- // child: GestureDetector(
- // onTap: () {
- // if (ref.read(currentExchangeNameStateProvider.state).state !=
- // SimpleSwapExchange.exchangeName) {
- // // ref.read(currentExchangeNameStateProvider.state).state =
- // // SimpleSwapExchange.exchangeName;
- // ref.read(exchangeFormStateProvider).exchange =
- // Exchange.fromName(ref
- // .read(currentExchangeNameStateProvider.state)
- // .state);
- // }
- // },
- // child: Container(
- // color: Colors.transparent,
- // child: Padding(
- // padding: isDesktop
- // ? const EdgeInsets.all(16)
- // : const EdgeInsets.all(0),
- // child: Row(
- // crossAxisAlignment: CrossAxisAlignment.start,
- // children: [
- // SizedBox(
- // width: 20,
- // height: 20,
- // child: Radio(
- // activeColor: Theme.of(context)
- // .extension()!
- // .radioButtonIconEnabled,
- // value: SimpleSwapExchange.exchangeName,
- // groupValue: ref
- // .watch(currentExchangeNameStateProvider.state)
- // .state,
- // onChanged: (value) {
- // if (value is String) {
- // ref
- // .read(currentExchangeNameStateProvider.state)
- // .state = value;
- // ref.read(exchangeFormStateProvider).exchange =
- // Exchange.fromName(ref
- // .read(currentExchangeNameStateProvider
- // .state)
- // .state);
- // }
- // },
- // ),
- // ),
- // const SizedBox(
- // width: 14,
- // ),
- // // SvgPicture.asset(
- // // Assets.exchange.simpleSwap,
- // // width: isDesktop ? 32 : 24,
- // // height: isDesktop ? 32 : 24,
- // // ),
- // // const SizedBox(
- // // width: 10,
- // // ),
- // // Expanded(
- // // child: Column(
- // // mainAxisAlignment: MainAxisAlignment.start,
- // // mainAxisSize: MainAxisSize.min,
- // // crossAxisAlignment: CrossAxisAlignment.start,
- // // children: [
- // // Text(
- // // SimpleSwapExchange.exchangeName,
- // // style: STextStyles.titleBold12(context).copyWith(
- // // color: Theme.of(context)
- // // .extension()!
- // // .textDark2,
- // // ),
- // // ),
- // // if (from != null &&
- // // to != null &&
- // // toAmount != null &&
- // // toAmount! > Decimal.zero &&
- // // fromAmount != null &&
- // // fromAmount! > Decimal.zero)
- // // FutureBuilder(
- // // future: SimpleSwapExchange().getEstimate(
- // // from!,
- // // to!,
- // // // reversed ? toAmount! : fromAmount!,
- // // fromAmount!,
- // // fixedRate,
- // // // reversed,
- // // false,
- // // ),
- // // builder: (context,
- // // AsyncSnapshot>
- // // snapshot) {
- // // if (snapshot.connectionState ==
- // // ConnectionState.done &&
- // // snapshot.hasData) {
- // // final estimate = snapshot.data?.value;
- // // if (estimate != null) {
- // // Decimal rate = (estimate.estimatedAmount /
- // // fromAmount!)
- // // .toDecimal(
- // // scaleOnInfinitePrecision: 12);
- // //
- // // Coin coin;
- // // try {
- // // coin =
- // // coinFromTickerCaseInsensitive(to!);
- // // } catch (_) {
- // // coin = Coin.bitcoin;
- // // }
- // // return Text(
- // // "1 ${from!.toUpperCase()} ~ ${Format.localizedStringAsFixed(
- // // value: rate,
- // // locale: ref.watch(
- // // localeServiceChangeNotifierProvider
- // // .select(
- // // (value) => value.locale),
- // // ),
- // // decimalPlaces:
- // // Constants.decimalPlacesForCoin(
- // // coin),
- // // )} ${to!.toUpperCase()}",
- // // style:
- // // STextStyles.itemSubtitle12(context)
- // // .copyWith(
- // // color: Theme.of(context)
- // // .extension()!
- // // .textSubtitle1,
- // // ),
- // // );
- // // } else {
- // // Logging.instance.log(
- // // "$runtimeType failed to fetch rate for SimpleSwap: ${snapshot.data}",
- // // level: LogLevel.Warning,
- // // );
- // // return Text(
- // // "Failed to fetch rate",
- // // style:
- // // STextStyles.itemSubtitle12(context)
- // // .copyWith(
- // // color: Theme.of(context)
- // // .extension()!
- // // .textSubtitle1,
- // // ),
- // // );
- // // }
- // // } else {
- // // return AnimatedText(
- // // stringsToLoopThrough: const [
- // // "Loading",
- // // "Loading.",
- // // "Loading..",
- // // "Loading...",
- // // ],
- // // style: STextStyles.itemSubtitle12(context)
- // // .copyWith(
- // // color: Theme.of(context)
- // // .extension()!
- // // .textSubtitle1,
- // // ),
- // // );
- // // }
- // // },
- // // ),
- // // // if (!(from != null &&
- // // // to != null &&
- // // // (reversed
- // // // ? toAmount != null && toAmount! > Decimal.zero
- // // // : fromAmount != null &&
- // // // fromAmount! > Decimal.zero)))
- // // if (!(from != null &&
- // // to != null &&
- // // toAmount != null &&
- // // toAmount! > Decimal.zero &&
- // // fromAmount != null &&
- // // fromAmount! > Decimal.zero))
- // // Text(
- // // "n/a",
- // // style: STextStyles.itemSubtitle12(context)
- // // .copyWith(
- // // color: Theme.of(context)
- // // .extension()!
- // // .textSubtitle1,
- // // ),
- // // ),
- // // ],
- // // ),
- // // ),
- // ],
- // ),
- // ),
- // ),
- // ),
- // ),
],
),
);
diff --git a/lib/pages/exchange_view/sub_widgets/rate_type_toggle.dart b/lib/pages/exchange_view/sub_widgets/rate_type_toggle.dart
index f85c0cfcd..b83883509 100644
--- a/lib/pages/exchange_view/sub_widgets/rate_type_toggle.dart
+++ b/lib/pages/exchange_view/sub_widgets/rate_type_toggle.dart
@@ -29,9 +29,7 @@ class RateTypeToggle extends ConsumerWidget {
onChanged?.call(ExchangeRateType.estimated);
}
},
- isOn: ref.watch(exchangeFormStateProvider
- .select((value) => value.exchangeRateType)) ==
- ExchangeRateType.fixed,
+ isOn: ref.watch(efRateTypeProvider) == ExchangeRateType.fixed,
onColor: isDesktop
? Theme.of(context)
.extension()!
diff --git a/lib/pages/exchange_view/trade_details_view.dart b/lib/pages/exchange_view/trade_details_view.dart
index f0cb7b7a8..5f8e9d471 100644
--- a/lib/pages/exchange_view/trade_details_view.dart
+++ b/lib/pages/exchange_view/trade_details_view.dart
@@ -20,6 +20,7 @@ import 'package:stackwallet/services/exchange/change_now/change_now_exchange.dar
import 'package:stackwallet/services/exchange/exchange.dart';
import 'package:stackwallet/services/exchange/majestic_bank/majestic_bank_exchange.dart';
import 'package:stackwallet/services/exchange/simpleswap/simpleswap_exchange.dart';
+import 'package:stackwallet/services/exchange/trocador/trocador_exchange.dart';
import 'package:stackwallet/utilities/amount/amount.dart';
import 'package:stackwallet/utilities/assets.dart';
import 'package:stackwallet/utilities/clipboard_interface.dart';
@@ -1205,6 +1206,13 @@ class _TradeDetailsViewState extends ConsumerState {
url =
"https://majesticbank.sc/track?trx=${trade.tradeId}";
break;
+
+ default:
+ if (trade.exchangeName
+ .startsWith(TrocadorExchange.exchangeName)) {
+ url =
+ "https://trocador.app/en/checkout${trade.tradeId}";
+ }
}
return ConditionalParent(
condition: isDesktop,
diff --git a/lib/pages/exchange_view/wallet_initiated_exchange_view.dart b/lib/pages/exchange_view/wallet_initiated_exchange_view.dart
index 3c726b702..7085bd8aa 100644
--- a/lib/pages/exchange_view/wallet_initiated_exchange_view.dart
+++ b/lib/pages/exchange_view/wallet_initiated_exchange_view.dart
@@ -54,7 +54,8 @@ class _WalletInitiatedExchangeViewState
ExchangeDataLoadingService.instance.onLoadingComplete = () {
WidgetsBinding.instance.addPostFrameCallback((timeStamp) async {
await ExchangeDataLoadingService.instance.setCurrenciesIfEmpty(
- ref.read(exchangeFormStateProvider),
+ ref.read(efCurrencyPairProvider),
+ ref.read(efRateTypeProvider),
);
setState(() {
_initialCachePopulationUnderway = false;
@@ -70,7 +71,8 @@ class _WalletInitiatedExchangeViewState
ExchangeDataLoadingService.instance.onLoadingComplete = () {
WidgetsBinding.instance.addPostFrameCallback((timeStamp) async {
await ExchangeDataLoadingService.instance.setCurrenciesIfEmpty(
- ref.read(exchangeFormStateProvider),
+ ref.read(efCurrencyPairProvider),
+ ref.read(efRateTypeProvider),
);
setState(() {
_initialCachePopulationUnderway = false;
diff --git a/lib/pages/pinpad_views/lock_screen_view.dart b/lib/pages/pinpad_views/lock_screen_view.dart
index 316ef9b5d..380717f72 100644
--- a/lib/pages/pinpad_views/lock_screen_view.dart
+++ b/lib/pages/pinpad_views/lock_screen_view.dart
@@ -55,7 +55,6 @@ class LockscreenView extends ConsumerStatefulWidget {
final VoidCallback? onSuccess;
final String customKeyLabel;
-
@override
ConsumerState createState() => _LockscreenViewState();
}
@@ -205,49 +204,58 @@ class _LockscreenViewState extends ConsumerState {
late Biometrics biometrics;
Widget get _body => Background(
- child: Scaffold(
- backgroundColor:
- Theme.of(context).extension()!.background,
- appBar: AppBar(
- leading: widget.showBackButton
- ? AppBarBackButton(
- onPressed: () async {
- if (FocusScope.of(context).hasFocus) {
- FocusScope.of(context).unfocus();
- await Future.delayed(
- const Duration(milliseconds: 70));
- }
- if (mounted) {
- Navigator.of(context).pop();
- }
- },
- )
- : Container(),
- ),
- body: SafeArea(
- child: Column(
+ child: SafeArea(
+ child: Scaffold(
+ extendBodyBehindAppBar: true,
+ backgroundColor:
+ Theme.of(context).extension()!.background,
+ appBar: AppBar(
+ leading: widget.showBackButton
+ ? AppBarBackButton(
+ onPressed: () async {
+ if (FocusScope.of(context).hasFocus) {
+ FocusScope.of(context).unfocus();
+ await Future.delayed(
+ const Duration(milliseconds: 70));
+ }
+ if (mounted) {
+ Navigator.of(context).pop();
+ }
+ },
+ )
+ : Container(),
+ actions: [
+ // check prefs and hide if user has biometrics toggle off?
+ Column(
+ mainAxisAlignment: MainAxisAlignment.center,
+ children: [
+ Padding(
+ padding: const EdgeInsets.only(
+ right: 16.0,
+ ),
+ child: Row(
+ mainAxisAlignment: MainAxisAlignment.end,
+ children: [
+ if (ref
+ .read(prefsChangeNotifierProvider)
+ .useBiometrics ==
+ true)
+ CustomTextButton(
+ text: "Use biometrics",
+ onTap: () async {
+ await _checkUseBiometrics();
+ },
+ ),
+ ],
+ ),
+ ),
+ ],
+ ),
+ ],
+ ),
+ body: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
- // check prefs and hide if user has biometrics toggle off?
- Padding(
- padding: const EdgeInsets.only(right: 40.0),
- child: Row(
- mainAxisAlignment: MainAxisAlignment.end,
- children: [
- if (ref.read(prefsChangeNotifierProvider).useBiometrics ==
- true)
- CustomTextButton(
- text: "Use biometrics",
- onTap: () async {
- await _checkUseBiometrics();
- },
- ),
- ],
- ),
- ),
- const SizedBox(
- height: 55,
- ),
Shake(
animationDuration: const Duration(milliseconds: 700),
animationRange: 12,
diff --git a/lib/pages/wallets_view/wallets_overview.dart b/lib/pages/wallets_view/wallets_overview.dart
index 8d7f9b6ac..7fe54d0c1 100644
--- a/lib/pages/wallets_view/wallets_overview.dart
+++ b/lib/pages/wallets_view/wallets_overview.dart
@@ -1,7 +1,9 @@
import 'package:flutter/material.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:flutter_svg/svg.dart';
+import 'package:stackwallet/models/add_wallet_list_entity/sub_classes/coin_entity.dart';
import 'package:stackwallet/models/isar/models/ethereum/eth_contract.dart';
+import 'package:stackwallet/pages/add_wallet_views/create_or_restore_wallet_view/create_or_restore_wallet_view.dart';
import 'package:stackwallet/pages_desktop_specific/my_stack_view/dialogs/desktop_expanding_wallet_card.dart';
import 'package:stackwallet/providers/db/main_db_provider.dart';
import 'package:stackwallet/providers/providers.dart';
@@ -174,6 +176,27 @@ class _EthWalletsOverviewState extends ConsumerState {
"${widget.coin.prettyName} (${widget.coin.ticker}) wallets",
style: STextStyles.navBarTitle(context),
),
+ actions: [
+ AspectRatio(
+ aspectRatio: 1,
+ child: AppBarIconButton(
+ icon: SvgPicture.asset(
+ Assets.svg.plus,
+ width: 18,
+ height: 18,
+ color: Theme.of(context)
+ .extension()!
+ .topNavIconPrimary,
+ ),
+ onPressed: () {
+ Navigator.of(context).pushNamed(
+ CreateOrRestoreWalletView.routeName,
+ arguments: CoinEntity(widget.coin),
+ );
+ },
+ ),
+ ),
+ ],
),
body: SafeArea(
child: Padding(
diff --git a/lib/pages_desktop_specific/desktop_exchange/desktop_all_trades_view.dart b/lib/pages_desktop_specific/desktop_exchange/desktop_all_trades_view.dart
index 34d688b21..7d344f47d 100644
--- a/lib/pages_desktop_specific/desktop_exchange/desktop_all_trades_view.dart
+++ b/lib/pages_desktop_specific/desktop_exchange/desktop_all_trades_view.dart
@@ -5,6 +5,7 @@ import 'package:flutter/material.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:flutter_svg/svg.dart';
import 'package:isar/isar.dart';
+import 'package:stackwallet/db/isar/main_db.dart';
import 'package:stackwallet/models/exchange/change_now/exchange_transaction_status.dart';
import 'package:stackwallet/models/exchange/response_objects/trade.dart';
import 'package:stackwallet/models/isar/models/isar_models.dart';
@@ -29,8 +30,6 @@ import 'package:stackwallet/widgets/stack_text_field.dart';
import 'package:stackwallet/widgets/textfield_icon_button.dart';
import 'package:tuple/tuple.dart';
-import '../../db/isar/main_db.dart';
-
class DesktopAllTradesView extends ConsumerStatefulWidget {
const DesktopAllTradesView({Key? key}) : super(key: key);
diff --git a/lib/pages_desktop_specific/desktop_exchange/desktop_exchange_view.dart b/lib/pages_desktop_specific/desktop_exchange/desktop_exchange_view.dart
index 17cd1c778..ab8279866 100644
--- a/lib/pages_desktop_specific/desktop_exchange/desktop_exchange_view.dart
+++ b/lib/pages_desktop_specific/desktop_exchange/desktop_exchange_view.dart
@@ -1,6 +1,7 @@
import 'package:flutter/material.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:stackwallet/pages/exchange_view/exchange_form.dart';
+import 'package:stackwallet/pages_desktop_specific/desktop_exchange/desktop_all_trades_view.dart';
import 'package:stackwallet/pages_desktop_specific/desktop_exchange/subwidgets/desktop_trade_history.dart';
import 'package:stackwallet/providers/exchange/exchange_form_state_provider.dart';
import 'package:stackwallet/providers/global/prefs_provider.dart';
@@ -14,8 +15,6 @@ import 'package:stackwallet/widgets/desktop/desktop_app_bar.dart';
import 'package:stackwallet/widgets/desktop/desktop_scaffold.dart';
import 'package:stackwallet/widgets/rounded_white_container.dart';
-import 'desktop_all_trades_view.dart';
-
class DesktopExchangeView extends ConsumerStatefulWidget {
const DesktopExchangeView({Key? key}) : super(key: key);
@@ -38,7 +37,8 @@ class _DesktopExchangeViewState extends ConsumerState {
ExchangeDataLoadingService.instance.onLoadingComplete = () {
WidgetsBinding.instance.addPostFrameCallback((timeStamp) async {
await ExchangeDataLoadingService.instance.setCurrenciesIfEmpty(
- ref.read(exchangeFormStateProvider),
+ ref.read(efCurrencyPairProvider),
+ ref.read(efRateTypeProvider),
);
setState(() {
_initialCachePopulationUnderway = false;
@@ -54,7 +54,8 @@ class _DesktopExchangeViewState extends ConsumerState {
ExchangeDataLoadingService.instance.onLoadingComplete = () {
WidgetsBinding.instance.addPostFrameCallback((timeStamp) async {
await ExchangeDataLoadingService.instance.setCurrenciesIfEmpty(
- ref.read(exchangeFormStateProvider),
+ ref.read(efCurrencyPairProvider),
+ ref.read(efRateTypeProvider),
);
setState(() {
_initialCachePopulationUnderway = false;
@@ -149,10 +150,14 @@ class _DesktopExchangeViewState extends ConsumerState {
child: Row(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
- const Expanded(
- child: RoundedWhiteContainer(
- padding: EdgeInsets.all(24),
- child: ExchangeForm(),
+ Expanded(
+ child: ListView(
+ children: const [
+ RoundedWhiteContainer(
+ padding: EdgeInsets.all(24),
+ child: ExchangeForm(),
+ ),
+ ],
),
),
const SizedBox(
diff --git a/lib/pages_desktop_specific/desktop_exchange/exchange_steps/step_scaffold.dart b/lib/pages_desktop_specific/desktop_exchange/exchange_steps/step_scaffold.dart
index 203ae2d9b..e99c63821 100644
--- a/lib/pages_desktop_specific/desktop_exchange/exchange_steps/step_scaffold.dart
+++ b/lib/pages_desktop_specific/desktop_exchange/exchange_steps/step_scaffold.dart
@@ -84,8 +84,7 @@ class _StepScaffoldState extends ConsumerState {
);
final ExchangeResponse response = await ref
- .read(exchangeFormStateProvider)
- .exchange
+ .read(efExchangeProvider)
.createTrade(
from: ref.read(desktopExchangeModelProvider)!.sendTicker,
to: ref.read(desktopExchangeModelProvider)!.receiveTicker,
@@ -98,24 +97,24 @@ class _StepScaffoldState extends ConsumerState {
extraId: null,
addressRefund: ref.read(desktopExchangeModelProvider)!.refundAddress!,
refundExtraId: "",
- rateId: ref.read(desktopExchangeModelProvider)!.rateId,
+ estimate: ref.read(desktopExchangeModelProvider)!.estimate,
reversed: ref.read(desktopExchangeModelProvider)!.reversed,
);
if (response.value == null) {
if (mounted) {
Navigator.of(context).pop();
- }
- unawaited(
- showDialog(
- context: context,
- barrierDismissible: true,
- builder: (_) => SimpleDesktopDialog(
- title: "Failed to create trade",
- message: response.exception?.toString() ?? ""),
- ),
- );
+ unawaited(
+ showDialog(
+ context: context,
+ barrierDismissible: true,
+ builder: (_) => SimpleDesktopDialog(
+ title: "Failed to create trade",
+ message: response.exception?.toString() ?? ""),
+ ),
+ );
+ }
return false;
}
diff --git a/lib/pages_desktop_specific/desktop_exchange/exchange_steps/subwidgets/desktop_step_1.dart b/lib/pages_desktop_specific/desktop_exchange/exchange_steps/subwidgets/desktop_step_1.dart
index 16c3b8116..dffe71bb9 100644
--- a/lib/pages_desktop_specific/desktop_exchange/exchange_steps/subwidgets/desktop_step_1.dart
+++ b/lib/pages_desktop_specific/desktop_exchange/exchange_steps/subwidgets/desktop_step_1.dart
@@ -38,8 +38,7 @@ class DesktopStep1 extends ConsumerWidget {
children: [
DesktopStepItem(
label: "Swap",
- value: ref.watch(exchangeFormStateProvider
- .select((value) => value.exchange.name)),
+ value: ref.watch(efExchangeProviderNameProvider),
),
Container(
height: 1,
diff --git a/lib/pages_desktop_specific/desktop_exchange/exchange_steps/subwidgets/desktop_step_3.dart b/lib/pages_desktop_specific/desktop_exchange/exchange_steps/subwidgets/desktop_step_3.dart
index ef72553fa..a5d3fba43 100644
--- a/lib/pages_desktop_specific/desktop_exchange/exchange_steps/subwidgets/desktop_step_3.dart
+++ b/lib/pages_desktop_specific/desktop_exchange/exchange_steps/subwidgets/desktop_step_3.dart
@@ -36,8 +36,7 @@ class _DesktopStep3State extends ConsumerState {
children: [
DesktopStepItem(
label: "Swap",
- value: ref.watch(exchangeFormStateProvider
- .select((value) => value.exchange.name)),
+ value: ref.watch(efExchangeProviderNameProvider),
),
Container(
height: 1,
diff --git a/lib/pages_desktop_specific/desktop_exchange/exchange_steps/subwidgets/desktop_step_4.dart b/lib/pages_desktop_specific/desktop_exchange/exchange_steps/subwidgets/desktop_step_4.dart
index 2654c2cea..bbc3fd30c 100644
--- a/lib/pages_desktop_specific/desktop_exchange/exchange_steps/subwidgets/desktop_step_4.dart
+++ b/lib/pages_desktop_specific/desktop_exchange/exchange_steps/subwidgets/desktop_step_4.dart
@@ -46,7 +46,7 @@ class _DesktopStep4State extends ConsumerState {
}
final statusResponse =
- await ref.read(exchangeFormStateProvider).exchange.updateTrade(trade);
+ await ref.read(efExchangeProvider).updateTrade(trade);
String status = "Waiting";
if (statusResponse.value != null) {
status = statusResponse.value!.status;
diff --git a/lib/pages_desktop_specific/desktop_exchange/subwidgets/desktop_trade_history.dart b/lib/pages_desktop_specific/desktop_exchange/subwidgets/desktop_trade_history.dart
index 707976425..d369a61df 100644
--- a/lib/pages_desktop_specific/desktop_exchange/subwidgets/desktop_trade_history.dart
+++ b/lib/pages_desktop_specific/desktop_exchange/subwidgets/desktop_trade_history.dart
@@ -269,13 +269,6 @@ class _DesktopTradeHistoryState extends ConsumerState {
mainAxisSize: MainAxisSize.min,
crossAxisAlignment: CrossAxisAlignment.start,
children: [
- Text(
- "Recent trades",
- style: STextStyles.desktopTextExtraExtraSmall(context),
- ),
- const SizedBox(
- height: 16,
- ),
RoundedWhiteContainer(
child: Center(
child: Text(
diff --git a/lib/providers/exchange/exchange_form_state_provider.dart b/lib/providers/exchange/exchange_form_state_provider.dart
index cb7d32bfe..afac49de8 100644
--- a/lib/providers/exchange/exchange_form_state_provider.dart
+++ b/lib/providers/exchange/exchange_form_state_provider.dart
@@ -1,5 +1,91 @@
+import 'package:decimal/decimal.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
-import 'package:stackwallet/models/exchange/exchange_form_state.dart';
+import 'package:stackwallet/models/exchange/active_pair.dart';
+import 'package:stackwallet/models/exchange/response_objects/estimate.dart';
+import 'package:stackwallet/models/exchange/response_objects/range.dart';
+import 'package:stackwallet/services/exchange/exchange.dart';
+import 'package:stackwallet/services/exchange/exchange_response.dart';
+import 'package:stackwallet/utilities/enums/exchange_rate_type_enum.dart';
+import 'package:tuple/tuple.dart';
-final exchangeFormStateProvider =
- ChangeNotifierProvider((ref) => ExchangeFormState());
+final efEstimatesListProvider = StateProvider.family<
+ Tuple2>, Range?>?,
+ String>((ref, exchangeName) => null);
+
+final efRateTypeProvider =
+ StateProvider((ref) => ExchangeRateType.estimated);
+
+final efExchangeProvider =
+ StateProvider((ref) => Exchange.defaultExchange);
+final efExchangeProviderNameProvider =
+ StateProvider((ref) => Exchange.defaultExchange.name);
+
+final currentCombinedExchangeIdProvider = Provider((ref) {
+ return "${ref.watch(efExchangeProvider).name}"
+ " (${ref.watch(efExchangeProviderNameProvider)})";
+});
+
+final efSendAmountProvider = StateProvider((ref) => null);
+final efReceiveAmountProvider = StateProvider((ref) => null);
+
+final efSendAmountStringProvider = StateProvider((ref) {
+ final refreshing = ref.watch(efRefreshingProvider);
+ final reversed = ref.watch(efReversedProvider);
+ if (refreshing && reversed) {
+ return "-";
+ } else {
+ return ref.watch(efSendAmountProvider)?.toString() ?? "";
+ }
+});
+final efReceiveAmountStringProvider = StateProvider((ref) {
+ final refreshing = ref.watch(efRefreshingProvider);
+ final reversed = ref.watch(efReversedProvider);
+
+ if (refreshing && reversed == false) {
+ return "-";
+ } else {
+ return ref.watch(efReceiveAmountProvider)?.toString() ?? "";
+ }
+});
+
+final efReversedProvider = StateProvider((ref) => false);
+
+final efCurrencyPairProvider = ChangeNotifierProvider(
+ (ref) => ActivePair(),
+);
+
+final efEstimateProvider = StateProvider((ref) {
+ final exchange = ref.watch(efExchangeProvider);
+ final provider = ref.watch(efExchangeProviderNameProvider);
+ final reversed = ref.watch(efReversedProvider);
+ final fixedRate = ref.watch(efRateTypeProvider) == ExchangeRateType.fixed;
+
+ final matches = ref
+ .watch(efEstimatesListProvider(exchange.name))
+ ?.item1
+ .value
+ ?.where((e) {
+ return e.exchangeProvider == provider &&
+ e.fixedRate == fixedRate &&
+ e.reversed == reversed;
+ });
+
+ Estimate? result;
+
+ if (matches != null && matches.isNotEmpty) {
+ result = matches.first;
+ } else {
+ result = null;
+ }
+
+ return result;
+});
+
+final efCanExchangeProvider = StateProvider((ref) {
+ final Estimate? estimate = ref.watch(efEstimateProvider);
+ final refreshing = ref.watch(efRefreshingProvider);
+
+ return !refreshing && estimate != null;
+});
+
+final efRefreshingProvider = StateProvider((ref) => false);
diff --git a/lib/services/coins/epiccash/epiccash_wallet.dart b/lib/services/coins/epiccash/epiccash_wallet.dart
index a67f7e183..b44442c51 100644
--- a/lib/services/coins/epiccash/epiccash_wallet.dart
+++ b/lib/services/coins/epiccash/epiccash_wallet.dart
@@ -452,8 +452,6 @@ class EpicCashWallet extends CoinServiceAPI
EpicBoxConfigModel epicboxConfig = await getEpicBoxConfig();
- print("EPICBOX CONFIG HERE IS $epicboxConfig");
-
// TODO determine whether it is worth sending change to a change address.
dynamic message;
@@ -522,6 +520,11 @@ class EpicCashWallet extends CoinServiceAPI
throw BadEpicHttpAddressException(message: sendTx);
}
+ Map txAddressInfo = {};
+ txAddressInfo['from'] = await currentReceivingAddress;
+ txAddressInfo['to'] = txData['addresss'] as String;
+ await putSendToAddresses(sendTx, txAddressInfo);
+
Logging.instance.log("CONFIRM_RESULT_IS $sendTx", level: LogLevel.Info);
final decodeData = json.decode(sendTx);
@@ -1247,7 +1250,6 @@ class EpicCashWallet extends CoinServiceAPI
await _secureStore.write(key: '${_walletId}_config', value: stringConfig);
await _secureStore.write(key: '${_walletId}_password', value: password);
- print("EPIC BOX MODEL IS ${epicboxConfig.toString()}");
await _secureStore.write(
key: '${_walletId}_epicboxConfig', value: epicboxConfig.toString());
@@ -1390,7 +1392,8 @@ class EpicCashWallet extends CoinServiceAPI
}
}
- Future putSendToAddresses(String slateMessage) async {
+ Future putSendToAddresses(
+ String slateMessage, Map txAddressInfo) async {
try {
var slatesToCommits = await getSlatesToCommits();
final slate0 = jsonDecode(slateMessage);
@@ -1400,19 +1403,19 @@ class EpicCashWallet extends CoinServiceAPI
final slateId = part1[0]['tx_slate_id'];
final commitId = part2['tx']['body']['outputs'][0]['commit'];
- final toFromInfoString = jsonDecode(slateMessage);
- final toFromInfo = jsonDecode(toFromInfoString[1] as String);
- final from = toFromInfo['from'];
- final to = toFromInfo['to'];
+ final from = txAddressInfo['from'];
+ final to = txAddressInfo['to'];
slatesToCommits[slateId] = {
"commitId": commitId,
"from": from,
"to": to,
};
+
await epicUpdateSlatesToCommits(slatesToCommits);
return true;
} catch (e, s) {
- Logging.instance.log("$e $s", level: LogLevel.Error);
+ Logging.instance
+ .log("ERROR STORING ADDRESS $e $s", level: LogLevel.Error);
return false;
}
}
diff --git a/lib/services/exchange/TMP.dart b/lib/services/exchange/TMP.dart
deleted file mode 100644
index 868ace14e..000000000
--- a/lib/services/exchange/TMP.dart
+++ /dev/null
@@ -1,389 +0,0 @@
-// import 'package:decimal/decimal.dart';
-// import 'package:flutter/foundation.dart';
-// import 'package:stackwallet/models/exchange/response_objects/currency.dart';
-// import 'package:stackwallet/pages/exchange_view/sub_widgets/exchange_rate_sheet.dart';
-// import 'package:stackwallet/services/exchange/change_now/change_now_exchange.dart';
-// import 'package:stackwallet/services/exchange/exchange.dart';
-// import 'package:stackwallet/utilities/logger.dart';
-//
-// class ExchangeFormState extends ChangeNotifier {
-// ExchangeFormState(this.exchangeRateType);
-// final ExchangeRateType exchangeRateType;
-//
-// Exchange? _exchange;
-// Exchange get exchange =>
-// _exchange ??= ChangeNowExchange(); // default to change now
-// set exchange(Exchange value) {
-// _exchange = value;
-// _updateRangesAndEstimate(
-// shouldNotifyListeners: true,
-// );
-// }
-//
-// bool _reversed = false;
-// bool get reversed => _reversed;
-// // set reversed(bool reversed) {
-// // _reversed = reversed;
-// // //
-// // }
-//
-// Decimal? _rate;
-// Decimal? get rate => _rate;
-// // set rate(Decimal? rate) {
-// // _rate = rate;
-// // //
-// // }
-//
-// Decimal? _sendAmount;
-// Decimal? get sendAmount => _sendAmount;
-// // set sendAmount(Decimal? sendAmount) {
-// // _sendAmount = sendAmount;
-// // //
-// // }
-//
-// Decimal? _receiveAmount;
-// Decimal? get receiveAmount => _receiveAmount;
-// // set receiveAmount(Decimal? receiveAmount) {
-// // _receiveAmount = receiveAmount;
-// // //
-// // }
-//
-// Currency? _sendCurrency;
-// Currency? get sendCurrency => _sendCurrency;
-// // set sendCurrency(Currency? sendCurrency) {
-// // _sendCurrency = sendCurrency;
-// // //
-// // }
-//
-// Currency? _receiveCurrency;
-// Currency? get receiveCurrency => _receiveCurrency;
-// // set receiveCurrency(Currency? receiveCurrency) {
-// // _receiveCurrency = receiveCurrency;
-// // //
-// // }
-//
-// Decimal? _minSendAmount;
-// Decimal? get minSendAmount => _minSendAmount;
-// // set minSendAmount(Decimal? minSendAmount) {
-// // _minSendAmount = minSendAmount;
-// // //
-// // }
-//
-// Decimal? _minReceiveAmount;
-// Decimal? get minReceiveAmount => _minReceiveAmount;
-// // set minReceiveAmount(Decimal? minReceiveAmount) {
-// // _minReceiveAmount = minReceiveAmount;
-// // //
-// // }
-//
-// Decimal? _maxSendAmount;
-// Decimal? get maxSendAmount => _maxSendAmount;
-// // set maxSendAmount(Decimal? maxSendAmount) {
-// // _maxSendAmount = maxSendAmount;
-// // //
-// // }
-//
-// Decimal? _maxReceiveAmount;
-// Decimal? get maxReceiveAmount => _maxReceiveAmount;
-// // set maxReceiveAmount(Decimal? maxReceiveAmount) {
-// // _maxReceiveAmount = maxReceiveAmount;
-// // //
-// // }
-//
-// //============================================================================
-// // computed properties
-// //============================================================================
-//
-// String? get fromTicker => _sendCurrency?.ticker;
-//
-// String? get toTicker => _receiveCurrency?.ticker;
-//
-// String get warning {
-// if (reversed) {
-// if (_receiveCurrency != null && _receiveAmount != null) {
-// if (_minReceiveAmount != null &&
-// _receiveAmount! < _minReceiveAmount! &&
-// _receiveAmount! > Decimal.zero) {
-// return "Minimum amount ${_minReceiveAmount!.toString()} ${_receiveCurrency!.ticker.toUpperCase()}";
-// } else if (_maxReceiveAmount != null &&
-// _receiveAmount! > _maxReceiveAmount!) {
-// return "Maximum amount ${_maxReceiveAmount!.toString()} ${_receiveCurrency!.ticker.toUpperCase()}";
-// }
-// }
-// } else {
-// if (_sendCurrency != null && _sendAmount != null) {
-// if (_minSendAmount != null &&
-// _sendAmount! < _minSendAmount! &&
-// _sendAmount! > Decimal.zero) {
-// return "Minimum amount ${_minSendAmount!.toString()} ${_sendCurrency!.ticker.toUpperCase()}";
-// } else if (_maxSendAmount != null && _sendAmount! > _maxSendAmount!) {
-// return "Maximum amount ${_maxSendAmount!.toString()} ${_sendCurrency!.ticker.toUpperCase()}";
-// }
-// }
-// }
-//
-// return "";
-// }
-//
-// //============================================================================
-// // public state updaters
-// //============================================================================
-//
-// void reset(bool shouldNotifyListeners) {
-// _exchange = null;
-// _reversed = false;
-// _rate = null;
-// _sendAmount = null;
-// _receiveAmount = null;
-// _sendCurrency = null;
-// _receiveCurrency = null;
-// _minSendAmount = null;
-// _minReceiveAmount = null;
-// _maxSendAmount = null;
-// _maxReceiveAmount = null;
-//
-// if (shouldNotifyListeners) {
-// notifyListeners();
-// }
-// }
-//
-// Future setFromAmountAndCalculateToAmount(
-// Decimal? newSendAmount,
-// bool shouldNotifyListeners,
-// ) async {
-// if (newSendAmount == null) {
-// // todo: check if this breaks things and stuff
-// _receiveAmount = null;
-// _sendAmount = null;
-// } else {
-// if (newSendAmount <= Decimal.zero) {
-// _receiveAmount = Decimal.zero;
-// }
-//
-// _sendAmount = newSendAmount;
-// _reversed = false;
-//
-// await _updateRangesAndEstimate(
-// shouldNotifyListeners: false,
-// );
-// }
-//
-// if (shouldNotifyListeners) {
-// notifyListeners();
-// }
-// }
-//
-// Future setToAmountAndCalculateFromAmount(
-// Decimal? newReceiveAmount,
-// bool shouldNotifyListeners,
-// ) async {
-// if (newReceiveAmount == null) {
-// // todo: check if this breaks things and stuff
-// _receiveAmount = null;
-// _sendAmount = null;
-// } else {
-// if (newReceiveAmount <= Decimal.zero) {
-// _sendAmount = Decimal.zero;
-// }
-//
-// _receiveAmount = newReceiveAmount;
-// _reversed = true;
-//
-// await _updateRangesAndEstimate(
-// shouldNotifyListeners: false,
-// );
-// }
-//
-// if (shouldNotifyListeners) {
-// notifyListeners();
-// }
-// }
-//
-// Future updateFrom(
-// Currency sendCurrency,
-// bool shouldNotifyListeners,
-// ) async {
-// try {
-// _sendCurrency = sendCurrency;
-// if (_receiveCurrency == null) {
-// _rate = null;
-// } else {
-// await _updateRangesAndEstimate(
-// shouldNotifyListeners: false,
-// );
-// }
-// } catch (e, s) {
-// Logging.instance.log("$e\n$s", level: LogLevel.Error);
-// }
-// if (shouldNotifyListeners) {
-// notifyListeners();
-// }
-// }
-//
-// Future updateTo(
-// Currency receiveCurrency,
-// bool shouldNotifyListeners,
-// ) async {
-// try {
-// _receiveCurrency = receiveCurrency;
-//
-// if (_sendCurrency == null) {
-// _rate = null;
-// } else {
-// await _updateRangesAndEstimate(
-// shouldNotifyListeners: false,
-// );
-// }
-// } catch (e, s) {
-// Logging.instance.log("$e\n$s", level: LogLevel.Error);
-// }
-// if (shouldNotifyListeners) {
-// notifyListeners();
-// }
-// }
-//
-// Future swap(
-// {required bool shouldNotifyListeners,}) async {
-// final Decimal? temp = sendAmount;
-// _sendAmount = receiveAmount;
-// _receiveAmount = temp;
-//
-// _minSendAmount = null;
-// _maxSendAmount = null;
-// _minReceiveAmount = null;
-// _maxReceiveAmount = null;
-//
-// final Currency? tmp = sendCurrency;
-// _sendCurrency = receiveCurrency;
-// _receiveCurrency = tmp;
-//
-// await _updateRangesAndEstimate(
-// shouldNotifyListeners: false,
-// );
-// }
-//
-// //============================================================================
-// // private state updaters
-// //============================================================================
-//
-// Future _updateRangesAndEstimate(
-// {required bool shouldNotifyListeners,}) async {
-// await _updateRanges(shouldNotifyListeners: false);
-// await _updateEstimate(shouldNotifyListeners: false);
-// if (shouldNotifyListeners) {
-// notifyListeners();
-// }
-// }
-//
-// Future _updateRanges({required bool shouldNotifyListeners,}) async {
-// // if (exchange?.name == SimpleSwapExchange.exchangeName) {
-// // reversed = false;
-// // }
-// final _send = sendCurrency;
-// final _receive = receiveCurrency;
-// if (_send == null || _receive == null) {
-// Logging.instance.log(
-// "Tried to $runtimeType.updateRanges where ( $_send || $_receive) for: $exchange",
-// level: LogLevel.Info,
-// );
-// return;
-// }
-// final response = await exchange.getRange(
-// _send.ticker,
-// _receive.ticker,
-// exchangeRateType == ExchangeRateType.fixed,
-// );
-//
-// if (response.value == null) {
-// Logging.instance.log(
-// "Tried to $runtimeType.updateRanges for: $exchange where response: $response",
-// level: LogLevel.Info,
-// );
-// return;
-// }
-// final responseReversed = await exchange.getRange(
-// _receive.ticker,
-// _send.ticker,
-// exchangeRateType == ExchangeRateType.fixed,
-// );
-//
-// if (responseReversed.value == null) {
-// Logging.instance.log(
-// "Tried to $runtimeType.updateRanges for: $exchange where response: $responseReversed",
-// level: LogLevel.Info,
-// );
-// return;
-// }
-//
-// final range = response.value!;
-// final rangeReversed = responseReversed.value!;
-//
-// _minSendAmount = range.min;
-// _maxSendAmount = range.max;
-// _minReceiveAmount = rangeReversed.min;
-// _maxReceiveAmount = rangeReversed.max;
-//
-// //todo: check if print needed
-// // debugPrint(
-// // "updated range for: $exchange for $_fromTicker-$_toTicker: $range");
-//
-// if (shouldNotifyListeners) {
-// notifyListeners();
-// }
-// }
-//
-// Future _updateEstimate({
-// required bool shouldNotifyListeners,
-// }) async {
-// // if (exchange?.name == SimpleSwapExchange.exchangeName) {
-// // reversed = false;
-// // }
-// final amount = reversed ? receiveAmount : sendAmount;
-// if (sendCurrency == null ||
-// receiveCurrency == null ||
-// amount == null ||
-// amount <= Decimal.zero) {
-// Logging.instance.log(
-// "Tried to $runtimeType.updateEstimate for: $exchange where (from: $sendCurrency || to: $receiveCurrency || amount: $amount)",
-// level: LogLevel.Info,
-// );
-// return;
-// }
-// final response = await exchange.getEstimate(
-// sendCurrency!.ticker,
-// receiveCurrency!.ticker,
-// amount,
-// exchangeRateType == ExchangeRateType.fixed,
-// reversed,
-// );
-//
-// if (response.value == null) {
-// Logging.instance.log(
-// "Tried to $runtimeType.updateEstimate for: $exchange where response: $response",
-// level: LogLevel.Info,
-// );
-// return;
-// }
-//
-// final estimate = response.value!;
-//
-// if (reversed) {
-// _sendAmount = estimate.estimatedAmount;
-// } else {
-// _receiveAmount = estimate.estimatedAmount;
-// }
-//
-// _rate =
-// (receiveAmount! / sendAmount!).toDecimal(scaleOnInfinitePrecision: 12);
-//
-// //todo: check if print needed
-// // debugPrint(
-// // "updated estimate for: $exchange for $fromTicker-$toTicker: $estimate");
-//
-// if (shouldNotifyListeners) {
-// notifyListeners();
-// }
-// }
-//
-//
-// }
diff --git a/lib/services/exchange/change_now/change_now_api.dart b/lib/services/exchange/change_now/change_now_api.dart
index 57823a813..e14b2f128 100644
--- a/lib/services/exchange/change_now/change_now_api.dart
+++ b/lib/services/exchange/change_now/change_now_api.dart
@@ -100,12 +100,18 @@ class ChangeNowAPI {
body: jsonEncode(body),
);
- final parsed = jsonDecode(response.body);
+ try {
+ final parsed = jsonDecode(response.body);
- return parsed;
+ return parsed;
+ } catch (_) {
+ Logging.instance.log("ChangeNOW api failed to parse: ${response.body}",
+ level: LogLevel.Error);
+ rethrow;
+ }
} catch (e, s) {
Logging.instance
- .log("_makeRequest($uri) threw: $e\n$s", level: LogLevel.Error);
+ .log("_makePostRequest($uri) threw: $e\n$s", level: LogLevel.Error);
rethrow;
}
}
@@ -483,6 +489,7 @@ class ChangeNowAPI {
reversed: false,
rateId: value.rateId,
warningMessage: value.warningMessage,
+ exchangeProvider: ChangeNowExchange.exchangeName,
),
);
} catch (_) {
@@ -566,6 +573,7 @@ class ChangeNowAPI {
reversed: reversed,
rateId: value.rateId,
warningMessage: value.warningMessage,
+ exchangeProvider: ChangeNowExchange.exchangeName,
),
);
} catch (_) {
diff --git a/lib/services/exchange/change_now/change_now_exchange.dart b/lib/services/exchange/change_now/change_now_exchange.dart
index 3189ff84e..a6037b511 100644
--- a/lib/services/exchange/change_now/change_now_exchange.dart
+++ b/lib/services/exchange/change_now/change_now_exchange.dart
@@ -31,7 +31,7 @@ class ChangeNowExchange extends Exchange {
String? extraId,
required String addressRefund,
required String refundExtraId,
- String? rateId,
+ Estimate? estimate,
required bool reversed,
}) async {
late final ExchangeResponse response;
@@ -41,7 +41,7 @@ class ChangeNowExchange extends Exchange {
toTicker: to,
receivingAddress: addressTo,
amount: amount,
- rateId: rateId!,
+ rateId: estimate!.rateId!,
extraId: extraId ?? "",
refundAddress: addressRefund,
refundExtraId: refundExtraId,
@@ -128,7 +128,7 @@ class ChangeNowExchange extends Exchange {
}
@override
- Future> getEstimate(
+ Future>> getEstimates(
String from,
String to,
Decimal amount,
@@ -151,7 +151,10 @@ class ChangeNowExchange extends Exchange {
fromAmount: amount,
);
}
- return response;
+ return ExchangeResponse(
+ value: response.value == null ? null : [response.value!],
+ exception: response.exception,
+ );
}
@override
diff --git a/lib/services/exchange/exchange.dart b/lib/services/exchange/exchange.dart
index e4f5ce8d5..1db451457 100644
--- a/lib/services/exchange/exchange.dart
+++ b/lib/services/exchange/exchange.dart
@@ -8,6 +8,7 @@ import 'package:stackwallet/services/exchange/change_now/change_now_exchange.dar
import 'package:stackwallet/services/exchange/exchange_response.dart';
import 'package:stackwallet/services/exchange/majestic_bank/majestic_bank_exchange.dart';
import 'package:stackwallet/services/exchange/simpleswap/simpleswap_exchange.dart';
+import 'package:stackwallet/services/exchange/trocador/trocador_exchange.dart';
abstract class Exchange {
static Exchange get defaultExchange => ChangeNowExchange.instance;
@@ -20,7 +21,14 @@ abstract class Exchange {
return SimpleSwapExchange.instance;
case MajesticBankExchange.exchangeName:
return MajesticBankExchange.instance;
+ case TrocadorExchange.exchangeName:
+ return TrocadorExchange.instance;
default:
+ final split = name.split(" ");
+ if (split.length >= 2) {
+ // silly way to check for 'Trocador ($providerName)'
+ return fromName(split.first);
+ }
throw ArgumentError("Unknown exchange name");
}
}
@@ -52,7 +60,7 @@ abstract class Exchange {
bool fixedRate,
);
- Future> getEstimate(
+ Future>> getEstimates(
String from,
String to,
Decimal amount,
@@ -69,7 +77,7 @@ abstract class Exchange {
String? extraId,
required String addressRefund,
required String refundExtraId,
- String? rateId,
+ Estimate? estimate,
required bool reversed,
});
}
diff --git a/lib/services/exchange/exchange_data_loading_service.dart b/lib/services/exchange/exchange_data_loading_service.dart
index 2da6016f4..c0aa2e2b0 100644
--- a/lib/services/exchange/exchange_data_loading_service.dart
+++ b/lib/services/exchange/exchange_data_loading_service.dart
@@ -1,12 +1,13 @@
import 'package:flutter/foundation.dart';
import 'package:isar/isar.dart';
import 'package:stackwallet/db/hive/db.dart';
+import 'package:stackwallet/models/exchange/active_pair.dart';
import 'package:stackwallet/models/exchange/aggregate_currency.dart';
-import 'package:stackwallet/models/exchange/exchange_form_state.dart';
import 'package:stackwallet/models/isar/exchange_cache/currency.dart';
import 'package:stackwallet/models/isar/exchange_cache/pair.dart';
import 'package:stackwallet/services/exchange/change_now/change_now_exchange.dart';
import 'package:stackwallet/services/exchange/majestic_bank/majestic_bank_exchange.dart';
+import 'package:stackwallet/services/exchange/trocador/trocador_exchange.dart';
import 'package:stackwallet/utilities/enums/exchange_rate_type_enum.dart';
import 'package:stackwallet/utilities/logger.dart';
import 'package:stackwallet/utilities/stack_file_system.dart';
@@ -56,20 +57,29 @@ class ExchangeDataLoadingService {
);
}
- Future setCurrenciesIfEmpty(ExchangeFormState state) async {
- if (state.sendCurrency == null && state.receiveCurrency == null) {
+ Future setCurrenciesIfEmpty(
+ ActivePair? pair,
+ ExchangeRateType rateType,
+ ) async {
+ if (pair?.send == null && pair?.receive == null) {
if (await isar.currencies.count() > 0) {
- final sendCurrency = await getAggregateCurrency(
- "BTC",
- state.exchangeRateType,
- null,
+ pair?.setSend(
+ await getAggregateCurrency(
+ "BTC",
+ rateType,
+ null,
+ ),
+ notifyListeners: false,
);
- final receiveCurrency = await getAggregateCurrency(
- "XMR",
- state.exchangeRateType,
- null,
+
+ pair?.setReceive(
+ await getAggregateCurrency(
+ "XMR",
+ rateType,
+ null,
+ ),
+ notifyListeners: false,
);
- state.setCurrencies(sendCurrency, receiveCurrency);
}
}
}
@@ -128,6 +138,7 @@ class ExchangeDataLoadingService {
// loadSimpleswapFixedRateCurrencies(ref),
// loadSimpleswapFloatingRateCurrencies(ref),
loadMajesticBankCurrencies(),
+ loadTrocadorCurrencies(),
]);
// quicker to load available currencies on the fly for a specific base currency
@@ -302,6 +313,28 @@ class ExchangeDataLoadingService {
}
}
+ Future loadTrocadorCurrencies() async {
+ final exchange = TrocadorExchange.instance;
+ final responseCurrencies = await exchange.getAllCurrencies(false);
+
+ if (responseCurrencies.value != null) {
+ await isar.writeTxn(() async {
+ final idsToDelete = await isar.currencies
+ .where()
+ .exchangeNameEqualTo(TrocadorExchange.exchangeName)
+ .idProperty()
+ .findAll();
+ await isar.currencies.deleteAll(idsToDelete);
+ await isar.currencies.putAll(responseCurrencies.value!);
+ });
+ } else {
+ Logging.instance.log(
+ "loadTrocadorCurrencies: $responseCurrencies",
+ level: LogLevel.Warning,
+ );
+ }
+ }
+
// Future loadMajesticBankPairs() async {
// final exchange = MajesticBankExchange.instance;
//
diff --git a/lib/services/exchange/exchange_service.dart b/lib/services/exchange/exchange_service.dart
deleted file mode 100644
index 036c522de..000000000
--- a/lib/services/exchange/exchange_service.dart
+++ /dev/null
@@ -1 +0,0 @@
-class ExchangeService {}
diff --git a/lib/services/exchange/majestic_bank/majestic_bank_exchange.dart b/lib/services/exchange/majestic_bank/majestic_bank_exchange.dart
index dcc63c68b..95031490c 100644
--- a/lib/services/exchange/majestic_bank/majestic_bank_exchange.dart
+++ b/lib/services/exchange/majestic_bank/majestic_bank_exchange.dart
@@ -44,7 +44,7 @@ class MajesticBankExchange extends Exchange {
String? extraId,
required String addressRefund,
required String refundExtraId,
- String? rateId,
+ Estimate? estimate,
required bool reversed,
}) async {
ExchangeResponse? response;
@@ -170,7 +170,7 @@ class MajesticBankExchange extends Exchange {
}
@override
- Future> getEstimate(
+ Future>> getEstimates(
String from,
String to,
Decimal amount,
@@ -192,8 +192,9 @@ class MajesticBankExchange extends Exchange {
estimatedAmount: reversed ? calc.fromAmount : calc.receiveAmount,
fixedRate: fixedRate,
reversed: reversed,
+ exchangeProvider: MajesticBankExchange.exchangeName,
);
- return ExchangeResponse(value: estimate);
+ return ExchangeResponse(value: [estimate]);
}
@override
diff --git a/lib/services/exchange/simpleswap/simpleswap_exchange.dart b/lib/services/exchange/simpleswap/simpleswap_exchange.dart
index 1157bd09e..0bfe93a38 100644
--- a/lib/services/exchange/simpleswap/simpleswap_exchange.dart
+++ b/lib/services/exchange/simpleswap/simpleswap_exchange.dart
@@ -30,7 +30,7 @@ class SimpleSwapExchange extends Exchange {
String? extraId,
required String addressRefund,
required String refundExtraId,
- String? rateId,
+ Estimate? estimate,
required bool reversed,
}) async {
return await SimpleSwapAPI.instance.createNewExchange(
@@ -89,7 +89,7 @@ class SimpleSwapExchange extends Exchange {
}
@override
- Future> getEstimate(
+ Future>> getEstimates(
String from,
String to,
Decimal amount,
@@ -109,11 +109,14 @@ class SimpleSwapExchange extends Exchange {
}
return ExchangeResponse(
- value: Estimate(
- estimatedAmount: Decimal.parse(response.value!),
- fixedRate: fixedRate,
- reversed: reversed,
- ),
+ value: [
+ Estimate(
+ estimatedAmount: Decimal.parse(response.value!),
+ fixedRate: fixedRate,
+ reversed: reversed,
+ exchangeProvider: SimpleSwapExchange.exchangeName,
+ ),
+ ],
);
}
diff --git a/lib/services/exchange/trocador/response_objects/trocador_coin.dart b/lib/services/exchange/trocador/response_objects/trocador_coin.dart
new file mode 100644
index 000000000..aea01d2f1
--- /dev/null
+++ b/lib/services/exchange/trocador/response_objects/trocador_coin.dart
@@ -0,0 +1,44 @@
+import 'package:decimal/decimal.dart';
+
+class TrocadorCoin {
+ final String name;
+ final String ticker;
+ final String network;
+ final bool memo;
+ final String image;
+ final Decimal minimum;
+ final Decimal maximum;
+
+ TrocadorCoin({
+ required this.name,
+ required this.ticker,
+ required this.network,
+ required this.memo,
+ required this.image,
+ required this.minimum,
+ required this.maximum,
+ });
+
+ factory TrocadorCoin.fromMap(Map json) => TrocadorCoin(
+ name: json['name'] as String,
+ ticker: json['ticker'] as String,
+ network: json['network'] as String,
+ memo: json['memo'] as bool,
+ image: json['image'] as String,
+ minimum: Decimal.parse(json['minimum'].toString()),
+ maximum: Decimal.parse(json['maximum'].toString()),
+ );
+
+ @override
+ String toString() {
+ return 'TrocadorCoin( '
+ 'name: $name, '
+ 'ticker: $ticker, '
+ 'network: $network, '
+ 'memo: $memo, '
+ 'image: $image, '
+ 'minimum: $minimum, '
+ 'maximum: $maximum '
+ ')';
+ }
+}
diff --git a/lib/services/exchange/trocador/response_objects/trocador_quote.dart b/lib/services/exchange/trocador/response_objects/trocador_quote.dart
new file mode 100644
index 000000000..ada8725f5
--- /dev/null
+++ b/lib/services/exchange/trocador/response_objects/trocador_quote.dart
@@ -0,0 +1,47 @@
+import 'package:decimal/decimal.dart';
+
+class TrocadorQuote {
+ final String provider;
+ final String kycRating;
+ final int insurance;
+ final bool fixed;
+ final Decimal? amountTo;
+ final Decimal? amountFrom;
+ final Decimal waste;
+
+ TrocadorQuote({
+ required this.provider,
+ required this.kycRating,
+ required this.insurance,
+ required this.fixed,
+ required this.amountTo,
+ required this.amountFrom,
+ required this.waste,
+ });
+
+ factory TrocadorQuote.fromMap(Map map) {
+ return TrocadorQuote(
+ provider: map['provider'] as String,
+ kycRating: map['kycrating'] as String,
+ insurance: map['insurance'] as int,
+ // wtf trocador?
+ fixed: map['fixed'] == "True",
+ amountTo: Decimal.tryParse(map['amount_to'].toString()),
+ amountFrom: Decimal.tryParse(map['amount_from'].toString()),
+ waste: Decimal.parse(map['waste'].toString()),
+ );
+ }
+
+ @override
+ String toString() {
+ return 'TrocadorQuote( '
+ 'provider: $provider, '
+ 'kycRating: $kycRating, '
+ 'insurance: $insurance, '
+ 'fixed: $fixed, '
+ 'amountTo: $amountTo, '
+ 'amountFrom: $amountFrom, '
+ 'waste: $waste '
+ ')';
+ }
+}
diff --git a/lib/services/exchange/trocador/response_objects/trocador_rate.dart b/lib/services/exchange/trocador/response_objects/trocador_rate.dart
new file mode 100644
index 000000000..5c3d18da2
--- /dev/null
+++ b/lib/services/exchange/trocador/response_objects/trocador_rate.dart
@@ -0,0 +1,84 @@
+import 'package:decimal/decimal.dart';
+import 'package:stackwallet/services/exchange/trocador/response_objects/trocador_quote.dart';
+
+class TrocadorRate {
+ final String tradeId;
+ final DateTime date;
+ final String tickerFrom;
+ final String tickerTo;
+ final String coinFrom;
+ final String coinTo;
+ final String networkFrom;
+ final String networkTo;
+ final Decimal amountFrom;
+ final Decimal amountTo;
+ final String provider;
+ final bool fixed;
+ final bool payment;
+ final String status;
+ final List quotes;
+
+ TrocadorRate({
+ required this.tradeId,
+ required this.date,
+ required this.tickerFrom,
+ required this.tickerTo,
+ required this.coinFrom,
+ required this.coinTo,
+ required this.networkFrom,
+ required this.networkTo,
+ required this.amountFrom,
+ required this.amountTo,
+ required this.provider,
+ required this.fixed,
+ required this.payment,
+ required this.status,
+ required this.quotes,
+ });
+
+ factory TrocadorRate.fromMap(Map map) {
+ final list =
+ List