Merge branch 'simplex' into paynyms

This commit is contained in:
julian 2023-01-26 10:16:01 -06:00
commit 5de22ca858
64 changed files with 4939 additions and 315 deletions

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 10 KiB

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 10 KiB

View file

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

After

Width:  |  Height:  |  Size: 528 B

View file

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

After

Width:  |  Height:  |  Size: 2.1 KiB

View file

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

After

Width:  |  Height:  |  Size: 2.7 KiB

View file

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

After

Width:  |  Height:  |  Size: 621 B

View file

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

After

Width:  |  Height:  |  Size: 322 B

View file

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

After

Width:  |  Height:  |  Size: 1.3 KiB

View file

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

Before

Width:  |  Height:  |  Size: 1.7 KiB

After

Width:  |  Height:  |  Size: 489 B

View file

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

After

Width:  |  Height:  |  Size: 472 B

View file

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

After

Width:  |  Height:  |  Size: 705 B

View file

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

After

Width:  |  Height:  |  Size: 874 B

View file

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

After

Width:  |  Height:  |  Size: 651 B

View file

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

View file

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

View file

@ -0,0 +1,13 @@
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;
}
bool reversed = false;
}

View file

@ -0,0 +1,44 @@
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;
}
}

View file

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

View file

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

View file

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

View file

@ -0,0 +1,51 @@
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/order.dart';
import 'package:stackwallet/models/buy/response_objects/quote.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,
);
SimplexOrder order = SimplexOrder(
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,
),
orderId: 'aaaaaaaa-bbbb-cccc-dddd-eeeeeeeeeeee',
paymentId: 'aaaaaaaa-bbbb-cccc-dddd-eeeeeeeeeeee',
userId: 'aaaaaaaa-bbbb-cccc-dddd-eeeeeeeeeeee');
void updateSupportedCryptos(List<Crypto> newCryptos) {
supportedCryptos = newCryptos;
}
void updateSupportedFiats(List<Fiat> newFiats) {
supportedFiats = newFiats;
}
void updateQuote(SimplexQuote newQuote) {
quote = newQuote;
}
void updateOrder(SimplexOrder newOrder) {
order = newOrder;
}
}

File diff suppressed because it is too large Load diff

View file

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

View file

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

View file

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

View file

@ -0,0 +1,143 @@
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 {
final response = await SimplexAPI.instance.newOrder(quote);
// if (response.value != null) {
// ref.read(simplexProvider).updateOrder(response.value!);
// } else {
// Logging.instance.log(
// "_loadQuote: $response",
// level: LogLevel.Warning,
// );
// }
return response;
}
Future<BuyResponse<bool>> redirect(SimplexOrder order) async {
return SimplexAPI.instance.redirect(order);
}
@override
Widget build(BuildContext context) {
final isDesktop = Util.isDesktop;
Future<void> _buyInvoice() async {
await showDialog<void>(
context: context,
// useRootNavigator: isDesktop,
builder: (context) {
return isDesktop
? DesktopDialog(
maxHeight: 700,
maxWidth: 580,
child: Column(
children: [
Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Padding(
padding: const EdgeInsets.only(
left: 32,
),
child: Text(
"Order details",
style: STextStyles.desktopH3(context),
),
),
const DesktopDialogCloseButton(),
],
),
Expanded(
child: Padding(
padding: const EdgeInsets.only(
left: 32,
right: 32,
bottom: 32,
),
child: Row(
children: [
Expanded(
child: RoundedWhiteContainer(
padding: const EdgeInsets.all(16),
borderColor: Theme.of(context)
.extension<StackColors>()!
.background,
child: BuyOrderDetailsView(
order: order as SimplexOrder,
),
),
),
],
),
),
),
],
),
)
: BuyOrderDetailsView(
order: order as SimplexOrder,
);
});
}
return StackDialog(
title: "Buy ${quote.crypto.ticker}",
message: "This purchase is provided and fulfilled by Simplex by nuvei "
"(a third party). You will be taken to their website. Please follow "
"their instructions.",
leftButton: SecondaryButton(
label: "Cancel",
onPressed: Navigator.of(context, rootNavigator: isDesktop).pop,
),
rightButton: PrimaryButton(
label: "Continue",
onPressed: () async {
BuyResponse<SimplexOrder> order = await newOrder(quote);
await redirect(order.value as SimplexOrder).then((_response) async {
this.order = order.value as SimplexOrder;
Navigator.of(context, rootNavigator: isDesktop).pop();
Navigator.of(context, rootNavigator: isDesktop).pop();
await _buyInvoice();
});
},
),
icon: SizedBox(
width: 64,
height: 32,
child: SvgPicture.asset(
Assets.buy.simplexLogo(context),
),
),
);
}
}

