Merge pull request #319 from cypherstack/simplex

Add sandboxed Simplex buy support
This commit is contained in:
Diego Salazar 2023-01-25 12:30:44 -07:00 committed by GitHub
commit 0cc21fa8af
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
55 changed files with 4914 additions and 254 deletions

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 10 KiB

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 10 KiB

View file

@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 336.41 337.42"><defs><style>.cls-1{fill:#f0b90b;stroke:#f0b90b;}</style></defs><title>Asset 1</title><g id="Layer_2" data-name="Layer 2"><g id="Layer_1-2" data-name="Layer 1"><path class="cls-1" d="M168.2.71l41.5,42.5L105.2,147.71l-41.5-41.5Z"/><path class="cls-1" d="M231.2,63.71l41.5,42.5L105.2,273.71l-41.5-41.5Z"/><path class="cls-1" d="M42.2,126.71l41.5,42.5-41.5,41.5L.7,169.21Z"/><path class="cls-1" d="M294.2,126.71l41.5,42.5L168.2,336.71l-41.5-41.5Z"/></g></g></svg>

After

Width:  |  Height:  |  Size: 528 B

View file

@ -0,0 +1 @@
<svg id="Layer_1" data-name="Layer 1" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 2500 2500"><title>cosmos-atom-logo</title><circle cx="1250" cy="1250" r="1250" style="fill:#2e3148"/><circle cx="1250" cy="1250" r="725.31" style="fill:#1b1e36"/><path d="M1252.57,159.47c-134.93,0-244.34,489.4-244.34,1093.11s109.41,1093.11,244.34,1093.11,244.34-489.4,244.34-1093.11S1387.5,159.47,1252.57,159.47ZM1269.44,2284c-15.43,20.58-30.86,5.14-30.86,5.14-62.14-72-93.21-205.76-93.21-205.76-108.69-349.79-82.82-1100.82-82.82-1100.82,51.08-596.24,144-737.09,175.62-768.36a19.29,19.29,0,0,1,24.74-2c45.88,32.51,84.36,168.47,84.36,168.47,113.63,421.81,103.34,817.9,103.34,817.9,10.29,344.65-56.94,730.45-56.94,730.45C1341.92,2222.22,1269.44,2284,1269.44,2284Z" style="fill:#6f7390"/><path d="M2200.72,708.59c-67.18-117.08-546.09,31.58-1070,332s-893.47,638.89-826.34,755.92,546.09-31.58,1070-332,893.47-638.89,826.34-755.92h0ZM366.36,1780.45c-25.72-3.24-19.91-24.38-19.91-24.38C378,1666.36,478.4,1572.84,478.4,1572.84c249.43-268.36,913.79-619.65,913.79-619.65,542.54-252.42,711.06-241.77,753.81-230a19.29,19.29,0,0,1,14,20.58c-5.14,56-104.17,157-104.17,157C1746.71,1209.36,1398,1397.58,1398,1397.58c-293.83,180.5-661.93,314.09-661.93,314.09-280.09,100.93-369.7,68.78-369.7,68.78h0Z" style="fill:#6f7390"/><path d="M2198.35,1800.41c67.7-116.77-300.93-456.79-823-759.47S374.43,587.76,306.79,704.73s300.93,456.79,823.3,759.47S2130.71,1917.39,2198.35,1800.41ZM351.65,749.85c-10-23.71,11.11-29.42,11.11-29.42C456.22,702.78,587.5,743,587.5,743c357.15,81.33,994,480.25,994,480.25,490.33,343.11,565.53,494.24,576.8,537.14a19.29,19.29,0,0,1-10.7,22.43c-51.13,23.41-188.07-11.47-188.07-11.47-422.07-113.17-759.62-320.52-759.62-320.52-303.29-163.58-603.19-415.28-603.19-415.28-227.88-191.87-245-285.44-245-285.44Z" style="fill:#6f7390"/><circle cx="1250" cy="1250" r="128.6" style="fill:#b7b9c8"/><ellipse cx="1777.26" cy="756.17" rx="74.59" ry="77.16" style="fill:#b7b9c8"/><ellipse cx="552.98" cy="1018.52" rx="74.59" ry="77.16" style="fill:#b7b9c8"/><ellipse cx="1098.25" cy="1965.02" rx="74.59" ry="77.16" style="fill:#b7b9c8"/></svg>

After

Width:  |  Height:  |  Size: 2.1 KiB

View file

@ -0,0 +1,10 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
<!-- Creator: CorelDRAW 2019 (64-Bit) -->
<svg xmlns="http://www.w3.org/2000/svg" xml:space="preserve" width="100%" height="100%" version="1.1" shape-rendering="geometricPrecision" text-rendering="geometricPrecision" image-rendering="optimizeQuality" fill-rule="evenodd" clip-rule="evenodd" viewBox="0 0 444.44 444.44" xmlns:xlink="http://www.w3.org/1999/xlink" xmlns:xodm="http://www.corel.com/coreldraw/odm/2003">
<g id="Layer_x0020_1">
<metadata id="CorelCorpID_0Corel-Layer"/>
<path fill="#F5AC37" fill-rule="nonzero" d="M222.22 0c122.74,0 222.22,99.5 222.22,222.22 0,122.74 -99.48,222.22 -222.22,222.22 -122.72,0 -222.22,-99.49 -222.22,-222.22 0,-122.72 99.5,-222.22 222.22,-222.22z"/>
<path fill="#FEFEFD" fill-rule="nonzero" d="M230.41 237.91l84.44 0c1.8,0 2.65,0 2.78,-2.36 0.69,-8.59 0.69,-17.23 0,-25.83 0,-1.67 -0.83,-2.36 -2.64,-2.36l-168.05 0c-2.08,0 -2.64,0.69 -2.64,2.64l0 24.72c0,3.19 0,3.19 3.33,3.19l82.78 0zm77.79 -59.44c0.24,-0.63 0.24,-1.32 0,-1.94 -1.41,-3.07 -3.08,-6 -5.02,-8.75 -2.92,-4.7 -6.36,-9.03 -10.28,-12.92 -1.85,-2.35 -3.99,-4.46 -6.39,-6.25 -12.02,-10.23 -26.31,-17.47 -41.67,-21.11 -7.75,-1.74 -15.67,-2.57 -23.61,-2.5l-74.58 0c-2.08,0 -2.36,0.83 -2.36,2.64l0 49.3c0,2.08 0,2.64 2.64,2.64l160.27 0c0,0 1.39,-0.28 1.67,-1.11l-0.68 0zm0 88.33c-2.36,-0.26 -4.74,-0.26 -7.1,0l-154.02 0c-2.08,0 -2.78,0 -2.78,2.78l0 48.2c0,2.22 0,2.78 2.78,2.78l71.11 0c3.4,0.26 6.8,0.02 10.13,-0.69 10.32,-0.74 20.47,-2.98 30.15,-6.67 3.52,-1.22 6.92,-2.81 10.13,-4.72l0.97 0c16.67,-8.67 30.21,-22.29 38.75,-39.01 0,0 0.97,-2.1 -0.12,-2.65zm-191.81 78.75l0 -0.83 0 -32.36 0 -10.97 0 -32.64c0,-1.81 0,-2.08 -2.22,-2.08l-30.14 0c-1.67,0 -2.36,0 -2.36,-2.22l0 -26.39 32.22 0c1.8,0 2.5,0 2.5,-2.36l0 -26.11c0,-1.67 0,-2.08 -2.22,-2.08l-30.14 0c-1.67,0 -2.36,0 -2.36,-2.22l0 -24.44c0,-1.53 0,-1.94 2.22,-1.94l29.86 0c2.08,0 2.64,0 2.64,-2.64l0 -74.86c0,-2.22 0,-2.78 2.78,-2.78l104.16 0c7.56,0.3 15.07,1.13 22.5,2.5 15.31,2.83 30.02,8.3 43.47,16.11 8.92,5.25 17.13,11.59 24.44,18.89 5.5,5.71 10.46,11.89 14.86,18.47 4.37,6.67 8,13.8 10.85,21.25 0.35,1.94 2.21,3.25 4.15,2.92l24.86 0c3.19,0 3.19,0 3.33,3.06l0 22.78c0,2.22 -0.83,2.78 -3.06,2.78l-19.17 0c-1.94,0 -2.5,0 -2.36,2.5 0.76,8.46 0.76,16.95 0,25.41 0,2.36 0,2.64 2.65,2.64l21.93 0c0.97,1.25 0,2.5 0,3.76 0.14,1.61 0.14,3.24 0,4.85l0 16.81c0,2.36 -0.69,3.06 -2.78,3.06l-26.25 0c-1.83,-0.35 -3.61,0.82 -4.03,2.64 -6.25,16.25 -16.25,30.82 -29.17,42.5 -4.72,4.25 -9.68,8.25 -14.86,11.94 -5.56,3.2 -10.97,6.53 -16.67,9.17 -10.49,4.72 -21.49,8.2 -32.78,10.41 -10.72,1.92 -21.59,2.79 -32.5,2.64l-96.39 0 0 -0.14z"/>
</g>
</svg>

After

Width:  |  Height:  |  Size: 2.7 KiB

View file

@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 513.4 416.8"><defs><style>.cls-1{fill:#008de4;}</style></defs><title>d</title><g id="Layer_2" data-name="Layer 2"><g id="Layer_1-2" data-name="Layer 1"><path class="cls-1" d="M336.25,0H149.35l-15.5,86.6,168.7.2c83.1,0,107.6,30.2,106.9,80.2-.4,25.6-11.5,69-16.3,83.1-12.8,37.5-39.1,80.2-137.7,80.1l-164-.1L76,416.8h186.5c65.8,0,93.7-7.7,123.4-21.3,65.7-30.5,104.8-95.3,120.5-179.9C529.65,89.6,500.65,0,336.25,0"/><path class="cls-1" d="M68.7,164.9c-49,0-56,31.9-60.6,51.2C2,241.3,0,251.6,0,251.6H191.4c49,0,56-31.9,60.6-51.2,6.1-25.2,8.1-35.5,8.1-35.5Z"/></g></g></svg>

After

Width:  |  Height:  |  Size: 621 B

View file

@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 33.2 50"><title>Asset 1</title><g id="Layer_2" data-name="Layer 2"><g id="Layer_1-2" data-name="Layer 1"><path d="M16.6,0,4.9,16.1,0,39.9,16.6,50,33.2,39.9,28.2,16ZM2.7,38.8,6.4,20.7l8.4,25.5ZM7.6,16.6l9-12.4,9,12.4-9,27.2ZM18.3,46.2l8.4-25.5,3.7,18.1Z"/></g></g></svg>

After

Width:  |  Height:  |  Size: 322 B

View file

@ -0,0 +1,18 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
<!-- Creator: CorelDRAW 2019 (64-Bit) -->
<svg xmlns="http://www.w3.org/2000/svg" xml:space="preserve" width="100%" height="100%" version="1.1" shape-rendering="geometricPrecision" text-rendering="geometricPrecision" image-rendering="optimizeQuality" fill-rule="evenodd" clip-rule="evenodd" viewBox="0 0 784.37 1277.39" xmlns:xlink="http://www.w3.org/1999/xlink" xmlns:xodm="http://www.corel.com/coreldraw/odm/2003">
<g id="Layer_x0020_1">
<metadata id="CorelCorpID_0Corel-Layer"/>
<g id="_1421394342400">
<g>
<polygon fill="#343434" fill-rule="nonzero" points="392.07,0 383.5,29.11 383.5,873.74 392.07,882.29 784.13,650.54 "/>
<polygon fill="#8C8C8C" fill-rule="nonzero" points="392.07,0 -0,650.54 392.07,882.29 392.07,472.33 "/>
<polygon fill="#3C3C3B" fill-rule="nonzero" points="392.07,956.52 387.24,962.41 387.24,1263.28 392.07,1277.38 784.37,724.89 "/>
<polygon fill="#8C8C8C" fill-rule="nonzero" points="392.07,1277.38 392.07,956.52 -0,724.89 "/>
<polygon fill="#141414" fill-rule="nonzero" points="392.07,882.29 784.13,650.54 392.07,472.33 "/>
<polygon fill="#393939" fill-rule="nonzero" points="0,650.54 392.07,882.29 392.07,472.33 "/>
</g>
</g>
</g>
</svg>

After

Width:  |  Height:  |  Size: 1.3 KiB

View file

