Merge remote-tracking branch 'origin/simplex' into ui-fixes
65
assets/svg/buy/Simplex-Nuvei-Logo-light.svg
Normal file
After Width: | Height: | Size: 10 KiB |
65
assets/svg/buy/Simplex-Nuvei-Logo.svg
Normal file
After Width: | Height: | Size: 10 KiB |
1
assets/svg/coin_icons/BinanceUSD.svg
Normal 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 |
1
assets/svg/coin_icons/Cosmos.svg
Normal 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 |
10
assets/svg/coin_icons/Dai.svg
Normal 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 |
1
assets/svg/coin_icons/Dash.svg
Normal 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 |
1
assets/svg/coin_icons/EOS.svg
Normal 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 |
18
assets/svg/coin_icons/Ethereum.svg
Normal 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 |
|
@ -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 |
1
assets/svg/coin_icons/Ripple.svg
Normal 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 |
1
assets/svg/coin_icons/Stellar.svg
Normal 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 |
1
assets/svg/coin_icons/Tether.svg
Normal 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 |
1
assets/svg/coin_icons/Tron.svg
Normal 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 |
|
@ -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}) =>
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
|
25
lib/models/buy/buy_form_state.dart
Normal 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)
|
||||
}
|
||||
}
|
61
lib/models/buy/response_objects/crypto.dart
Normal 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()}";
|
||||
}
|
||||
}
|
64
lib/models/buy/response_objects/fiat.dart
Normal 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()}";
|
||||
}
|
||||
}
|
17
lib/models/buy/response_objects/order.dart
Normal 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,
|
||||
});
|
||||
}
|
73
lib/models/buy/response_objects/pair.dart
Normal 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()}";
|
||||
}
|
26
lib/models/buy/response_objects/quote.dart
Normal 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,
|
||||
});
|
||||
}
|
32
lib/models/buy/simplex/simplex.dart
Normal 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;
|
||||
}
|
||||
}
|
1193
lib/pages/buy_view/buy_form.dart
Normal file
267
lib/pages/buy_view/buy_order_details.dart
Normal 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();
|
||||
},
|
||||
)
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
233
lib/pages/buy_view/buy_quote_preview.dart
Normal 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,
|
||||
)
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
|
@ -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(),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
|
132
lib/pages/buy_view/sub_widgets/buy_warning_popup.dart
Normal 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),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
262
lib/pages/buy_view/sub_widgets/crypto_selection_view.dart
Normal 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;
|
||||
}
|
270
lib/pages/buy_view/sub_widgets/fiat_selection_view.dart
Normal 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,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
},
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
|
@ -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) {
|
||||
|
|
|
@ -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,
|
||||
// ),
|
||||
// ),
|
||||
],
|
||||
),
|
||||
),
|
||||
|
|
|
@ -127,37 +127,48 @@ 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>()!
|
||||
.buttonTextSecondary,
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
);
|
||||
}
|
||||
|
|
|
@ -232,6 +232,8 @@ class _StackPrivacyCalls extends ConsumerState<StackPrivacyCalls> {
|
|||
if (isEasy) {
|
||||
unawaited(ExchangeDataLoadingService()
|
||||
.loadAll(ref));
|
||||
// unawaited(
|
||||
// BuyDataLoadingService().loadAll(ref));
|
||||
ref
|
||||
.read(priceAnd24hChangeNotifierProvider)
|
||||
.start(true);
|
||||
|
|
|
@ -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,
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
|
|
78
lib/pages_desktop_specific/desktop_buy/desktop_buy_view.dart
Normal 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(),
|
||||
// ),
|
||||
// ],
|
||||
// ),
|
||||
// ),
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
|
@ -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,
|
||||
|
|
|
@ -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],
|
||||
),
|
||||
],
|
||||
),
|
||||
|
|
|
@ -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);
|
||||
|
||||
|
|
|
@ -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)
|
||||
|
|
6
lib/providers/buy/buy_form_state_provider.dart
Normal 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(),
|
||||
);
|
11
lib/providers/buy/simplex_initial_load_status.dart
Normal 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);
|
6
lib/providers/buy/simplex_provider.dart
Normal 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(),
|
||||
);
|
|
@ -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';
|
||||
|
|
|
@ -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
|
@ -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,
|
||||
// });
|
||||
}
|
62
lib/services/buy/buy_data_loading_service.dart
Normal 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;
|
||||
// }
|
||||
// }
|
||||
// }
|
24
lib/services/buy/buy_response.dart
Normal 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}";
|
||||
}
|
||||
}
|
332
lib/services/buy/simplex/simplex_api.dart
Normal 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")}";
|
||||
}
|
|
@ -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 {
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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":
|
||||
|
|
1156
lib/utilities/enums/fiat_enum.dart
Normal 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();
|
||||
}
|
||||
}
|
||||
|
|
15
pubspec.yaml
|
@ -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
|
||||
|
|