View file

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

View file

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

View file

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

View file

@ -3,12 +3,11 @@ import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:stackwallet/pages/exchange_view/sub_widgets/exchange_rate_sheet.dart';
import 'package:stackwallet/providers/providers.dart';
import 'package:stackwallet/utilities/assets.dart';
import 'package:stackwallet/utilities/constants.dart';
import 'package:stackwallet/utilities/theme/stack_colors.dart';
import 'package:stackwallet/utilities/util.dart';
import 'package:stackwallet/widgets/toggle.dart';
import '../../../utilities/constants.dart';
class RateTypeToggle extends ConsumerWidget {
const RateTypeToggle({
Key? key,
@ -37,10 +36,16 @@ class RateTypeToggle extends ConsumerWidget {
}
},
isOn: !estimated,
onColor: Theme.of(context).extension<StackColors>()!.textFieldDefaultBG,
onColor: isDesktop
? Theme.of(context)
.extension<StackColors>()!
.rateTypeToggleDesktopColorOn
: Theme.of(context).extension<StackColors>()!.rateTypeToggleColorOn,
offColor: isDesktop
? Theme.of(context).extension<StackColors>()!.buttonBackSecondary
: Theme.of(context).extension<StackColors>()!.popupBG,
? Theme.of(context)
.extension<StackColors>()!
.rateTypeToggleDesktopColorOff
: Theme.of(context).extension<StackColors>()!.rateTypeToggleColorOff,
decoration: BoxDecoration(
borderRadius: BorderRadius.circular(
Constants.size.circularBorderRadius,

View file

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

View file

@ -127,37 +127,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,
),
),
),
),
],
);
}

View file

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

View file

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

View file

@ -0,0 +1,74 @@
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: const [
SizedBox(
height: 16,
),
RoundedWhiteContainer(
padding: EdgeInsets.all(24),
child: BuyForm(),
),
],
),
),
const SizedBox(
width: 16,
),
// Expanded(
// child: Row(
// children: const [
// Expanded(
// child: DesktopTradeHistory(),
// ),
// ],
// ),
// ),
],
),
),
);
}
}

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

@ -0,0 +1,3 @@
abstract class Buy {
String get name;
}

View file

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

View file

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

View file