@ -1,11 +1 @@
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
<g clip-path="url(#clip0_6052_99642)">
<rect width="24" height="24" rx="12" fill="white"/>
<path d="M11.9976 0C9.62389 0 7.30353 0.703873 5.3299 2.02261C3.35627 3.34135 1.81802 5.21572 0.909655 7.4087C0.00129377 9.60167 -0.236375 12.0148 0.226704 14.3428C0.689782 16.6709 1.83281 18.8093 3.51124 20.4878C5.18968 22.1662 7.32813 23.3092 9.65618 23.7723C11.9842 24.2354 14.3973 23.9977 16.5903 23.0893C18.7833 22.181 20.6577 20.6427 21.9764 18.6691C23.2951 16.6955 23.999 14.3751 23.999 12.0015C23.999 10.4254 23.6886 8.86478 23.0854 7.4087C22.4823 5.95261 21.5983 4.62958 20.4839 3.51514C19.3694 2.40071 18.0464 1.51669 16.5903 0.913556C15.1342 0.310427 13.5736 0 11.9976 0V0ZM12.1921 12.3963L10.9437 16.6087H17.6209C17.674 16.6088 17.7263 16.6213 17.7738 16.6451C17.8212 16.669 17.8625 16.7035 17.8943 16.746C17.9261 16.7885 17.9476 16.8378 17.9571 16.8901C17.9666 16.9423 17.9638 16.9961 17.9489 17.0471L17.3683 19.0473C17.3406 19.1429 17.2826 19.2268 17.203 19.2865C17.1234 19.3462 17.0265 19.3784 16.927 19.3783H6.72841L8.45286 13.5546L6.54551 14.1352L6.96646 12.7737L8.87671 12.1931L11.2979 4.0121C11.3245 3.91623 11.3818 3.8317 11.4609 3.77142C11.5401 3.71114 11.6368 3.67842 11.7363 3.67824H14.32C14.3731 3.67805 14.4254 3.69017 14.4729 3.71364C14.5205 3.73712 14.5619 3.77131 14.594 3.81352C14.6261 3.85573 14.6479 3.90482 14.6578 3.95691C14.6677 4.009 14.6654 4.06267 14.651 4.11371L12.6188 11.0318L14.5262 10.4512L14.1168 11.836L12.1921 12.3963Z" fill="#315D9E"/>
</g>
<defs>
<clipPath id="clip0_6052_99642">
<rect width="24" height="24" rx="12" fill="white"/>
</clipPath>
</defs>
</svg>
<svg id="Layer_1" data-name="Layer 1" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 82.6 82.6"><title>litecoin-ltc-logo</title><circle cx="41.3" cy="41.3" r="36.83" style="fill:#fff"/><path d="M41.3,0A41.3,41.3,0,1,0,82.6,41.3h0A41.18,41.18,0,0,0,41.54,0ZM42,42.7,37.7,57.2h23a1.16,1.16,0,0,1,1.2,1.12v.38l-2,6.9a1.49,1.49,0,0,1-1.5,1.1H23.2l5.9-20.1-6.6,2L24,44l6.6-2,8.3-28.2a1.51,1.51,0,0,1,1.5-1.1h8.9a1.16,1.16,0,0,1,1.2,1.12v.38L43.5,38l6.6-2-1.4,4.8Z" style="fill:#345d9d"/></svg>

Before

Width:  |  Height:  |  Size: 1.7 KiB

After

Width:  |  Height:  |  Size: 489 B

View file

@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 424"><defs><style>.cls-1{fill:#23292f;}</style></defs><title>x</title><g id="Layer_2" data-name="Layer 2"><g id="Layer_1-2" data-name="Layer 1"><path class="cls-1" d="M437,0h74L357,152.48c-55.77,55.19-146.19,55.19-202,0L.94,0H75L192,115.83a91.11,91.11,0,0,0,127.91,0Z"/><path class="cls-1" d="M74.05,424H0L155,270.58c55.77-55.19,146.19-55.19,202,0L512,424H438L320,307.23a91.11,91.11,0,0,0-127.91,0Z"/></g></g></svg>

After

Width:  |  Height:  |  Size: 472 B

View file

@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 236.36 200"><title>Asset 1</title><g id="Layer_2" data-name="Layer 2"><g id="Layer_1-2" data-name="Layer 1"><path d="M203,26.16l-28.46,14.5-137.43,70a82.49,82.49,0,0,1-.7-10.69A81.87,81.87,0,0,1,158.2,28.6l16.29-8.3,2.43-1.24A100,100,0,0,0,18.18,100q0,3.82.29,7.61a18.19,18.19,0,0,1-9.88,17.58L0,129.57V150l25.29-12.89,0,0,8.19-4.18,8.07-4.11v0L186.43,55l16.28-8.29,33.65-17.15V9.14Z"/><path d="M236.36,50,49.78,145,33.5,153.31,0,170.38v20.41l33.27-16.95,28.46-14.5L199.3,89.24A83.45,83.45,0,0,1,200,100,81.87,81.87,0,0,1,78.09,171.36l-1,.53-17.66,9A100,100,0,0,0,218.18,100c0-2.57-.1-5.14-.29-7.68a18.2,18.2,0,0,1,9.87-17.58l8.6-4.38Z"/></g></g></svg>

After

Width:  |  Height:  |  Size: 705 B

View file

@ -0,0 +1 @@
<svg id="Layer_1" data-name="Layer 1" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 339.43 295.27"><title>tether-usdt-logo</title><path d="M62.15,1.45l-61.89,130a2.52,2.52,0,0,0,.54,2.94L167.95,294.56a2.55,2.55,0,0,0,3.53,0L338.63,134.4a2.52,2.52,0,0,0,.54-2.94l-61.89-130A2.5,2.5,0,0,0,275,0H64.45a2.5,2.5,0,0,0-2.3,1.45h0Z" style="fill:#50af95;fill-rule:evenodd"/><path d="M191.19,144.8v0c-1.2.09-7.4,0.46-21.23,0.46-11,0-18.81-.33-21.55-0.46v0c-42.51-1.87-74.24-9.27-74.24-18.13s31.73-16.25,74.24-18.15v28.91c2.78,0.2,10.74.67,21.74,0.67,13.2,0,19.81-.55,21-0.66v-28.9c42.42,1.89,74.08,9.29,74.08,18.13s-31.65,16.24-74.08,18.12h0Zm0-39.25V79.68h59.2V40.23H89.21V79.68H148.4v25.86c-48.11,2.21-84.29,11.74-84.29,23.16s36.18,20.94,84.29,23.16v82.9h42.78V151.83c48-2.21,84.12-11.73,84.12-23.14s-36.09-20.93-84.12-23.15h0Zm0,0h0Z" style="fill:#fff;fill-rule:evenodd"/></svg>

After

Width:  |  Height:  |  Size: 874 B

View file

@ -0,0 +1 @@
<svg id="Calque_1" data-name="Calque 1" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 64 64"><defs><style>.cls-1{fill:#ff060a;}</style></defs><title>tron</title><g id="tron"><path class="cls-1" d="M61.55,19.28c-3-2.77-7.15-7-10.53-10l-.2-.14a3.82,3.82,0,0,0-1.11-.62l0,0C41.56,7,3.63-.09,2.89,0a1.4,1.4,0,0,0-.58.22L2.12.37a2.23,2.23,0,0,0-.52.84l-.05.13v.71l0,.11C5.82,14.05,22.68,53,26,62.14c.2.62.58,1.8,1.29,1.86h.16c.38,0,2-2.14,2-2.14S58.41,26.74,61.34,23a9.46,9.46,0,0,0,1-1.48A2.41,2.41,0,0,0,61.55,19.28ZM36.88,23.37,49.24,13.12l7.25,6.68Zm-4.8-.67L10.8,5.26l34.43,6.35ZM34,27.27l21.78-3.51-24.9,30ZM7.91,7,30.3,26,27.06,53.78Z"/></g></svg>

After

Width:  |  Height:  |  Size: 651 B

View file

@ -33,6 +33,7 @@ class DB {
static const String boxNameDBInfo = "dbInfo";
static const String boxNameTheme = "theme";
static const String boxNameDesktopData = "desktopData";
static const String boxNameBuys = "buysBox";
String boxNameTxCache({required Coin coin}) => "${coin.name}_txCache";
String boxNameSetCache({required Coin coin}) =>

View file

@ -41,6 +41,7 @@ import 'package:stackwallet/providers/global/trades_service_provider.dart';
import 'package:stackwallet/providers/providers.dart';
import 'package:stackwallet/providers/ui/color_theme_provider.dart';
import 'package:stackwallet/route_generator.dart';
// import 'package:stackwallet/services/buy/buy_data_loading_service.dart';
import 'package:stackwallet/services/debug_service.dart';
import 'package:stackwallet/services/exchange/change_now/change_now_exchange.dart';
import 'package:stackwallet/services/exchange/exchange_data_loading_service.dart';
@ -292,10 +293,14 @@ class _MaterialAppWithThemeState extends ConsumerState<MaterialAppWithTheme>
// unawaited(_nodeService.updateCommunityNodes());
// run without awaiting
if (Constants.enableExchange &&
ref.read(prefsChangeNotifierProvider).externalCalls &&
if (ref.read(prefsChangeNotifierProvider).externalCalls &&
await ref.read(prefsChangeNotifierProvider).isExternalCallsSet()) {
unawaited(ExchangeDataLoadingService().loadAll(ref));
if (Constants.enableExchange) {
unawaited(ExchangeDataLoadingService().loadAll(ref));
}
// if (Constants.enableBuy) {
// unawaited(BuyDataLoadingService().loadAll(ref));
// }
}
if (ref.read(prefsChangeNotifierProvider).isAutoBackupEnabled) {
@ -312,6 +317,11 @@ class _MaterialAppWithThemeState extends ConsumerState<MaterialAppWithTheme>
break;
}
}
// ref
// .read(prefsChangeNotifierProvider)
// .userID; // Just reading the ref should set it if it's not already set
// We shouldn't need to do this, instead only generating an ID when (or if) the userID is looked up when creating a quote
} catch (e, s) {
Logger.print("$e $s", normalLength: false);
}

View file

@ -0,0 +1,25 @@
import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart';
import 'package:stackwallet/services/buy/buy.dart';
class BuyFormState extends ChangeNotifier {
Buy? _buy;
Buy? get buy => _buy;
set buy(Buy? value) {
_buy = value;
// _onBuyTypeChanged();
}
bool reversed = false;
Future<void> updateEstimate({
required bool shouldNotifyListeners,
required bool reversed,
}) async {
// TODO implement updating estimaate based on changed selected crypto, fiat, etc
}
Future<void> swap({dynamic? market}) async {
// TODO implement swapping values on FiatOrCrypto toggle (or whatever it's called)
}
}

View file

@ -0,0 +1,61 @@
class Crypto {
/// Crypto ticker
final String ticker;
/// Crypto name
final String name;
/// Crypto network
final String? network;
/// Crypto contract address
final String? contractAddress;
Crypto({
required this.ticker,
required this.name,
required this.network,
required this.contractAddress,
});
factory Crypto.fromJson(Map<String, dynamic> json) {
try {
return Crypto(
ticker: "${json['ticker']}",
name: "${json['name']}",
network: "${json['network']}",
contractAddress: "${json['contractAddress']}",
);
} catch (e) {
rethrow;
}
}
Map<String, dynamic> toJson() {
final map = {
"ticker": ticker,
"name": name,
"network": network,
"contractAddress": contractAddress,
};
return map;
}
Crypto copyWith({
String? ticker,
String? name,
}) {
return Crypto(
ticker: ticker ?? this.ticker,
name: name ?? this.name,
network: network ?? this.network,
contractAddress: contractAddress ?? this.contractAddress,
);
}
@override
String toString() {
return "Crypto: ${toJson()}";
}
}

View file

@ -0,0 +1,64 @@
import 'package:decimal/decimal.dart';
class Fiat {
/// Fiat ticker
final String ticker;
/// Fiat name
final String name;
/// Fiat name
final Decimal min_amount;
/// Fiat name
final Decimal max_amount;
Fiat(
{required this.ticker,
required this.name,
required this.min_amount,
required this.max_amount});
factory Fiat.fromJson(Map<String, dynamic> json) {
try {
return Fiat(
ticker: "${json['ticker']}",
name: "${json['name']}", // TODO nameFromTicker
min_amount: Decimal.parse("${json['min_amount'] ?? 0}"),
max_amount: Decimal.parse("${json['max_amount'] ?? 0}"),
);
} catch (e) {
rethrow;
}
}
Map<String, dynamic> toJson() {
final map = {
"ticker": ticker,
"name": name,
"min_amount": min_amount,
"max_amount": max_amount,
};
return map;
}
Fiat copyWith({
String? ticker,
String? name,
Decimal? min_amount,
Decimal? max_amount,
}) {
return Fiat(
ticker: ticker ?? this.ticker,
name: name ?? this.name,
min_amount: min_amount ?? this.min_amount,
max_amount: max_amount ?? this.max_amount,
);
}
@override
String toString() {
return "Fiat: ${toJson()}";
}
}

View file

@ -0,0 +1,17 @@
import 'package:stackwallet/models/buy/response_objects/quote.dart';
class SimplexOrder {
final SimplexQuote quote;
late final String paymentId;
late final String orderId;
late final String userId;
// TODO remove after userIds are sourced from isar/storage
SimplexOrder({
required this.quote,
required this.paymentId,
required this.orderId,
required this.userId,
});
}

View file

@ -0,0 +1,73 @@
import 'dart:ui';
import 'package:stackwallet/utilities/logger.dart';
class Pair {
final String from;
final String fromNetwork;
final String to;
final String toNetwork;
final bool fixedRate;
final bool floatingRate;
Pair({
required this.from,
required this.fromNetwork,
required this.to,
required this.toNetwork,
required this.fixedRate,
required this.floatingRate,
});
factory Pair.fromMap(Map<String, dynamic> map) {
try {
return Pair(
from: map["from"] as String,
fromNetwork: map["fromNetwork"] as String,
to: map["to"] as String,
toNetwork: map["toNetwork"] as String,
fixedRate: map["fixedRate"] as bool,
floatingRate: map["floatingRate"] as bool,
);
} catch (e, s) {
Logging.instance.log("Pair.fromMap(): $e\n$s", level: LogLevel.Error);
rethrow;
}
}
Map<String, dynamic> toMap() {
return {
"from": from,
"fromNetwork": fromNetwork,
"to": to,
"toNetwork": toNetwork,
"fixedRate": fixedRate,
"floatingRate": floatingRate,
};
}
@override
bool operator ==(other) =>
other is Pair &&
from == other.from &&
fromNetwork == other.fromNetwork &&
to == other.to &&
toNetwork == other.toNetwork &&
fixedRate == other.fixedRate &&
floatingRate == other.floatingRate;
@override
int get hashCode => hashValues(
from,
fromNetwork,
to,
toNetwork,
fixedRate,
floatingRate,
);
@override
String toString() => "Pair: ${toMap()}";
}

View file

@ -0,0 +1,26 @@
import 'package:decimal/decimal.dart';
import 'package:stackwallet/models/buy/response_objects/crypto.dart';
import 'package:stackwallet/models/buy/response_objects/fiat.dart';
class SimplexQuote {
final Crypto crypto;
final Fiat fiat;
late final Decimal youPayFiatPrice;
late final Decimal youReceiveCryptoAmount;
late final String id;
late final String receivingAddress;
late final bool buyWithFiat;
SimplexQuote({
required this.crypto,
required this.fiat,
required this.youPayFiatPrice,
required this.youReceiveCryptoAmount,
required this.id,
required this.receivingAddress,
required this.buyWithFiat,
});
}

View file

@ -0,0 +1,32 @@
import 'package:decimal/decimal.dart';
import 'package:stackwallet/models/buy/response_objects/crypto.dart';
import 'package:stackwallet/models/buy/response_objects/fiat.dart';
import 'package:stackwallet/models/buy/response_objects/quote.dart';
// import 'package:stackwallet/models/buy/response_objects/pair.dart';
class Simplex {
List<Crypto> supportedCryptos = [];
List<Fiat> supportedFiats = [];
SimplexQuote quote = SimplexQuote(
crypto: Crypto.fromJson({'ticker': 'BTC', 'name': 'Bitcoin', 'image': ''}),
fiat: Fiat.fromJson(
{'ticker': 'USD', 'name': 'United States Dollar', 'image': ''}),
youPayFiatPrice: Decimal.parse("100"),
youReceiveCryptoAmount: Decimal.parse("1.0238917"),
id: "someID",
receivingAddress: '',
buyWithFiat: true,
);
void updateSupportedCryptos(List<Crypto> newCryptos) {
supportedCryptos = newCryptos;
}
void updateSupportedFiats(List<Fiat> newFiats) {
supportedFiats = newFiats;
}
void updateQuote(SimplexQuote newQuote) {
quote = newQuote;
}
}

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,267 @@
import 'package:flutter/material.dart';
import 'package:flutter_svg/svg.dart';
import 'package:stackwallet/models/buy/response_objects/order.dart';
import 'package:stackwallet/utilities/assets.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/background.dart';
import 'package:stackwallet/widgets/conditional_parent.dart';
import 'package:stackwallet/widgets/custom_buttons/app_bar_icon_button.dart';
import 'package:stackwallet/widgets/desktop/primary_button.dart';
import 'package:stackwallet/widgets/rounded_white_container.dart';
class BuyOrderDetailsView extends StatefulWidget {
const BuyOrderDetailsView({
Key? key,
required this.order,
}) : super(key: key);
final SimplexOrder order;
static const String routeName = "/buyOrderDetails";
@override
State<BuyOrderDetailsView> createState() => _BuyOrderDetailsViewState();
}
class _BuyOrderDetailsViewState extends State<BuyOrderDetailsView> {
final isDesktop = Util.isDesktop;
@override
Widget build(BuildContext context) {
return ConditionalParent(
condition: !isDesktop,
builder: (child) {
return Background(
child: Scaffold(
backgroundColor:
Theme.of(context).extension<StackColors>()!.background,
appBar: AppBar(
backgroundColor:
Theme.of(context).extension<StackColors>()!.backgroundAppBar,
leading: const AppBarBackButton(),
title: Text(
"Order details",
style: STextStyles.navBarTitle(context),
),
),
body: LayoutBuilder(
builder: (builderContext, constraints) {
return Padding(
padding: const EdgeInsets.only(
left: 12,
top: 12,
right: 12,
),
child: SingleChildScrollView(
child: ConstrainedBox(
constraints: BoxConstraints(
minHeight: constraints.maxHeight - 24,
),
child: IntrinsicHeight(
child: Padding(
padding: const EdgeInsets.all(4),
child: child,
),
),
),
),
);
},
),
),
);
},
child: Column(
crossAxisAlignment: CrossAxisAlignment.stretch,
children: [
Text(
"Simplex order",
style: STextStyles.pageTitleH1(context),
),
const SizedBox(
height: 16,
),
RoundedWhiteContainer(
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Text(
"Purchase ID",
style: STextStyles.label(context),
),
Text(
widget.order.paymentId,
style: STextStyles.label(context).copyWith(
color: Theme.of(context).extension<StackColors>()!.textDark,
),
),
],
),
),
const SizedBox(
height: 8,
),
RoundedWhiteContainer(
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Text(
"User ID",
style: STextStyles.label(context),
),
Text(
widget.order.userId,
style: STextStyles.label(context).copyWith(
color: Theme.of(context).extension<StackColors>()!.textDark,
),
),
],
),
),
const SizedBox(
height: 8,
),
RoundedWhiteContainer(
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Text(
"Quote ID",
style: STextStyles.label(context),
),
Text(
widget.order.quote.id,
style: STextStyles.label(context).copyWith(
color: Theme.of(context).extension<StackColors>()!.textDark,
),
),
],
),
),
const SizedBox(
height: 8,
),
RoundedWhiteContainer(
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Text(
"Quoted cost",
style: STextStyles.label(context),
),
Text(
"${widget.order.quote.youPayFiatPrice.toStringAsFixed(2)} ${widget.order.quote.fiat.ticker.toUpperCase()}",
style: STextStyles.label(context).copyWith(
color: Theme.of(context).extension<StackColors>()!.textDark,
),
),
],
),
),
const SizedBox(
height: 8,
),
// RoundedWhiteContainer(
// child: Row(
// mainAxisAlignment: MainAxisAlignment.spaceBetween,
// children: [
// Text(
// "You pay with",
// style: STextStyles.label(context),
// ),
// Text(
// widget.quote.fiat.name,
// style: STextStyles.label(context).copyWith(
// color: Theme.of(context).extension<StackColors>()!.textDark,
// ),
// ),
// ],
// ),
// ),
// const SizedBox(
// height: 8,
// ),
RoundedWhiteContainer(
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Text(
"Quoted amount",
style: STextStyles.label(context),
),
Text(
"${widget.order.quote.youReceiveCryptoAmount} ${widget.order.quote.crypto.ticker.toUpperCase()}",
style: STextStyles.label(context).copyWith(
color: Theme.of(context).extension<StackColors>()!.textDark,
),
),
],
),
),
const SizedBox(
height: 8,
),
RoundedWhiteContainer(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
"Receiving ${widget.order.quote.crypto.ticker.toUpperCase()} address",
style: STextStyles.label(context),
),
Text(
"${widget.order.quote.receivingAddress} ",
style: STextStyles.label(context).copyWith(
color: Theme.of(context).extension<StackColors>()!.textDark,
),
),
],
),
),
const SizedBox(
height: 8,
),
RoundedWhiteContainer(
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Text(
"Provider",
style: STextStyles.label(context),
),
SizedBox(
width: 64,
height: 32,
child: SvgPicture.asset(
Assets.buy.simplexLogo(context),
),
),
],
),
),
const SizedBox(
height: 24,
),
Row(mainAxisAlignment: MainAxisAlignment.center, children: [
Text(
"This information is not saved,\nscreenshot it now for your records",
style: STextStyles.label(context).copyWith(
color: Theme.of(context).extension<StackColors>()!.textDark,
),
textAlign: TextAlign.center,
),
]),
const Spacer(),
PrimaryButton(
label: "Dismiss",
onPressed: () {
Navigator.of(context, rootNavigator: isDesktop).pop();
},
)
],
),
);
}
}

View file

@ -0,0 +1,233 @@
import 'dart:async';
import 'package:flutter/material.dart';
import 'package:flutter_svg/svg.dart';
import 'package:intl/intl.dart';
import 'package:stackwallet/models/buy/response_objects/quote.dart';
import 'package:stackwallet/pages/buy_view/sub_widgets/buy_warning_popup.dart';
import 'package:stackwallet/utilities/assets.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/background.dart';
import 'package:stackwallet/widgets/conditional_parent.dart';
import 'package:stackwallet/widgets/custom_buttons/app_bar_icon_button.dart';
import 'package:stackwallet/widgets/desktop/primary_button.dart';
import 'package:stackwallet/widgets/rounded_white_container.dart';
class BuyQuotePreviewView extends StatefulWidget {
const BuyQuotePreviewView({
Key? key,
required this.quote,
}) : super(key: key);
final SimplexQuote quote;
static const String routeName = "/buyQuotePreview";
@override
State<BuyQuotePreviewView> createState() => _BuyQuotePreviewViewState();
}
class _BuyQuotePreviewViewState extends State<BuyQuotePreviewView> {
final isDesktop = Util.isDesktop;
Future<void> _buyWarning() async {
await showDialog<void>(
context: context,
builder: (context) => BuyWarningPopup(
quote: widget.quote,
),
);
}
@override
Widget build(BuildContext context) {
Locale locale = Localizations.localeOf(context);
var format = NumberFormat.simpleCurrency(locale: locale.toString());
// See https://stackoverflow.com/a/67055685
return ConditionalParent(
condition: !isDesktop,
builder: (child) {
return Background(
child: Scaffold(
backgroundColor:
Theme.of(context).extension<StackColors>()!.background,
appBar: AppBar(
backgroundColor:
Theme.of(context).extension<StackColors>()!.backgroundAppBar,
leading: const AppBarBackButton(),
title: Text(
"Preview quote",
style: STextStyles.navBarTitle(context),
),
),
body: LayoutBuilder(
builder: (builderContext, constraints) {
return Padding(
padding: const EdgeInsets.only(
left: 12,
top: 12,
right: 12,
),
child: SingleChildScrollView(
child: ConstrainedBox(
constraints: BoxConstraints(
minHeight: constraints.maxHeight - 24,
),
child: IntrinsicHeight(
child: Padding(
padding: const EdgeInsets.all(4),
child: child,
),
),
),
),
);
},
),
),
);
},
child: Column(
crossAxisAlignment: CrossAxisAlignment.stretch,
children: [
Text(
"Buy ${widget.quote.crypto.ticker.toUpperCase()}",
style: STextStyles.pageTitleH1(context),
),
const SizedBox(
height: 16,
),
RoundedWhiteContainer(
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Text(
"You pay",
style: STextStyles.label(context),
),
Text(
"${format.simpleCurrencySymbol(widget.quote.fiat.ticker.toUpperCase())}${widget.quote.youPayFiatPrice.toStringAsFixed(2)} ${widget.quote.fiat.ticker.toUpperCase()}",
style: STextStyles.label(context).copyWith(
color: Theme.of(context).extension<StackColors>()!.textDark,
),
),
],
),
),
const SizedBox(
height: 8,
),
// RoundedWhiteContainer(
// child: Row(
// mainAxisAlignment: MainAxisAlignment.spaceBetween,
// children: [
// Text(
// "You pay with",
// style: STextStyles.label(context),
// ),
// Text(
// widget.quote.fiat.name,
// style: STextStyles.label(context).copyWith(
// color: Theme.of(context).extension<StackColors>()!.textDark,
// ),
// ),
// ],
// ),
// ),
// const SizedBox(
// height: 8,
// ),
RoundedWhiteContainer(
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Text(
"You receive",
style: STextStyles.label(context),
),
Text(
"${widget.quote.youReceiveCryptoAmount} ${widget.quote.crypto.ticker.toUpperCase()}",
style: STextStyles.label(context).copyWith(
color: Theme.of(context).extension<StackColors>()!.textDark,
),
),
],
),
),
const SizedBox(
height: 8,
),
RoundedWhiteContainer(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
"Receiving ${widget.quote.crypto.ticker.toUpperCase()} address",
style: STextStyles.label(context),
),
Text(
"${widget.quote.receivingAddress} ",
style: STextStyles.label(context).copyWith(
color: Theme.of(context).extension<StackColors>()!.textDark,
),
),
],
),
),
const SizedBox(
height: 8,
),
RoundedWhiteContainer(
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Text(
"Quote ID",
style: STextStyles.label(context),
),
Text(
widget.quote.id,
style: STextStyles.label(context).copyWith(
color: Theme.of(context).extension<StackColors>()!.textDark,
),
),
],
),
),
const SizedBox(
height: 8,
),
RoundedWhiteContainer(
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Text(
"Provider",
style: STextStyles.label(context),
),
SizedBox(
width: 64,
height: 32,
child: SvgPicture.asset(
Assets.buy.simplexLogo(context),
),
),
],
),
),
const SizedBox(
height: 8,
),
const Spacer(),
PrimaryButton(
label: "Buy",
onPressed: _buyWarning,
)
],
),
);
}
}

View file