@ -0,0 +1,330 @@
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 = "simplex-sandbox.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,
'minAmount': "${fiat['min_amount']}",
'maxAmount': "${fiat['max_amount']}",
'image': "",
}));
} // TODO handle else
}
return BuyResponse(value: fiats);
} catch (e, s) {
Logging.instance
.log("_parseSupported exception: $e\n$s", level: LogLevel.Error);
return BuyResponse(
exception: BuyException(
e.toString(),
BuyExceptionType.generic,
),
);
}
}
Future<BuyResponse<SimplexQuote>> getQuote(SimplexQuote quote) async {
try {
await _prefs.init();
String? userID = _prefs.userID;
Map<String, String> headers = {
'Content-Type': 'application/x-www-form-urlencoded',
};
Map<String, String> data = {
'ROUTE': 'quote',
'CRYPTO_TICKER': quote.crypto.ticker.toUpperCase(),
'FIAT_TICKER': quote.fiat.ticker.toUpperCase(),
'REQUESTED_TICKER': quote.buyWithFiat
? quote.fiat.ticker.toUpperCase()
: quote.crypto.ticker.toUpperCase(),
'REQUESTED_AMOUNT': quote.buyWithFiat
? "${quote.youPayFiatPrice}"
: "${quote.youReceiveCryptoAmount}",
};
if (userID != null) {
data['USER_ID'] = userID;
}
Uri url = _buildUri('api.php', data);
var res = await http.get(url, headers: headers);
if (res.statusCode != 200) {
throw Exception('getQuote exception: statusCode= ${res.statusCode}');
}
final jsonArray = jsonDecode(res.body);
jsonArray['quote'] = quote; // Add and pass this on
return _parseQuote(jsonArray);
} catch (e, s) {
Logging.instance.log("getQuote exception: $e\n$s", level: LogLevel.Error);
return BuyResponse(
exception: BuyException(
e.toString(),
BuyExceptionType.generic,
),
);
}
}
BuyResponse<SimplexQuote> _parseQuote(dynamic jsonArray) {
try {
String cryptoAmount = "${jsonArray['digital_money']['amount']}";
SimplexQuote quote = jsonArray['quote'] as SimplexQuote;
final SimplexQuote _quote = SimplexQuote(
crypto: quote.crypto,
fiat: quote.fiat,
youPayFiatPrice: quote.buyWithFiat
? quote.youPayFiatPrice
: Decimal.parse("${jsonArray['fiat_money']['base_amount']}"),
youReceiveCryptoAmount:
Decimal.parse("${jsonArray['digital_money']['amount']}"),
id: jsonArray['quote_id'] as String,
receivingAddress: quote.receivingAddress,
buyWithFiat: quote.buyWithFiat,
);
return BuyResponse(value: _quote);
} catch (e, s) {
Logging.instance
.log("_parseQuote exception: $e\n$s", level: LogLevel.Error);
return BuyResponse(
exception: BuyException(
e.toString(),
BuyExceptionType.generic,
),
);
}
}
Future<BuyResponse<SimplexOrder>> newOrder(SimplexQuote quote) async {
// Calling Simplex's API manually:
// curl --request POST \
// --url https://sandbox.test-simplexcc.com/wallet/merchant/v2/payments/partner/data \
// --header 'Authorization: ApiKey $apiKey' \
// --header 'accept: application/json' \
// --header 'content-type: application/json' \
// -d '{"account_details": {"app_provider_id": "$publicKey", "app_version_id": "123", "app_end_user_id": "01e7a0b9-8dfc-4988-a28d-84a34e5f0a63", "signup_login": {"timestamp": "1994-11-05T08:15:30-05:00", "ip": "207.66.86.226"}}, "transaction_details": {"payment_details": {"quote_id": "3b58f4b4-ed6f-447c-b96a-ffe97d7b6803", "payment_id": "baaaaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa", "order_id": "789", "original_http_ref_url": "https://stackwallet.com/simplex", "destination_wallet": {"currency": "BTC", "address": "bc1qjvj9ca8gdsv3g58yrzrk6jycvgnjh9uj35rja2"}}}}'
try {
await _prefs.init();
String? userID = _prefs.userID;
int? signupEpoch = _prefs.signupEpoch;
Map<String, String> headers = {
'Content-Type': 'application/x-www-form-urlencoded',
};
Map<String, String> data = {
'ROUTE': 'order',
'QUOTE_ID': quote.id,
'ADDRESS': quote.receivingAddress,
'CRYPTO_TICKER': quote.crypto.ticker.toUpperCase(),
};
if (userID != null) {
data['USER_ID'] = userID;
}
if (signupEpoch != null && signupEpoch != 0) {
DateTime date = DateTime.fromMillisecondsSinceEpoch(signupEpoch * 1000);
data['SIGNUP_TIMESTAMP'] =
date.toIso8601String() + timeZoneFormatter(date.timeZoneOffset);
}
Uri url = _buildUri('api.php', data);
print(data);
var res = await http.get(url, headers: headers);
if (res.statusCode != 200) {
throw Exception('newOrder exception: statusCode= ${res.statusCode}');
}
final jsonArray = jsonDecode(res.body); // TODO check if valid json
SimplexOrder _order = SimplexOrder(
quote: quote,
paymentId: "${jsonArray['paymentId']}",
orderId: "${jsonArray['orderId']}",
userId: "${jsonArray['userId']}",
);
return BuyResponse(value: _order);
} catch (e, s) {
Logging.instance.log("newOrder exception: $e\n$s", level: LogLevel.Error);
return BuyResponse(
exception: BuyException(
e.toString(),
BuyExceptionType.generic,
),
);
}
}
Future<BuyResponse<bool>> redirect(SimplexOrder order) async {
try {
Map<String, String> headers = {
'Content-Type': 'application/x-www-form-urlencoded',
};
Map<String, String> data = {
'ROUTE': 'redirect',
'PAYMENT_ID': order.paymentId,
};
Uri url = _buildUri('api.php', data);
bool status = await launchUrl(
url,
mode: LaunchMode.externalApplication,
);
return BuyResponse(value: status);
} catch (e, s) {
Logging.instance.log("newOrder exception: $e\n$s", level: LogLevel.Error);
return BuyResponse(
exception: BuyException(
e.toString(),
BuyExceptionType.generic,
));
}
}
bool isSimplexFiat(String ticker) {
try {
fiatFromTickerCaseInsensitive(ticker);
return true;
} on ArgumentError catch (_) {
return false;
}
}
// See https://github.com/dart-lang/sdk/issues/43391#issuecomment-1229656422
String timeZoneFormatter(Duration offset) =>
"${offset.isNegative ? "-" : "+"}${offset.inHours.abs().toString().padLeft(2, "0")}:${(offset.inMinutes - offset.inHours * 60).abs().toString().padLeft(2, "0")}";
}