@ -1,9 +1,11 @@
import 'package:flutter/material.dart';
import 'package:stackwallet/utilities/text_styles.dart';
import 'package:stackwallet/pages/buy_view/buy_form.dart';
class BuyView extends StatefulWidget {
const BuyView({Key? key}) : super(key: key);
static const String routeName = "/stackBuyView";
@override
State<BuyView> createState() => _BuyViewState();
}
@ -11,39 +13,16 @@ class BuyView extends StatefulWidget {
class _BuyViewState extends State<BuyView> {
@override
Widget build(BuildContext context) {
//todo: check if print needed
// debugPrint("BUILD: BuyView");
return SafeArea(
child: Center(
child: SingleChildScrollView(
child: Column(
children: [
Center(
child: Text(
"Coming soon",
style: STextStyles.pageTitleH1(context),
),
),
],
),
debugPrint("BUILD: $runtimeType");
return const SafeArea(
child: Padding(
padding: EdgeInsets.only(
left: 16,
right: 16,
top: 16,
),
// child: Column(
// children: [
// Container(
// color: Colors.green,
// child: Text("BuyView"),
// ),
// Container(
// color: Colors.green,
// child: Text("BuyView"),
// ),
// Spacer(),
// Container(
// color: Colors.green,
// child: Text("BuyView"),
// ),
// ],
// ),
child: BuyForm(),
),
);
}

View file

@ -0,0 +1,132 @@
import 'dart:async';
import 'package:flutter/material.dart';
import 'package:flutter_svg/svg.dart';
import 'package:stackwallet/models/buy/response_objects/order.dart';
import 'package:stackwallet/models/buy/response_objects/quote.dart';
import 'package:stackwallet/pages/buy_view/buy_order_details.dart';
import 'package:stackwallet/services/buy/buy_response.dart';
import 'package:stackwallet/services/buy/simplex/simplex_api.dart';
import 'package:stackwallet/utilities/assets.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/desktop/desktop_dialog.dart';
import 'package:stackwallet/widgets/desktop/desktop_dialog_close_button.dart';
import 'package:stackwallet/widgets/desktop/primary_button.dart';
import 'package:stackwallet/widgets/desktop/secondary_button.dart';
import 'package:stackwallet/widgets/rounded_white_container.dart';
import 'package:stackwallet/widgets/stack_dialog.dart';
class BuyWarningPopup extends StatelessWidget {
BuyWarningPopup({
Key? key,
required this.quote,
this.order,
}) : super(key: key);
final SimplexQuote quote;
SimplexOrder? order;
Future<BuyResponse<SimplexOrder>> newOrder(SimplexQuote quote) async {
return SimplexAPI.instance.newOrder(quote);
}
Future<BuyResponse<bool>> redirect(SimplexOrder order) async {
return SimplexAPI.instance.redirect(order);
}
@override
Widget build(BuildContext context) {
final isDesktop = Util.isDesktop;
Future<void> _buyInvoice() async {
await showDialog<void>(
context: context,
// useRootNavigator: isDesktop,
builder: (context) {
return isDesktop
? DesktopDialog(
maxHeight: 700,
maxWidth: 580,
child: Column(
children: [
Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Padding(
padding: const EdgeInsets.only(
left: 32,
),
child: Text(
"Order details",
style: STextStyles.desktopH3(context),
),
),
const DesktopDialogCloseButton(),
],
),
Expanded(
child: Padding(
padding: const EdgeInsets.only(
left: 32,
right: 32,
bottom: 32,
),
child: Row(
children: [
Expanded(
child: RoundedWhiteContainer(
padding: const EdgeInsets.all(16),
borderColor: Theme.of(context)
.extension<StackColors>()!
.background,
child: BuyOrderDetailsView(
order: order as SimplexOrder,
),
),
),
],
),
),
),
],
),
)
: BuyOrderDetailsView(
order: order as SimplexOrder,
);
});
}
return StackDialog(
title: "Buy ${quote.crypto.ticker}",
message: "This purchase is provided and fulfilled by Simplex by nuvei "
"(a third party). You will be taken to their website. Please follow "
"their instructions.",
leftButton: SecondaryButton(
label: "Cancel",
onPressed: Navigator.of(context, rootNavigator: isDesktop).pop,
),
rightButton: PrimaryButton(
label: "Continue",
onPressed: () async {
BuyResponse<SimplexOrder> order = await newOrder(quote);
await redirect(order.value as SimplexOrder).then((_response) async {
this.order = order.value as SimplexOrder;
Navigator.of(context, rootNavigator: isDesktop).pop();
Navigator.of(context, rootNavigator: isDesktop).pop();
await _buyInvoice();
});
},
),
icon: SizedBox(
width: 64,
height: 32,
child: SvgPicture.asset(
Assets.buy.simplexLogo(context),
),
),
);
}
}

View file

@ -0,0 +1,262 @@
import 'package:flutter/material.dart';
import 'package:flutter_svg/svg.dart';
import 'package:stackwallet/models/buy/response_objects/crypto.dart';
import 'package:stackwallet/utilities/assets.dart';
import 'package:stackwallet/utilities/constants.dart';
import 'package:stackwallet/utilities/enums/coin_enum.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/background.dart';
import 'package:stackwallet/widgets/conditional_parent.dart';
import 'package:stackwallet/widgets/custom_buttons/app_bar_icon_button.dart';
import 'package:stackwallet/widgets/icon_widgets/x_icon.dart';
import 'package:stackwallet/widgets/rounded_white_container.dart';
import 'package:stackwallet/widgets/stack_text_field.dart';
import 'package:stackwallet/widgets/textfield_icon_button.dart';
class CryptoSelectionView extends StatefulWidget {
const CryptoSelectionView({
Key? key,
required this.coins,
}) : super(key: key);
final List<Crypto> coins;
@override
State<CryptoSelectionView> createState() => _CryptoSelectionViewState();
}
class _CryptoSelectionViewState extends State<CryptoSelectionView> {
late TextEditingController _searchController;
final _searchFocusNode = FocusNode();
late final List<Crypto> coins;
late List<Crypto> _coins;
void filter(String text) {
setState(() {
_coins = [
...coins.where((e) =>
e.name.toLowerCase().contains(text.toLowerCase()) ||
e.ticker.toLowerCase().contains(text.toLowerCase()))
];
});
}
@override
void initState() {
_searchController = TextEditingController();
coins = [...widget.coins];
coins.sort(
(a, b) => a.ticker.toLowerCase().compareTo(b.ticker.toLowerCase()));
for (Coin coin in Coin.values.reversed) {
int index = coins.indexWhere((element) =>
element.ticker.toLowerCase() == coin.ticker.toLowerCase());
if (index > 0) {
final currency = coins.removeAt(index);
coins.insert(0, currency);
}
}
_coins = [...coins];
super.initState();
}
@override
void dispose() {
_searchController.dispose();
_searchFocusNode.dispose();
super.dispose();
}
@override
Widget build(BuildContext context) {
final isDesktop = Util.isDesktop;
return ConditionalParent(
condition: !isDesktop,
builder: (child) {
return Background(
child: Scaffold(
backgroundColor:
Theme.of(context).extension<StackColors>()!.background,
appBar: AppBar(
leading: AppBarBackButton(
onPressed: () async {
if (FocusScope.of(context).hasFocus) {
FocusScope.of(context).unfocus();
await Future<void>.delayed(
const Duration(milliseconds: 50));
}
if (mounted) {
Navigator.of(context).pop();
}
},
),
title: Text(
"Choose a coin to buy",
style: STextStyles.pageTitleH2(context),
),
),
body: Padding(
padding: const EdgeInsets.symmetric(
horizontal: 16,
),
child: child,
),
),
);
},
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
mainAxisSize: isDesktop ? MainAxisSize.min : MainAxisSize.max,
children: [
if (!isDesktop)
const SizedBox(
height: 16,
),
ClipRRect(
borderRadius: BorderRadius.circular(
Constants.size.circularBorderRadius,
),
child: TextField(
autofocus: isDesktop,
autocorrect: !isDesktop,
enableSuggestions: !isDesktop,
controller: _searchController,
focusNode: _searchFocusNode,
onChanged: filter,
style: STextStyles.field(context),
decoration: standardInputDecoration(
"Search",
_searchFocusNode,
context,
desktopMed: isDesktop,
).copyWith(
prefixIcon: Padding(
padding: const EdgeInsets.symmetric(
horizontal: 10,
vertical: 16,
),
child: SvgPicture.asset(
Assets.svg.search,
width: 16,
height: 16,
),
),
suffixIcon: _searchController.text.isNotEmpty
? Padding(
padding: const EdgeInsets.only(right: 0),
child: UnconstrainedBox(
child: Row(
children: [
TextFieldIconButton(
child: const XIcon(),
onTap: () async {
setState(() {
_searchController.text = "";
});
filter("");
},
),
],
),
),
)
: null,
),
),
),
const SizedBox(
height: 10,
),
Text(
"All coins",
style: STextStyles.smallMed12(context),
),
const SizedBox(
height: 12,
),
Flexible(
child: RoundedWhiteContainer(
padding: const EdgeInsets.all(0),
child: ListView.builder(
shrinkWrap: true,
primary: isDesktop ? false : null,
itemCount: _coins.length,
itemBuilder: (builderContext, index) {
return Padding(
padding: const EdgeInsets.symmetric(vertical: 4),
child: GestureDetector(
onTap: () {
Navigator.of(context).pop(_coins[index]);
},
child: RoundedWhiteContainer(
child: Row(
children: [
SizedBox(
width: 24,
height: 24,
child: getIconForTicker(_coins[index].ticker)),
const SizedBox(
width: 10,
),
Expanded(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
_coins[index].name,
style: STextStyles.largeMedium14(context),
),
const SizedBox(
height: 2,
),
Text(
_coins[index].ticker.toUpperCase(),
style: STextStyles.smallMed12(context)
.copyWith(
color: Theme.of(context)
.extension<StackColors>()!
.textSubtitle1,
),
),
],
),
),
],
),
),
),
);
},
),
),
),
],
),
);
}
}
bool isStackCoin(String? ticker) {
if (ticker == null) return false;
try {
coinFromTickerCaseInsensitive(ticker);
return true;
} on ArgumentError catch (_) {
return false;
}
}
Widget? getIconForTicker(String ticker) {
String? iconAsset = isStackCoin(ticker)
? Assets.svg.iconFor(coin: coinFromTickerCaseInsensitive(ticker))
: Assets.svg.buyIconFor(ticker);
return (iconAsset != null)
? SvgPicture.asset(iconAsset, height: 20, width: 20)
: null;
}

View file

@ -0,0 +1,270 @@
import 'package:flutter/material.dart';
import 'package:flutter_svg/svg.dart';
import 'package:intl/intl.dart';
import 'package:stackwallet/models/buy/response_objects/fiat.dart';
import 'package:stackwallet/utilities/assets.dart';
import 'package:stackwallet/utilities/constants.dart';
import 'package:stackwallet/utilities/enums/fiat_enum.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/background.dart';
import 'package:stackwallet/widgets/conditional_parent.dart';
import 'package:stackwallet/widgets/custom_buttons/app_bar_icon_button.dart';
import 'package:stackwallet/widgets/icon_widgets/x_icon.dart';
import 'package:stackwallet/widgets/rounded_white_container.dart';
import 'package:stackwallet/widgets/stack_text_field.dart';
import 'package:stackwallet/widgets/textfield_icon_button.dart';
class FiatSelectionView extends StatefulWidget {
const FiatSelectionView({
Key? key,
required this.fiats,
}) : super(key: key);
final List<Fiat> fiats;
@override
State<FiatSelectionView> createState() => _FiatSelectionViewState();
}
class _FiatSelectionViewState extends State<FiatSelectionView> {
late TextEditingController _searchController;
final _searchFocusNode = FocusNode();
late final List<Fiat> fiats;
late List<Fiat> _fiats;
void filter(String text) {
setState(() {
_fiats = [
...fiats.where((e) =>
e.name.toLowerCase().contains(text.toLowerCase()) ||
e.ticker.toLowerCase().contains(text.toLowerCase()))
];
});
}
@override
void initState() {
_searchController = TextEditingController();
fiats = [...widget.fiats];
fiats.sort(
(a, b) => a.ticker.toLowerCase().compareTo(b.ticker.toLowerCase()));
for (Fiats fiat in Fiats.values.reversed) {
int index = fiats.indexWhere((element) =>
element.ticker.toLowerCase() == fiat.ticker.toLowerCase());
if (index > 0) {
final currency = fiats.removeAt(index);
fiats.insert(0, currency);
}
}
_fiats = [...fiats];
super.initState();
}
@override
void dispose() {
_searchController.dispose();
_searchFocusNode.dispose();
super.dispose();
}
@override
Widget build(BuildContext context) {
Locale locale = Localizations.localeOf(context);
var format = NumberFormat.simpleCurrency(locale: locale.toString());
// See https://stackoverflow.com/a/67055685
final isDesktop = Util.isDesktop;
return ConditionalParent(
condition: !isDesktop,
builder: (child) {
return Background(
child: Scaffold(
backgroundColor:
Theme.of(context).extension<StackColors>()!.background,
appBar: AppBar(
leading: AppBarBackButton(
onPressed: () async {
if (FocusScope.of(context).hasFocus) {
FocusScope.of(context).unfocus();
await Future<void>.delayed(
const Duration(milliseconds: 50));
}
if (mounted) {
Navigator.of(context).pop();
}
},
),
title: Text(
"Choose a currency with which to pay",
style: STextStyles.pageTitleH2(context),
),
),
body: Padding(
padding: const EdgeInsets.symmetric(
horizontal: 16,
),
child: child,
),
),
);
},
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
mainAxisSize: isDesktop ? MainAxisSize.min : MainAxisSize.max,
children: [
if (!isDesktop)
const SizedBox(
height: 16,
),
ClipRRect(
borderRadius: BorderRadius.circular(
Constants.size.circularBorderRadius,
),
child: TextField(
autofocus: isDesktop,
autocorrect: !isDesktop,
enableSuggestions: !isDesktop,
controller: _searchController,
focusNode: _searchFocusNode,
onChanged: filter,
style: STextStyles.field(context),
decoration: standardInputDecoration(
"Search",
_searchFocusNode,
context,
desktopMed: isDesktop,
).copyWith(
prefixIcon: Padding(
padding: const EdgeInsets.symmetric(
horizontal: 10,
vertical: 16,
),
child: SvgPicture.asset(
Assets.svg.search,
width: 16,
height: 16,
),
),
suffixIcon: _searchController.text.isNotEmpty
? Padding(
padding: const EdgeInsets.only(right: 0),
child: UnconstrainedBox(
child: Row(
children: [
TextFieldIconButton(
child: const XIcon(),
onTap: () async {
setState(() {
_searchController.text = "";
});
filter("");
},
),
],
),
),
)
: null,
),
),
),
const SizedBox(
height: 10,
),
Text(
"All currencies",
style: STextStyles.smallMed12(context),
),
const SizedBox(
height: 12,
),
Flexible(
child: RoundedWhiteContainer(
padding: const EdgeInsets.all(0),
child: ListView.builder(
shrinkWrap: true,
primary: isDesktop ? false : null,
itemCount: _fiats.length,
itemBuilder: (builderContext, index) {
return Padding(
padding: const EdgeInsets.symmetric(vertical: 4),
child: GestureDetector(
onTap: () {
Navigator.of(context).pop(_fiats[index]);
},
child: RoundedWhiteContainer(
child: Row(
children: [
Container(
padding: const EdgeInsets.all(7.5),
decoration: BoxDecoration(
color: Theme.of(context)
.extension<StackColors>()!
.highlight,
borderRadius: BorderRadius.circular(4),
),
child: Text(
format.simpleCurrencySymbol(
_fiats[index].ticker.toUpperCase()),
style: STextStyles.subtitle(context).apply(
fontSizeFactor: (1 /
format
.simpleCurrencySymbol(_fiats[index]
.ticker
.toUpperCase())
.length * // Couldn't get pow() working here
format
.simpleCurrencySymbol(_fiats[index]
.ticker
.toUpperCase())
.length)),
textAlign: TextAlign.center,
),
),
const SizedBox(
width: 10,
),
Expanded(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
_fiats[index].name,
style: STextStyles.largeMedium14(context),
),
const SizedBox(
height: 2,
),
Text(
_fiats[index].ticker.toUpperCase(),
style: STextStyles.smallMed12(context)
.copyWith(
color: Theme.of(context)
.extension<StackColors>()!
.textSubtitle1,
),
),
],
),
),
],
),
),
),
);
},
),
),
),
],
),
);
}
}

View file

@ -61,6 +61,8 @@ class _ConfirmChangeNowSendViewState
late final String routeOnSuccessName;
late final Trade trade;
final isDesktop = Util.isDesktop;
Future<void> _attemptSend(BuildContext context) async {
unawaited(
showDialog<void>(
@ -227,8 +229,6 @@ class _ConfirmChangeNowSendViewState
final managerProvider = ref.watch(walletsChangeNotifierProvider
.select((value) => value.getManagerProvider(walletId)));
final isDesktop = Util.isDesktop;
return ConditionalParent(
condition: !isDesktop,
builder: (child) {
@ -238,7 +238,7 @@ class _ConfirmChangeNowSendViewState
Theme.of(context).extension<StackColors>()!.background,
appBar: AppBar(
backgroundColor:
Theme.of(context).extension<StackColors>()!.background,
Theme.of(context).extension<StackColors>()!.backgroundAppBar,
leading: AppBarBackButton(
onPressed: () async {
// if (FocusScope.of(context).hasFocus) {

View file

@ -3,6 +3,7 @@ import 'dart:async';
import 'package:flutter/material.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:flutter_svg/svg.dart';
import 'package:stackwallet/pages/buy_view/buy_view.dart';
import 'package:stackwallet/pages/exchange_view/exchange_loading_overlay.dart';
import 'package:stackwallet/pages/exchange_view/exchange_view.dart';
import 'package:stackwallet/pages/home_view/sub_widgets/home_view_button_bar.dart';
@ -45,6 +46,7 @@ class _HomeViewState extends ConsumerState<HomeView> {
bool _exitEnabled = false;
final _exchangeDataLoadingService = ExchangeDataLoadingService();
// final _buyDataLoadingService = BuyDataLoadingService();
Future<bool> _onWillPop() async {
// go to home view when tapping back on the main exchange view
@ -92,6 +94,26 @@ class _HomeViewState extends ConsumerState<HomeView> {
}
}
// void _loadSimplexData() {
// // unawaited future
// if (ref.read(prefsChangeNotifierProvider).externalCalls) {
// _buyDataLoadingService.loadAll(ref);
// } else {
// Logging.instance.log("User does not want to use external calls",
// level: LogLevel.Info);
// }
// }
bool _lock = false;
Future<void> _animateToPage(int index) async {
await _pageController.animateToPage(
index,
duration: const Duration(milliseconds: 300),
curve: Curves.decelerate,
);
}
@override
void initState() {
_pageController = PageController();
@ -106,7 +128,14 @@ class _HomeViewState extends ConsumerState<HomeView> {
),
],
),
// const BuyView(),
if (Constants.enableBuy)
// Stack(
// children: [
const BuyView(),
// BuyLoadingOverlayView(
// unawaitedLoad: _loadSimplexData,
// ),
// ],
];
ref.read(notificationsProvider).startCheckingWatchedNotifications();
@ -301,35 +330,31 @@ class _HomeViewState extends ConsumerState<HomeView> {
builder: (_, _ref, __) {
_ref.listen(homeViewPageIndexStateProvider,
(previous, next) {
if (next is int) {
if (next is int && next >= 0 && next <= 2) {
if (next == 1) {
_exchangeDataLoadingService.loadAll(ref);
}
if (next >= 0 && next <= 1) {
_pageController.animateToPage(
next,
duration: const Duration(milliseconds: 300),
curve: Curves.decelerate,
);
}
// if (next == 2) {
// _buyDataLoadingService.loadAll(ref);
// }
_lock = true;
_animateToPage(next).then((value) => _lock = false);
}
});
return PageView(
controller: _pageController,
children: _children,
onPageChanged: (pageIndex) {
ref.read(homeViewPageIndexStateProvider.state).state =
pageIndex;
if (!_lock) {
ref.read(homeViewPageIndexStateProvider.state).state =
pageIndex;
}
},
);
},
),
),
// Expanded(
// child: HomeStack(
// children: _children,
// ),
// ),
],
),
),

View file

@ -127,37 +127,46 @@ class _HomeViewButtonBarState extends ConsumerState<HomeViewButtonBar> {
),
),
),
// TODO: Do not delete this code.
// only temporarily disabled
// SizedBox(
// width: 8,
// ),
// Expanded(
// child: TextButton(
// style: ButtonStyle(
// minimumSize: MaterialStateProperty.all<Size>(Size(46, 36)),
// backgroundColor: MaterialStateProperty.all<Color>(
// selectedIndex == 2
// ? CFColors.stackAccent
// : CFColors.disabledButton,
// ),
// ),
// onPressed: () {
// FocusScope.of(context).unfocus();
// if (selectedIndex != 2) {
// ref.read(homeViewPageIndexStateProvider.state).state = 2;
// }
// },
// child: Text(
// "Buy",
// style: STextStyles.button(context).copyWith(
// fontSize: 14,
// color:
// selectedIndex == 2 ? CFColors.light1 : Theme.of(context).extension<StackColors>()!.accentColorDark
// ),
// ),
// ),
// ),
const SizedBox(
width: 8,
),
Expanded(
child: TextButton(
style: selectedIndex == 2
? Theme.of(context)
.extension<StackColors>()!
.getPrimaryEnabledButtonStyle(context)!
.copyWith(
minimumSize:
MaterialStateProperty.all<Size>(const Size(46, 36)),
)
: Theme.of(context)
.extension<StackColors>()!
.getSecondaryEnabledButtonStyle(context)!
.copyWith(
minimumSize:
MaterialStateProperty.all<Size>(const Size(46, 36)),
),
onPressed: () async {
FocusScope.of(context).unfocus();
if (selectedIndex != 2) {
ref.read(homeViewPageIndexStateProvider.state).state = 2;
}
// await BuyDataLoadingService().loadAll(ref);
},
child: Text(
"Buy",
style: STextStyles.button(context).copyWith(
fontSize: 14,
color: selectedIndex == 2
? Theme.of(context)
.extension<StackColors>()!
.buttonTextPrimary
: Theme.of(context).extension<StackColors>()!.textDark,
),
),
),
),
],
);
}

View file

@ -232,6 +232,8 @@ class _StackPrivacyCalls extends ConsumerState<StackPrivacyCalls> {
if (isEasy) {
unawaited(ExchangeDataLoadingService()
.loadAll(ref));
// unawaited(
// BuyDataLoadingService().loadAll(ref));
ref
.read(priceAnd24hChangeNotifierProvider)
.start(true);

View file

@ -412,39 +412,47 @@ class _WalletNavigationBarState extends State<WalletNavigationBar> {
const SizedBox(
width: 12,
),
// TODO: Do not delete this code.
// only temporarily disabled
// Spacer(
// flex: 2,
// ),
// GestureDetector(
// onTap: onBuyPressed,
// child: Container(
// color: Colors.transparent,
// child: Padding(
// padding: const EdgeInsets.symmetric(vertical: 2.0),
// child: Column(
// crossAxisAlignment: CrossAxisAlignment.center,
// children: [
// Spacer(),
// SvgPicture.asset(
// Assets.svg.buy,
// width: 24,
// height: 24,
// ),
// SizedBox(
// height: 4,
// ),
// Text(
// "Buy",
// style: STextStyles.buttonSmall(context),
// ),
// Spacer(),
// ],
// ),
// ),
// ),
// ),
RawMaterialButton(
constraints: const BoxConstraints(
minWidth: 66,
),
onPressed: widget.onBuyPressed,
splashColor:
Theme.of(context).extension<StackColors>()!.highlight,
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(
widget.height / 2.0,
),
),
child: Container(
color: Colors.transparent,
child: Padding(
padding: const EdgeInsets.symmetric(vertical: 2.0),
child: Column(
crossAxisAlignment: CrossAxisAlignment.center,
children: [
const Spacer(),
SvgPicture.asset(
Assets.svg.buyDesktop,
width: 24,
height: 24,
),
const SizedBox(
height: 4,
),
Text(
"Buy",
style: STextStyles.buttonSmall(context),
),
const Spacer(),
],
),
),
),
),
const SizedBox(
width: 12,
),
],
),
),

View file

@ -0,0 +1,78 @@
import 'package:flutter/material.dart';
import 'package:stackwallet/pages/buy_view/buy_form.dart';
import 'package:stackwallet/utilities/text_styles.dart';
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';
class DesktopBuyView extends StatefulWidget {
const DesktopBuyView({Key? key}) : super(key: key);
static const String routeName = "/desktopBuyView";
@override
State<DesktopBuyView> createState() => _DesktopBuyViewState();
}
class _DesktopBuyViewState extends State<DesktopBuyView> {
@override
Widget build(BuildContext context) {
return DesktopScaffold(
appBar: DesktopAppBar(
isCompactHeight: true,
leading: Padding(
padding: const EdgeInsets.only(
left: 24,
),
child: Text(
"Buy crypto",
style: STextStyles.desktopH3(context),
),
),
),
body: Padding(
padding: const EdgeInsets.only(
left: 24,
right: 24,
bottom: 24,
),
child: Row(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Expanded(
child: Column(
mainAxisSize: MainAxisSize.min,
crossAxisAlignment: CrossAxisAlignment.start,
children: [
// Text(
// "Coming soon",
// style: STextStyles.desktopTextExtraExtraSmall(context),
// ),
const SizedBox(
height: 16,
),
const RoundedWhiteContainer(
padding: EdgeInsets.all(24),
child: BuyForm(),
),
],
),
),
const SizedBox(
width: 16,
),
// Expanded(
// child: Row(
// children: const [
// Expanded(
// child: DesktopTradeHistory(),
// ),
// ],
// ),
// ),
],
),
),
);
}
}

View file