View file

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

View file

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

View file

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

File diff suppressed because it is too large Load diff

View file

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

View file

@ -220,9 +220,16 @@ abstract class StackColorTheme {
Color get textConfirmTotalAmount;
Color get textSelectedWordTableItem;
// rate type toggle
Color get rateTypeToggleColorOn;
Color get rateTypeToggleColorOff;
Color get rateTypeToggleDesktopColorOn;
Color get rateTypeToggleDesktopColorOff;
BoxShadow get standardBoxShadow;
BoxShadow? get homeViewButtonBarBoxShadow;
}
// 0xFFFFD8CE
class CoinThemeColor {
const CoinThemeColor();

View file

@ -319,6 +319,16 @@ class DarkColors extends StackColorTheme {
@override
Color get textSelectedWordTableItem => const Color(0xFF00297A);
//rate type toggle
@override
Color get rateTypeToggleColorOn => textFieldDefaultBG;
@override
Color get rateTypeToggleColorOff => popupBG;
@override
Color get rateTypeToggleDesktopColorOn => textFieldDefaultBG;
@override
Color get rateTypeToggleDesktopColorOff => buttonBackSecondary;
@override
BoxShadow get standardBoxShadow => BoxShadow(
color: shadow,

View file

@ -319,6 +319,16 @@ class FruitSorbetColors extends StackColorTheme {
@override
Color get textSelectedWordTableItem => const Color(0xFF232323);
//rate type toggle
@override
Color get rateTypeToggleColorOn => const Color(0xFFFFD8CE);
@override
Color get rateTypeToggleColorOff => popupBG;
@override
Color get rateTypeToggleDesktopColorOn => const Color(0xFFFFD8CE);
@override
Color get rateTypeToggleDesktopColorOff => buttonBackSecondary;
@override
BoxShadow get standardBoxShadow => BoxShadow(
color: shadow,

View file

@ -319,6 +319,16 @@ class LightColors extends StackColorTheme {
@override
Color get textSelectedWordTableItem => const Color(0xFF232323);
//rate type toggle
@override
Color get rateTypeToggleColorOn => textFieldDefaultBG;
@override
Color get rateTypeToggleColorOff => popupBG;
@override
Color get rateTypeToggleDesktopColorOn => textFieldDefaultBG;
@override
Color get rateTypeToggleDesktopColorOff => buttonBackSecondary;
@override
BoxShadow get standardBoxShadow => BoxShadow(
color: shadow,

View file

@ -326,6 +326,16 @@ class OceanBreezeColors extends StackColorTheme {
@override
Color get textSelectedWordTableItem => const Color(0xFF232323);
//rate type toggle
@override
Color get rateTypeToggleColorOn => textFieldDefaultBG;
@override
Color get rateTypeToggleColorOff => popupBG;
@override
Color get rateTypeToggleDesktopColorOn => textFieldDefaultBG;
@override
Color get rateTypeToggleDesktopColorOff => buttonBackSecondary;
@override
BoxShadow get standardBoxShadow => BoxShadow(
color: shadow,

View file

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

View file

@ -178,6 +178,12 @@ class StackColors extends ThemeExtension<StackColors> {
final Color textConfirmTotalAmount;
final Color textSelectedWordTableItem;
// rate type toggle
final Color rateTypeToggleColorOn;
final Color rateTypeToggleColorOff;
final Color rateTypeToggleDesktopColorOn;
final Color rateTypeToggleDesktopColorOff;
final BoxShadow standardBoxShadow;
final BoxShadow? homeViewButtonBarBoxShadow;
@ -319,6 +325,10 @@ class StackColors extends ThemeExtension<StackColors> {
required this.myStackContactIconBG,
required this.textConfirmTotalAmount,
required this.textSelectedWordTableItem,
required this.rateTypeToggleColorOn,
required this.rateTypeToggleColorOff,
required this.rateTypeToggleDesktopColorOn,
required this.rateTypeToggleDesktopColorOff,
required this.standardBoxShadow,
required this.homeViewButtonBarBoxShadow,
});
@ -465,6 +475,10 @@ class StackColors extends ThemeExtension<StackColors> {
myStackContactIconBG: colorTheme.myStackContactIconBG,
textConfirmTotalAmount: colorTheme.textConfirmTotalAmount,
textSelectedWordTableItem: colorTheme.textSelectedWordTableItem,
rateTypeToggleColorOn: colorTheme.rateTypeToggleColorOn,
rateTypeToggleColorOff: colorTheme.rateTypeToggleColorOff,
rateTypeToggleDesktopColorOn: colorTheme.rateTypeToggleDesktopColorOn,
rateTypeToggleDesktopColorOff: colorTheme.rateTypeToggleDesktopColorOff,
homeViewButtonBarBoxShadow: colorTheme.homeViewButtonBarBoxShadow,
standardBoxShadow: colorTheme.standardBoxShadow,
);
@ -609,6 +623,10 @@ class StackColors extends ThemeExtension<StackColors> {
Color? myStackContactIconBG,
Color? textConfirmTotalAmount,
Color? textSelectedWordTableItem,
Color? rateTypeToggleColorOn,
Color? rateTypeToggleColorOff,
Color? rateTypeToggleDesktopColorOn,
Color? rateTypeToggleDesktopColorOff,
BoxShadow? homeViewButtonBarBoxShadow,
BoxShadow? standardBoxShadow,
}) {
@ -790,6 +808,14 @@ class StackColors extends ThemeExtension<StackColors> {
textConfirmTotalAmount ?? this.textConfirmTotalAmount,
textSelectedWordTableItem:
textSelectedWordTableItem ?? this.textSelectedWordTableItem,
rateTypeToggleColorOn:
rateTypeToggleColorOn ?? this.rateTypeToggleColorOn,
rateTypeToggleColorOff:
rateTypeToggleColorOff ?? this.rateTypeToggleColorOff,
rateTypeToggleDesktopColorOn:
rateTypeToggleDesktopColorOn ?? this.rateTypeToggleDesktopColorOn,
rateTypeToggleDesktopColorOff:
rateTypeToggleDesktopColorOff ?? this.rateTypeToggleDesktopColorOff,
homeViewButtonBarBoxShadow:
homeViewButtonBarBoxShadow ?? this.homeViewButtonBarBoxShadow,
standardBoxShadow: standardBoxShadow ?? this.standardBoxShadow,
@ -1483,6 +1509,26 @@ class StackColors extends ThemeExtension<StackColors> {
other.textSelectedWordTableItem,
t,
)!,
rateTypeToggleColorOn: Color.lerp(
rateTypeToggleColorOn,
other.rateTypeToggleColorOn,
t,
)!,
rateTypeToggleColorOff: Color.lerp(
rateTypeToggleColorOff,
other.rateTypeToggleColorOff,
t,
)!,
rateTypeToggleDesktopColorOn: Color.lerp(
rateTypeToggleDesktopColorOn,
other.rateTypeToggleDesktopColorOn,
t,
)!,
rateTypeToggleDesktopColorOff: Color.lerp(
rateTypeToggleDesktopColorOff,
other.rateTypeToggleDesktopColorOff,
t,
)!,
);
}

View file

@ -11,7 +11,7 @@ description: Stack Wallet
# In iOS, build-name is used as CFBundleShortVersionString while build-number used as CFBundleVersion.
# Read more about iOS versioning at
# https://developer.apple.com/library/archive/documentation/General/Reference/InfoPlistKeyReference/Articles/CoreFoundationKeys.html
version: 1.5.30+105
version: 1.5.35+110
environment:
sdk: ">=2.17.0 <3.0.0"
@ -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

View file

@ -84,12 +84,12 @@ void main() {
]);
});
test("get hashcode", () {
final adapter = LelantusCoinAdapter();
final result = adapter.hashCode;
expect(result, 9);
});
// test("get hashcode", () {
// final adapter = LelantusCoinAdapter();
//
// final result = adapter.hashCode;
// expect(result, 9);
// });
group("compare operator", () {
test("is equal one", () {

View file

@ -52,12 +52,12 @@ void main() {
]);
});
test("TransactionDataAdapter.hashcode", () {
final adapter = TransactionDataAdapter();
final result = adapter.hashCode;
expect(result, 1);
});
// test("TransactionDataAdapter.hashcode", () {
// final adapter = TransactionDataAdapter();
//
// final result = adapter.hashCode;
// expect(result, 1);
// });
group("TransactionDataAdapter compare operator", () {
test("TransactionDataAdapter is equal one", () {
@ -147,12 +147,12 @@ void main() {
]);
});
test("TransactionChunkAdapter.hashcode", () {
final adapter = TransactionChunkAdapter();
final result = adapter.hashCode;
expect(result, 2);
});
// test("TransactionChunkAdapter.hashcode", () {
// final adapter = TransactionChunkAdapter();
//
// final result = adapter.hashCode;
// expect(result, 2);
// });
group("TransactionChunkAdapter compare operator", () {
test("TransactionChunkAdapter is equal one", () {
@ -377,12 +377,12 @@ void main() {
]);
});
test("TransactionAdapter.hashcode", () {
final adapter = TransactionAdapter();
final result = adapter.hashCode;
expect(result, 3);
});
// test("TransactionAdapter.hashcode", () {
// final adapter = TransactionAdapter();
//
// final result = adapter.hashCode;
// expect(result, 3);
// });
group("TransactionAdapter compare operator", () {
test("TransactionAdapter is equal one", () {
@ -401,7 +401,7 @@ void main() {
expect(result, true);
});
test("TransactionAdapteris not equal one", () {
test("TransactionAdapter is not equal one", () {
final a = TransactionAdapter();
final b = TransactionDataAdapter();
@ -517,12 +517,12 @@ void main() {
]);
});
test("InputAdapter.hashcode", () {
final adapter = InputAdapter();
final result = adapter.hashCode;
expect(result, 4);
});
// test("InputAdapter.hashcode", () {
// final adapter = InputAdapter();
//
// final result = adapter.hashCode;
// expect(result, 4);
// });
group("InputAdapter compare operator", () {
test("InputAdapter is equal one", () {
@ -633,12 +633,12 @@ void main() {
]);
});
test("OutputAdapter.hashcode", () {
final adapter = OutputAdapter();
final result = adapter.hashCode;
expect(result, 5);
});
// test("OutputAdapter.hashcode", () {
// final adapter = OutputAdapter();
//
// final result = adapter.hashCode;
// expect(result, 5);
// });
group("OutputAdapter compare operator", () {
test("OutputAdapter is equal one", () {

View file

@ -87,12 +87,12 @@ void main() {
]);
});
test("UtxoDataAdapter.hashcode", () {
final adapter = UtxoDataAdapter();
final result = adapter.hashCode;
expect(result, 6);
});
// test("UtxoDataAdapter.hashcode", () {
// final adapter = UtxoDataAdapter();
//
// final result = adapter.hashCode;
// expect(result, 6);
// });
group("UtxoDataAdapter compare operator", () {
test("UtxoDataAdapter is equal one", () {
@ -238,12 +238,12 @@ void main() {
]);
});
test("UtxoObjectAdapter.hashcode", () {
final adapter = UtxoObjectAdapter();
final result = adapter.hashCode;
expect(result, 7);
});
// test("UtxoObjectAdapter.hashcode", () {
// final adapter = UtxoObjectAdapter();
//
// final result = adapter.hashCode;
// expect(result, 7);
// });
group("UtxoObjectAdapter compare operator", () {
test("UtxoObjectAdapter is equal one", () {
@ -359,12 +359,12 @@ void main() {
]);
});
test("StatusAdapter.hashcode", () {
final adapter = StatusAdapter();
final result = adapter.hashCode;
expect(result, 8);
});
// test("StatusAdapter.hashcode", () {
// final adapter = StatusAdapter();
//
// final result = adapter.hashCode;
// expect(result, 8);
// });
group("StatusAdapter compare operator", () {
test("StatusAdapter is equal one", () {