@ -1,6 +1,7 @@
import 'package:flutter/material.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:stackwallet/pages_desktop_specific/address_book_view/desktop_address_book.dart';
import 'package:stackwallet/pages_desktop_specific/desktop_buy/desktop_buy_view.dart';
import 'package:stackwallet/pages_desktop_specific/desktop_exchange/desktop_exchange_view.dart';
import 'package:stackwallet/pages_desktop_specific/desktop_menu.dart';
import 'package:stackwallet/pages_desktop_specific/my_stack_view/my_stack_view.dart';
@ -56,6 +57,11 @@ class _DesktopHomeViewState extends ConsumerState<DesktopHomeView> {
onGenerateRoute: RouteGenerator.generateRoute,
initialRoute: DesktopExchangeView.routeName,
),
DesktopMenuItemId.buy: const Navigator(
key: Key("desktopBuyHomeKey"),
onGenerateRoute: RouteGenerator.generateRoute,
initialRoute: DesktopBuyView.routeName,
),
DesktopMenuItemId.notifications: const Navigator(
key: Key("desktopNotificationsHomeKey"),
onGenerateRoute: RouteGenerator.generateRoute,

View file

@ -12,6 +12,7 @@ import 'package:stackwallet/widgets/desktop/living_stack_icon.dart';
enum DesktopMenuItemId {
myStack,
exchange,
buy,
notifications,
addressBook,
settings,
@ -42,6 +43,8 @@ class _DesktopMenuState extends ConsumerState<DesktopMenu> {
double _width = expandedWidth;
// final _buyDataLoadingService = BuyDataLoadingService();
void updateSelectedMenuItem(DesktopMenuItemId idKey) {
widget.onSelectionWillChange?.call(idKey);
@ -73,6 +76,7 @@ class _DesktopMenuState extends ConsumerState<DesktopMenu> {
DMIController(),
DMIController(),
DMIController(),
DMIController(),
];
super.initState();
@ -157,13 +161,24 @@ class _DesktopMenuState extends ConsumerState<DesktopMenu> {
const SizedBox(
height: 2,
),
DesktopMenuItem(
duration: duration,
icon: const DesktopBuyIcon(),
label: "Buy crypto",
value: DesktopMenuItemId.buy,
onChanged: updateSelectedMenuItem,
controller: controllers[2],
),
const SizedBox(
height: 2,
),
DesktopMenuItem(
duration: duration,
icon: const DesktopNotificationsIcon(),
label: "Notifications",
value: DesktopMenuItemId.notifications,
onChanged: updateSelectedMenuItem,
controller: controllers[2],
controller: controllers[3],
),
const SizedBox(
height: 2,
@ -174,7 +189,7 @@ class _DesktopMenuState extends ConsumerState<DesktopMenu> {
label: "Address Book",
value: DesktopMenuItemId.addressBook,
onChanged: updateSelectedMenuItem,
controller: controllers[3],
controller: controllers[4],
),
const SizedBox(
height: 2,
@ -185,7 +200,7 @@ class _DesktopMenuState extends ConsumerState<DesktopMenu> {
label: "Settings",
value: DesktopMenuItemId.settings,
onChanged: updateSelectedMenuItem,
controller: controllers[4],
controller: controllers[5],
),
const SizedBox(
height: 2,
@ -196,7 +211,7 @@ class _DesktopMenuState extends ConsumerState<DesktopMenu> {
label: "Support",
value: DesktopMenuItemId.support,
onChanged: updateSelectedMenuItem,
controller: controllers[5],
controller: controllers[6],
),
const SizedBox(
height: 2,
@ -207,7 +222,7 @@ class _DesktopMenuState extends ConsumerState<DesktopMenu> {
label: "About",
value: DesktopMenuItemId.about,
onChanged: updateSelectedMenuItem,
controller: controllers[6],
controller: controllers[7],
),
const Spacer(),
DesktopMenuItem(
@ -221,7 +236,7 @@ class _DesktopMenuState extends ConsumerState<DesktopMenu> {
// exit(0);
SystemNavigator.pop();
},
controller: controllers[7],
controller: controllers[8],
),
],
),

View file

@ -55,6 +55,26 @@ class DesktopExchangeIcon extends ConsumerWidget {
}
}
class DesktopBuyIcon extends ConsumerWidget {
const DesktopBuyIcon({Key? key}) : super(key: key);
@override
Widget build(BuildContext context, WidgetRef ref) {
return SvgPicture.asset(
Assets.svg.buyDesktop,
width: 20,
height: 20,
color: DesktopMenuItemId.buy ==
ref.watch(currentDesktopMenuItemProvider.state).state
? Theme.of(context).extension<StackColors>()!.accentColorDark
: Theme.of(context)
.extension<StackColors>()!
.accentColorDark
.withOpacity(0.8),
);
}
}
class DesktopNotificationsIcon extends ConsumerWidget {
const DesktopNotificationsIcon({Key? key}) : super(key: key);

View file

@ -6,8 +6,6 @@ import 'package:flutter/material.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:flutter_svg/svg.dart';
import 'package:stackwallet/notifications/show_flush_bar.dart';
import 'package:stackwallet/pages/exchange_view/sub_widgets/exchange_rate_sheet.dart';
import 'package:stackwallet/pages/exchange_view/wallet_initiated_exchange_view.dart';
import 'package:stackwallet/pages/paynym/paynym_claim_view.dart';
import 'package:stackwallet/pages/paynym/paynym_home_view.dart';
import 'package:stackwallet/pages_desktop_specific/my_stack_view/wallet_view/sub_widgets/delete_wallet_button.dart';
@ -26,8 +24,6 @@ import 'package:stackwallet/services/coins/dogecoin/dogecoin_wallet.dart';
import 'package:stackwallet/services/coins/firo/firo_wallet.dart';
import 'package:stackwallet/services/event_bus/events/global/wallet_sync_status_changed_event.dart';
import 'package:stackwallet/services/event_bus/global_event_bus.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/utilities/assets.dart';
import 'package:stackwallet/utilities/enums/backup_frequency_type.dart';
import 'package:stackwallet/utilities/enums/coin_enum.dart';
@ -44,8 +40,6 @@ import 'package:stackwallet/widgets/desktop/secondary_button.dart';
import 'package:stackwallet/widgets/hover_text_field.dart';
import 'package:stackwallet/widgets/loading_indicator.dart';
import 'package:stackwallet/widgets/rounded_white_container.dart';
import 'package:stackwallet/widgets/stack_dialog.dart';
import 'package:tuple/tuple.dart';
/// [eventBus] should only be set during testing
class DesktopWalletView extends ConsumerStatefulWidget {
@ -70,8 +64,6 @@ class _DesktopWalletViewState extends ConsumerState<DesktopWalletView> {
late final bool _shouldDisableAutoSyncOnLogOut;
final _cnLoadingService = ExchangeDataLoadingService();
Future<void> onBackPressed() async {
await _logout();
if (mounted) {
@ -96,87 +88,6 @@ class _DesktopWalletViewState extends ConsumerState<DesktopWalletView> {
ref.read(managerProvider.notifier).isActiveWallet = false;
}
void _loadCNData() {
// unawaited future
if (ref.read(prefsChangeNotifierProvider).externalCalls) {
_cnLoadingService.loadAll(ref,
coin: ref
.read(walletsChangeNotifierProvider)
.getManager(widget.walletId)
.coin);
} else {
Logging.instance.log("User does not want to use external calls",
level: LogLevel.Info);
}
}
void _onExchangePressed(BuildContext context) async {
final managerProvider = ref
.read(walletsChangeNotifierProvider)
.getManagerProvider(widget.walletId);
unawaited(_cnLoadingService.loadAll(ref));
final coin = ref.read(managerProvider).coin;
if (coin == Coin.epicCash) {
await showDialog<void>(
context: context,
builder: (_) => const StackOkDialog(
title: "Exchange not available for Epic Cash",
),
);
} else if (coin.name.endsWith("TestNet")) {
await showDialog<void>(
context: context,
builder: (_) => const StackOkDialog(
title: "Exchange not available for test net coins",
),
);
} else {
ref.read(currentExchangeNameStateProvider.state).state =
ChangeNowExchange.exchangeName;
ref.read(prefsChangeNotifierProvider).exchangeRateType =
ExchangeRateType.estimated;
ref.read(exchangeFormStateProvider).exchange = ref.read(exchangeProvider);
ref.read(exchangeFormStateProvider).exchangeType =
ExchangeRateType.estimated;
final currencies = ref
.read(availableChangeNowCurrenciesProvider)
.currencies
.where((element) =>
element.ticker.toLowerCase() == coin.ticker.toLowerCase());
if (currencies.isNotEmpty) {
ref.read(exchangeFormStateProvider).setCurrencies(
currencies.first,
ref
.read(availableChangeNowCurrenciesProvider)
.currencies
.firstWhere(
(element) =>
element.ticker.toLowerCase() !=
coin.ticker.toLowerCase(),
),
);
}
if (mounted) {
unawaited(
Navigator.of(context).pushNamed(
WalletInitiatedExchangeView.routeName,
arguments: Tuple3(
widget.walletId,
coin,
_loadCNData,
),
),
);
}
}
}
Future<void> attemptAnonymize() async {
final managerProvider = ref
.read(walletsChangeNotifierProvider)

View file

@ -0,0 +1,6 @@
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:stackwallet/models/buy/buy_form_state.dart';
final buyFormStateProvider = ChangeNotifierProvider<BuyFormState>(
(ref) => BuyFormState(),
);

View file

@ -0,0 +1,11 @@
import 'package:flutter_riverpod/flutter_riverpod.dart';
enum SimplexLoadStatus {
waiting,
loading,
success,
failed,
}
final simplexLoadStatusStateProvider =
StateProvider<SimplexLoadStatus>((ref) => SimplexLoadStatus.waiting);

View file

@ -0,0 +1,6 @@
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:stackwallet/models/buy/simplex/simplex.dart';
final simplexProvider = Provider<Simplex>(
(ref) => Simplex(),
);

View file

@ -1,3 +1,6 @@
export './buy/buy_form_state_provider.dart';
export './buy/simplex_initial_load_status.dart';
export './buy/simplex_provider.dart';
export './exchange/available_changenow_currencies_provider.dart';
export './exchange/available_simpleswap_currencies_provider.dart';
export './exchange/changenow_initial_load_status.dart';

View file

@ -2,6 +2,7 @@ import 'package:decimal/decimal.dart';
import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:stackwallet/models/buy/response_objects/quote.dart';
import 'package:stackwallet/models/contact_address_entry.dart';
import 'package:stackwallet/models/exchange/incomplete_exchange.dart';
import 'package:stackwallet/models/exchange/response_objects/trade.dart';
@ -21,6 +22,7 @@ import 'package:stackwallet/pages/address_book_views/subviews/address_book_filte
import 'package:stackwallet/pages/address_book_views/subviews/contact_details_view.dart';
import 'package:stackwallet/pages/address_book_views/subviews/edit_contact_address_view.dart';
import 'package:stackwallet/pages/address_book_views/subviews/edit_contact_name_emoji_view.dart';
import 'package:stackwallet/pages/buy_view/buy_quote_preview.dart';
import 'package:stackwallet/pages/exchange_view/choose_from_stack_view.dart';
import 'package:stackwallet/pages/exchange_view/edit_trade_note_view.dart';
import 'package:stackwallet/pages/exchange_view/exchange_loading_overlay.dart';
@ -87,6 +89,8 @@ import 'package:stackwallet/pages/wallet_view/transaction_views/transaction_sear
import 'package:stackwallet/pages/wallet_view/wallet_view.dart';
import 'package:stackwallet/pages/wallets_view/wallets_view.dart';
import 'package:stackwallet/pages_desktop_specific/address_book_view/desktop_address_book.dart';
// import 'package:stackwallet/pages_desktop_specific/desktop_exchange/desktop_all_buys_view.dart';
import 'package:stackwallet/pages_desktop_specific/desktop_buy/desktop_buy_view.dart';
import 'package:stackwallet/pages_desktop_specific/desktop_exchange/desktop_all_trades_view.dart';
import 'package:stackwallet/pages_desktop_specific/desktop_exchange/desktop_exchange_view.dart';
import 'package:stackwallet/pages_desktop_specific/desktop_home_view.dart';
@ -1045,6 +1049,20 @@ class RouteGenerator {
}
return _routeError("${settings.name} invalid args: ${args.toString()}");
case BuyQuotePreviewView.routeName:
if (args is SimplexQuote) {
return getRoute(
shouldUseMaterialRoute: useMaterialPageRoute,
builder: (_) => BuyQuotePreviewView(
quote: args,
),
settings: RouteSettings(
name: settings.name,
),
);
}
return _routeError("${settings.name} invalid args: ${args.toString()}");
// == Desktop specific routes ============================================
case CreatePasswordView.routeName:
if (args is bool) {
@ -1107,6 +1125,12 @@ class RouteGenerator {
builder: (_) => const DesktopExchangeView(),
settings: RouteSettings(name: settings.name));
case DesktopBuyView.routeName:
return getRoute(
shouldUseMaterialRoute: useMaterialPageRoute,
builder: (_) => const DesktopBuyView(),
settings: RouteSettings(name: settings.name));
case DesktopAllTradesView.routeName:
return getRoute(
shouldUseMaterialRoute: useMaterialPageRoute,

44
lib/services/buy/buy.dart Normal file
View file

@ -0,0 +1,44 @@
abstract class Buy {
String get name;
// Future<BuyResponse<List<Currency>>> getAllCurrencies(bool fixedRate);
//
// Future<BuyResponse<List<Pair>>> getPairsFor(
// String currency,
// bool fixedRate,
// );
//
// Future<BuyResponse<List<Pair>>> getAllPairs(bool fixedRate);
//
// Future<BuyResponse<Trade>> getTrade(String tradeId);
// Future<BuyResponse<Trade>> updateTrade(Trade trade);
//
// Future<BuyResponse<List<Trade>>> getTrades();
//
// Future<BuyResponse<Range>> getRange(
// String from,
// String to,
// bool fixedRate,
// );
//
// Future<BuyResponse<Estimate>> getEstimate(
// String from,
// String to,
// Decimal amount,
// bool fixedRate,
// bool reversed,
// );
//
// Future<BuyResponse<Trade>> createTrade({
// required String from,
// required String to,
// required bool fixedRate,
// required Decimal amount,
// required String addressTo,
// String? extraId,
// required String addressRefund,
// required String refundExtraId,
// String? rateId,
// required bool reversed,
// });
}

View file

@ -0,0 +1,62 @@
// import 'package:flutter_riverpod/flutter_riverpod.dart';
// import 'package:stackwallet/providers/providers.dart';
// import 'package:stackwallet/services/buy/simplex/simplex_api.dart';
// import 'package:stackwallet/utilities/logger.dart';
//
// class BuyDataLoadingService {
// Future<void> loadAll(WidgetRef ref) async {
// try {
// await Future.wait([
// _loadSimplexCurrencies(ref),
// ]);
// } catch (e, s) {
// Logging.instance.log("BuyDataLoadingService.loadAll failed: $e\n$s",
// level: LogLevel.Error);
// }
// }
//
// Future<void> _loadSimplexCurrencies(WidgetRef ref) async {
// bool error = false;
// // if (ref.read(simplexLoadStatusStateProvider.state).state ==
// // SimplexLoadStatus.loading) {
// // // already in progress so just
// // return;
// // }
//
// ref.read(simplexLoadStatusStateProvider.state).state =
// SimplexLoadStatus.loading;
//
// final response = await SimplexAPI.instance.getSupported();
//
// if (response.value != null) {
// ref
// .read(supportedSimplexCurrenciesProvider)
// .updateSupportedCryptos(response.value!.item1);
// } else {
// error = true;
// Logging.instance.log(
// "_loadSimplexCurrencies: $response",
// level: LogLevel.Warning,
// );
// }
//
// if (response.value != null) {
// ref
// .read(supportedSimplexCurrenciesProvider)
// .updateSupportedFiats(response.value!.item2);
// } else {
// error = true;
// Logging.instance.log(
// "_loadSimplexCurrencies: $response",
// level: LogLevel.Warning,
// );
// }
//
// if (error) {
// // _loadSimplexCurrencies() again?
// } else {
// ref.read(simplexLoadStatusStateProvider.state).state =
// SimplexLoadStatus.success;
// }
// }
// }

View file

@ -0,0 +1,24 @@
enum BuyExceptionType { generic, serializeResponseError }
class BuyException implements Exception {
String errorMessage;
BuyExceptionType type;
BuyException(this.errorMessage, this.type);
@override
String toString() {
return errorMessage;
}
}
class BuyResponse<T> {
final T? value;
final BuyException? exception;
BuyResponse({this.value, this.exception});
@override
String toString() {
return "{error: $exception, value: $value}";
}
}

View file

@ -0,0 +1,332 @@
// TODO use _buildUri
import 'dart:async';
import 'dart:convert';
import 'package:decimal/decimal.dart';
import 'package:http/http.dart' as http;
import 'package:stackwallet/models/buy/response_objects/crypto.dart';
import 'package:stackwallet/models/buy/response_objects/fiat.dart';
import 'package:stackwallet/models/buy/response_objects/order.dart';
import 'package:stackwallet/models/buy/response_objects/quote.dart';
import 'package:stackwallet/services/buy/buy_response.dart';
import 'package:stackwallet/utilities/enums/fiat_enum.dart';
import 'package:stackwallet/utilities/logger.dart';
import 'package:stackwallet/utilities/prefs.dart';
import 'package:url_launcher/url_launcher.dart';
class SimplexAPI {
static const String authority = "sandbox-api.stackwallet.com";
// static const String authority = "localhost";
static const String scheme = authority == "localhost" ? "http" : "https";
final _prefs = Prefs.instance;
SimplexAPI._();
static final SimplexAPI _instance = SimplexAPI._();
static SimplexAPI get instance => _instance;
/// set this to override using standard http client. Useful for testing
http.Client? client;
Uri _buildUri(String path, Map<String, String>? params) {
if (scheme == "http") {
return Uri.http(authority, path, params);
}
return Uri.https(authority, path, params);
}
Future<BuyResponse<List<Crypto>>> getSupportedCryptos() async {
try {
Map<String, String> headers = {
'Content-Type': 'application/x-www-form-urlencoded',
};
Map<String, String> data = {
'ROUTE': 'supported_cryptos',
};
Uri url = _buildUri('api.php', data);
var res = await http.post(url, headers: headers);
if (res.statusCode != 200) {
throw Exception(
'getAvailableCurrencies exception: statusCode= ${res.statusCode}');
}
final jsonArray = jsonDecode(res.body); // TODO handle if invalid json
return _parseSupportedCryptos(jsonArray);
} catch (e, s) {
Logging.instance.log("getAvailableCurrencies exception: $e\n$s",
level: LogLevel.Error);
return BuyResponse(
exception: BuyException(
e.toString(),
BuyExceptionType.generic,
),
);
}
}
BuyResponse<List<Crypto>> _parseSupportedCryptos(dynamic jsonArray) {
try {
List<Crypto> cryptos = [];
List<Fiat> fiats = [];
for (final crypto in jsonArray as List) {
// TODO validate jsonArray
cryptos.add(Crypto.fromJson({
'ticker': "${crypto['ticker_symbol']}",
'name': crypto['name'],
'network': "${crypto['network']}",
'contractAddress': "${crypto['contractAddress']}",
'image': "",
}));
}
return BuyResponse(value: cryptos);
} catch (e, s) {
Logging.instance
.log("_parseSupported exception: $e\n$s", level: LogLevel.Error);
return BuyResponse(
exception: BuyException(
e.toString(),
BuyExceptionType.generic,
),
);
}
}
Future<BuyResponse<List<Fiat>>> getSupportedFiats() async {
try {
Map<String, String> headers = {
'Content-Type': 'application/x-www-form-urlencoded',
};
Map<String, String> data = {
'ROUTE': 'supported_fiats',
};
Uri url = _buildUri('api.php', data);
var res = await http.post(url, headers: headers);
if (res.statusCode != 200) {
throw Exception(
'getAvailableCurrencies exception: statusCode= ${res.statusCode}');
}
final jsonArray = jsonDecode(res.body); // TODO validate json
return _parseSupportedFiats(jsonArray);
} catch (e, s) {
Logging.instance.log("getAvailableCurrencies exception: $e\n$s",
level: LogLevel.Error);
return BuyResponse(
exception: BuyException(
e.toString(),
BuyExceptionType.generic,
),
);
}
}
BuyResponse<List<Fiat>> _parseSupportedFiats(dynamic jsonArray) {
try {
List<Crypto> cryptos = [];
List<Fiat> fiats = [];
for (final fiat in jsonArray as List) {
if (isSimplexFiat("${fiat['ticker_symbol']}")) {
// TODO validate list
fiats.add(Fiat.fromJson({
'ticker': "${fiat['ticker_symbol']}",
'name': fiatFromTickerCaseInsensitive("${fiat['ticker_symbol']}")
.prettyName,
'min_amount': "${fiat['min_amount']}",
'max_amount': "${fiat['max_amount']}",
'image': "",
}));
} // TODO handle else
}
return BuyResponse(value: fiats);
} catch (e, s) {
Logging.instance
.log("_parseSupported exception: $e\n$s", level: LogLevel.Error);
return BuyResponse(
exception: BuyException(
e.toString(),
BuyExceptionType.generic,
),
);
}
}
Future<BuyResponse<SimplexQuote>> getQuote(SimplexQuote quote) async {
try {
await _prefs.init();
String? userID = _prefs.userID;
Map<String, String> headers = {
'Content-Type': 'application/x-www-form-urlencoded',
};
Map<String, String> data = {
'ROUTE': 'quote',
'CRYPTO_TICKER': quote.crypto.ticker.toUpperCase(),
'FIAT_TICKER': quote.fiat.ticker.toUpperCase(),
'REQUESTED_TICKER': quote.buyWithFiat
? quote.fiat.ticker.toUpperCase()
: quote.crypto.ticker.toUpperCase(),
'REQUESTED_AMOUNT': quote.buyWithFiat
? "${quote.youPayFiatPrice}"
: "${quote.youReceiveCryptoAmount}",
};
if (userID != null) {
data['USER_ID'] = userID;
}
Uri url = _buildUri('api.php', data);
var res = await http.get(url, headers: headers);
if (res.statusCode != 200) {
throw Exception('getQuote exception: statusCode= ${res.statusCode}');
}
final jsonArray = jsonDecode(res.body);
jsonArray['quote'] = quote; // Add and pass this on
return _parseQuote(jsonArray);
} catch (e, s) {
Logging.instance.log("getQuote exception: $e\n$s", level: LogLevel.Error);
return BuyResponse(
exception: BuyException(
e.toString(),
BuyExceptionType.generic,
),
);
}
}
BuyResponse<SimplexQuote> _parseQuote(dynamic jsonArray) {
try {
String cryptoAmount = "${jsonArray['digital_money']['amount']}";
SimplexQuote quote = jsonArray['quote'] as SimplexQuote;
final SimplexQuote _quote = SimplexQuote(
crypto: quote.crypto,
fiat: quote.fiat,
youPayFiatPrice: quote.buyWithFiat
? quote.youPayFiatPrice
: Decimal.parse("${jsonArray['fiat_money']['base_amount']}"),
youReceiveCryptoAmount:
Decimal.parse("${jsonArray['digital_money']['amount']}"),
id: jsonArray['quote_id'] as String,
receivingAddress: quote.receivingAddress,
buyWithFiat: quote.buyWithFiat,
);
return BuyResponse(value: _quote);
} catch (e, s) {
Logging.instance
.log("_parseQuote exception: $e\n$s", level: LogLevel.Error);
return BuyResponse(
exception: BuyException(
e.toString(),
BuyExceptionType.generic,
),
);
}
}
Future<BuyResponse<SimplexOrder>> newOrder(SimplexQuote quote) async {
// Calling Simplex's API manually:
// curl --request POST \
// --url https://sandbox.test-simplexcc.com/wallet/merchant/v2/payments/partner/data \
// --header 'Authorization: ApiKey $apiKey' \
// --header 'accept: application/json' \
// --header 'content-type: application/json' \
// -d '{"account_details": {"app_provider_id": "$publicKey", "app_version_id": "123", "app_end_user_id": "01e7a0b9-8dfc-4988-a28d-84a34e5f0a63", "signup_login": {"timestamp": "1994-11-05T08:15:30-05:00", "ip": "207.66.86.226"}}, "transaction_details": {"payment_details": {"quote_id": "3b58f4b4-ed6f-447c-b96a-ffe97d7b6803", "payment_id": "baaaaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa", "order_id": "789", "original_http_ref_url": "https://stackwallet.com/simplex", "destination_wallet": {"currency": "BTC", "address": "bc1qjvj9ca8gdsv3g58yrzrk6jycvgnjh9uj35rja2"}}}}'
try {
await _prefs.init();
String? userID = _prefs.userID;
int? signupEpoch = _prefs.signupEpoch;
Map<String, String> headers = {
'Content-Type': 'application/x-www-form-urlencoded',
};
Map<String, String> data = {
'ROUTE': 'order',
'QUOTE_ID': quote.id,
'ADDRESS': quote.receivingAddress,
'CRYPTO_TICKER': quote.crypto.ticker.toUpperCase(),
};
if (userID != null) {
data['USER_ID'] = userID;
}
if (signupEpoch != null && signupEpoch != 0) {
DateTime date = DateTime.fromMillisecondsSinceEpoch(signupEpoch * 1000);
data['SIGNUP_TIMESTAMP'] =
date.toIso8601String() + timeZoneFormatter(date.timeZoneOffset);
}
Uri url = _buildUri('api.php', data);
print(data);
var res = await http.get(url, headers: headers);
if (res.statusCode != 200) {
throw Exception('newOrder exception: statusCode= ${res.statusCode}');
}
final jsonArray = jsonDecode(res.body); // TODO check if valid json
SimplexOrder _order = SimplexOrder(
quote: quote,
paymentId: "${jsonArray['paymentId']}",
orderId: "${jsonArray['orderId']}",
userId: "${jsonArray['userId']}",
);
return BuyResponse(value: _order);
} catch (e, s) {
Logging.instance.log("newOrder exception: $e\n$s", level: LogLevel.Error);
return BuyResponse(
exception: BuyException(
e.toString(),
BuyExceptionType.generic,
),
);
}
}
Future<BuyResponse<bool>> redirect(SimplexOrder order) async {
try {
Map<String, String> headers = {
'Content-Type': 'application/x-www-form-urlencoded',
};
Map<String, String> data = {
'ROUTE': 'redirect',
'PAYMENT_ID': order.paymentId,
};
Uri url = _buildUri('api.php', data);
bool status = await launchUrl(
url,
mode: LaunchMode.externalApplication,
);
return BuyResponse(value: status);
} catch (e, s) {
Logging.instance.log("newOrder exception: $e\n$s", level: LogLevel.Error);
return BuyResponse(
exception: BuyException(
e.toString(),
BuyExceptionType.generic,
));
}
}
bool isSimplexFiat(String ticker) {
try {
fiatFromTickerCaseInsensitive(ticker);
return true;
} on ArgumentError catch (_) {
return false;
}
}
// See https://github.com/dart-lang/sdk/issues/43391#issuecomment-1229656422
String timeZoneFormatter(Duration offset) =>
"${offset.isNegative ? "-" : "+"}${offset.inHours.abs().toString().padLeft(2, "0")}:${(offset.inMinutes - offset.inHours * 60).abs().toString().padLeft(2, "0")}";
}

View file

@ -9,6 +9,7 @@ abstract class Assets {
static const lottie = _ANIMATIONS();
static const socials = _SOCIALS();
static const exchange = _EXCHANGE();
static const buy = _BUY();
}
class _SOCIALS {
@ -27,6 +28,25 @@ class _EXCHANGE {
String get simpleSwap => "assets/svg/exchange_icons/simpleswap-icon.svg";
}
class _BUY {
const _BUY();
// TODO: switch this to something like
// String buy(BuildContext context) =>
// "assets/svg/${Theme.of(context).extension<StackColors>()!.themeType.name}/buy.svg";
String get buy => "assets/svg/light/buy-coins-icon.svg";
String simplexLogo(BuildContext context) {
return (Theme.of(context).extension<StackColors>()!.themeType ==
ThemeType.dark ||
Theme.of(context).extension<StackColors>()!.themeType ==
ThemeType
.oledBlack) // TODO make sure this cover OLED black, too
? "assets/svg/buy/Simplex-Nuvei-Logo-light.svg"
: "assets/svg/buy/Simplex-Nuvei-Logo.svg";
}
}
class _SVG {
const _SVG();
String? background(BuildContext context) {
@ -169,6 +189,7 @@ class _SVG {
String get anonymizeFailed => "assets/svg/tx-icon-anonymize-failed.svg";
String get addressBookDesktop => "assets/svg/address-book-desktop.svg";
String get exchangeDesktop => "assets/svg/exchange-desktop.svg";
String get buyDesktop => "assets/svg/light/buy-coins-icon.svg";
String get aboutDesktop => "assets/svg/about-desktop.svg";
String get walletDesktop => "assets/svg/wallet-desktop.svg";
String get exitDesktop => "assets/svg/exit-desktop.svg";
@ -189,6 +210,17 @@ class _SVG {
String get namecoin => "assets/svg/coin_icons/Namecoin.svg";
String get particl => "assets/svg/coin_icons/Particl.svg";
String get cosmos => "assets/svg/coin_icons/Cosmos.svg";
String get binanceusd => "assets/svg/coin_icons/BinanceUSD.svg";
String get dai => "assets/svg/coin_icons/Dai.svg";
String get dash => "assets/svg/coin_icons/Dash.svg";
String get eos => "assets/svg/coin_icons/EOS.svg";
String get ethereum => "assets/svg/coin_icons/Ethereum.svg";
String get tron => "assets/svg/coin_icons/Tron.svg";
String get tether => "assets/svg/coin_icons/Tether.svg";
String get stellar => "assets/svg/coin_icons/Stellar.svg";
String get ripple => "assets/svg/coin_icons/Ripple.svg";
String get chevronRight => "assets/svg/chevron-right.svg";
String get minimize => "assets/svg/minimize.svg";
String get walletFa => "assets/svg/wallet-fa.svg";
@ -236,6 +268,33 @@ class _SVG {
return dogecoinTestnet;
}
}
String? buyIconFor(String ticker) {
switch (ticker.toLowerCase()) {
case 'atom':
return cosmos;
case 'busd':
return binanceusd;
case 'dai':
return dai;
case 'dash':
return dash;
case 'eos':
return eos;
case 'eth':
return ethereum;
case 'trx':
return tron;
case 'usdt':
return tether;
case 'xlm':
return stellar;
case 'xrp':
return ripple;
default:
return null;
}
}
}
class _PNG {

View file

@ -21,6 +21,7 @@ abstract class Constants {
}
static bool enableExchange = Util.isDesktop || !Platform.isIOS;
static bool enableBuy = true; // true for development, TODO change to "Util.isDesktop || !Platform.isIOS;" as above or even just = enableExchange
//TODO: correct for monero?
static const int _satsPerCoinMonero = 1000000000000;

View file

@ -337,8 +337,6 @@ Coin coinFromTickerCaseInsensitive(String ticker) {
return Coin.particl;
case "tltc":
return Coin.litecoinTestNet;
case "part":
return Coin.particl;
case "tbtc":
return Coin.bitcoinTestNet;
case "tbch":

File diff suppressed because it is too large Load diff

View file

@ -5,6 +5,7 @@ import 'package:stackwallet/utilities/constants.dart';
import 'package:stackwallet/utilities/enums/backup_frequency_type.dart';
import 'package:stackwallet/utilities/enums/languages_enum.dart';
import 'package:stackwallet/utilities/enums/sync_type_enum.dart';
import 'package:uuid/uuid.dart';
class Prefs extends ChangeNotifier {
Prefs._();
@ -38,6 +39,8 @@ class Prefs extends ChangeNotifier {
_startupWalletId = await _getStartupWalletId();
_externalCalls = await _getHasExternalCalls();
_familiarity = await _getHasFamiliarity();
_userId = await _getUserId();
_signupEpoch = await _getSignupEpoch();
_initialized = true;
}
@ -602,4 +605,45 @@ class Prefs extends ChangeNotifier {
}
return true;
}
String? _userId;
String? get userID => _userId;
Future<String?> _getUserId() async {
String? userID = await DB.instance
.get<dynamic>(boxName: DB.boxNamePrefs, key: "userID") as String?;
if (userID == null) {
userID = const Uuid().v4();
await saveUserID(userID);
}
return userID;
}
Future<void> saveUserID(String userId) async {
_userId = userId;
await DB.instance
.put<dynamic>(boxName: DB.boxNamePrefs, key: "userID", value: _userId);
// notifyListeners();
}
int? _signupEpoch;
int? get signupEpoch => _signupEpoch;
Future<int?> _getSignupEpoch() async {
int? signupEpoch = await DB.instance
.get<dynamic>(boxName: DB.boxNamePrefs, key: "signupEpoch") as int?;
if (signupEpoch == null) {
signupEpoch = DateTime.now().millisecondsSinceEpoch ~/
Duration.millisecondsPerSecond;
await saveSignupEpoch(signupEpoch);
}
return signupEpoch;
}
Future<void> saveSignupEpoch(int signupEpoch) async {
_signupEpoch = signupEpoch;
await DB.instance.put<dynamic>(
boxName: DB.boxNamePrefs, key: "signupEpoch", value: _signupEpoch);
// notifyListeners();
}
}

View file

@ -16,7 +16,7 @@ class OledBlackColors extends StackColorTheme {
Color get overlay => const Color(0xFF121212);
@override
Color get accentColorBlue => const Color(0xFF77A7F9);
Color get accentColorBlue => const Color(0xFFF26822);
@override
Color get accentColorGreen => const Color(0xFF4CC0A0);
@override
@ -61,17 +61,17 @@ class OledBlackColors extends StackColorTheme {
// button background
@override
Color get buttonBackPrimary => const Color(0xFF6F9CE9);
Color get buttonBackPrimary => const Color(0xFFF26822);
@override
Color get buttonBackSecondary => const Color(0xFF1F1F1F);
@override
Color get buttonBackPrimaryDisabled => const Color(0xFF212F46);
Color get buttonBackPrimaryDisabled => const Color(0xFF491F0A);
@override
Color get buttonBackSecondaryDisabled => const Color(0xFF3D3D3D);
Color get buttonBackSecondaryDisabled => const Color(0xFF0F0F0F);
@override
Color get buttonBackBorder => const Color(0xFF6F9CE9);
Color get buttonBackBorder => const Color(0xFFF26822);
@override
Color get buttonBackBorderDisabled => const Color(0xFF212F46);
Color get buttonBackBorderDisabled => const Color(0xFF491F0A);
@override
Color get buttonBackBorderSecondary => buttonBackSecondary;
@override
@ -80,7 +80,7 @@ class OledBlackColors extends StackColorTheme {
@override
Color get numberBackDefault => const Color(0xFF242424);
@override
Color get numpadBackDefault => const Color(0xFF6F9CE9);
Color get numpadBackDefault => const Color(0xFFF26822);
@override
Color get bottomNavBack => const Color(0xFF202122);
@ -92,15 +92,15 @@ class OledBlackColors extends StackColorTheme {
@override
Color get buttonTextPrimaryDisabled => const Color(0xFF000000);
@override
Color get buttonTextSecondaryDisabled => const Color(0xFF090909);
Color get buttonTextSecondaryDisabled => const Color(0xFF6F6F6F);
@override
Color get buttonTextBorder => const Color(0xFF6F9CE9);
Color get buttonTextBorder => const Color(0xFFF26822);
@override
Color get buttonTextDisabled => const Color(0xFF000000);
@override
Color get buttonTextBorderless => const Color(0xFF6F9CE9);
Color get buttonTextBorderless => const Color(0xFFF26822);
@override
Color get buttonTextBorderlessDisabled => const Color(0xFF212F46);
Color get buttonTextBorderlessDisabled => const Color(0xFF491F0A);
@override
Color get numberTextDefault => const Color(0xFFD3D3D3);
@override
@ -110,27 +110,27 @@ class OledBlackColors extends StackColorTheme {
// switch
@override
Color get switchBGOn => const Color(0xFF77A7F9);
Color get switchBGOn => const Color(0xFFF26822);
@override
Color get switchBGOff => const Color(0xFF445C85);
Color get switchBGOff => const Color(0xFF403F3F);
@override
Color get switchBGDisabled => const Color(0xFF333538);
@override
Color get switchCircleOn => const Color(0xFFC9DDF5);
Color get switchCircleOn => const Color(0xFFFFE8DC);
@override
Color get switchCircleOff => const Color(0xFF94AAC9);
Color get switchCircleOff => const Color(0xFFFAF6F3);
@override
Color get switchCircleDisabled => const Color(0xFF848484);
// step indicator background
@override
Color get stepIndicatorBGCheck => const Color(0xFF77A7F9);
Color get stepIndicatorBGCheck => const Color(0xFFF26822);
@override
Color get stepIndicatorBGNumber => const Color(0xFF77A7F9);
Color get stepIndicatorBGNumber => const Color(0xFFF26822);
@override
Color get stepIndicatorBGInactive => const Color(0xFF3B3F46);
@override
Color get stepIndicatorBGLines => const Color(0xFF6393E5);
Color get stepIndicatorBGLines => const Color(0xFFF26822);
@override
Color get stepIndicatorBGLinesInactive => const Color(0xFF63676E);
@override
@ -138,15 +138,15 @@ class OledBlackColors extends StackColorTheme {
@override
Color get stepIndicatorIconNumber => const Color(0xFF000000);
@override
Color get stepIndicatorIconInactive => const Color(0xFFA5A5A5);
Color get stepIndicatorIconInactive => const Color(0xFFAFAFAF);
// checkbox
@override
Color get checkboxBGChecked => const Color(0xFF77A7F9);
Color get checkboxBGChecked => const Color(0xFFF26822);
@override
Color get checkboxBorderEmpty => const Color(0xFF353536);
Color get checkboxBorderEmpty => const Color(0xFF66696A);
@override
Color get checkboxBGDisabled => const Color(0xFF5D759B);
Color get checkboxBGDisabled => const Color(0xFF783818);
@override
Color get checkboxIconChecked => const Color(0xFF000000);
@override
@ -251,17 +251,17 @@ class OledBlackColors extends StackColorTheme {
// radio buttons
@override
Color get radioButtonIconBorder => const Color(0xFF77A7F9);
Color get radioButtonIconBorder => const Color(0xFFF26822);
@override
Color get radioButtonIconBorderDisabled => const Color(0xFF7D7D7D);
@override
Color get radioButtonBorderEnabled => const Color(0xFF77A7F9);
Color get radioButtonBorderEnabled => const Color(0xFFF26822);
@override
Color get radioButtonBorderDisabled => const Color(0xFF7D7D7D);
@override
Color get radioButtonIconCircle => const Color(0xFF77A7F9);
Color get radioButtonIconCircle => const Color(0xFFF26822);
@override
Color get radioButtonIconEnabled => const Color(0xFF77A7F9);
Color get radioButtonIconEnabled => const Color(0xFFF26822);
@override
Color get radioButtonTextEnabled => const Color(0xFFA8AAB2);
@override
@ -279,7 +279,7 @@ class OledBlackColors extends StackColorTheme {
@override
Color get infoItemText => const Color(0xFFDEDEDE);
@override
Color get infoItemIcons => const Color(0xFF77A7F9);
Color get infoItemIcons => const Color(0xFF5C94F4);
// popup
@override

View file

@ -320,6 +320,17 @@ flutter:
- assets/svg/coin_icons/Wownero.svg
- assets/svg/coin_icons/Namecoin.svg
- assets/svg/coin_icons/Particl.svg
# buy coin icons
- assets/svg/coin_icons/Cosmos.svg
- assets/svg/coin_icons/BinanceUSD.svg
- assets/svg/coin_icons/Dai.svg
- assets/svg/coin_icons/Dash.svg
- assets/svg/coin_icons/EOS.svg
- assets/svg/coin_icons/Ethereum.svg
- assets/svg/coin_icons/Tron.svg
- assets/svg/coin_icons/Tether.svg
- assets/svg/coin_icons/Stellar.svg
- assets/svg/coin_icons/Ripple.svg
# lottie animations
- assets/lottie/test.json
- assets/lottie/test2.json
@ -425,6 +436,10 @@ flutter:
- assets/svg/fruitSorbet/buy-coins-icon.svg
- assets/svg/fruitSorbet/bg.svg
# buy
- assets/svg/buy/Simplex-Nuvei-Logo.svg
- assets/svg/buy/Simplex-Nuvei-Logo-light.svg
# An image asset can refer to one or more resolution-specific "variants", see
# https://flutter.dev/assets-and-images/#resolution-aware.
# For details regarding adding assets from package dependencies, see