Merge pull request #323 from cypherstack/simplex

Buy: validation, error handling, wallet view and color fixes, and remove non-Stack coins from buy list
This commit is contained in:
Diego Salazar 2023-01-27 17:25:37 -07:00 committed by GitHub
commit 12ce06838e
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
23 changed files with 477 additions and 222 deletions

View file

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

Before

Width:  |  Height:  |  Size: 528 B

View file

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

Before

Width:  |  Height:  |  Size: 2.1 KiB

View file

@ -1,10 +0,0 @@
<?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>

Before

Width:  |  Height:  |  Size: 2.7 KiB

View file

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

Before

Width:  |  Height:  |  Size: 621 B

View file

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

Before

Width:  |  Height:  |  Size: 322 B

View file

@ -1,18 +0,0 @@
<?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>

Before

Width:  |  Height:  |  Size: 1.3 KiB

View file

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

Before

Width:  |  Height:  |  Size: 472 B

View file

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

Before

Width:  |  Height:  |  Size: 705 B

View file

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

Before

Width:  |  Height:  |  Size: 874 B

View file

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

Before

Width:  |  Height:  |  Size: 651 B

View file

@ -17,6 +17,7 @@ import 'package:stackwallet/pages/buy_view/sub_widgets/fiat_selection_view.dart'
import 'package:stackwallet/pages/exchange_view/choose_from_stack_view.dart';
import 'package:stackwallet/pages_desktop_specific/my_stack_view/wallet_view/sub_widgets/address_book_address_chooser/address_book_address_chooser.dart';
import 'package:stackwallet/providers/providers.dart';
import 'package:stackwallet/services/buy/buy_response.dart';
import 'package:stackwallet/services/buy/simplex/simplex_api.dart';
import 'package:stackwallet/utilities/address_utils.dart';
import 'package:stackwallet/utilities/assets.dart';
@ -47,10 +48,13 @@ import 'package:stackwallet/widgets/textfield_icon_button.dart';
class BuyForm extends ConsumerStatefulWidget {
const BuyForm({
Key? key,
this.coin,
this.clipboard = const ClipboardWrapper(),
this.scanner = const BarcodeScannerWrapper(),
}) : super(key: key);
final Coin? coin;
final ClipboardInterface clipboard;
final BarcodeScannerInterface scanner;
@ -59,6 +63,8 @@ class BuyForm extends ConsumerStatefulWidget {
}
class _BuyFormState extends ConsumerState<BuyForm> {
late final Coin? coin;
late final ClipboardInterface clipboard;
late final BarcodeScannerInterface scanner;
@ -75,8 +81,8 @@ class _BuyFormState extends ConsumerState<BuyForm> {
List<Fiat>? fiats;
String? _address;
Fiat? selectedFiat;
Crypto? selectedCrypto;
static Fiat? selectedFiat;
static Crypto? selectedCrypto;
SimplexQuote quote = SimplexQuote(
crypto: Crypto.fromJson({'ticker': 'BTC', 'name': 'Bitcoin'}),
fiat: Fiat.fromJson({'ticker': 'USD', 'name': 'United States Dollar'}),
@ -87,13 +93,20 @@ class _BuyFormState extends ConsumerState<BuyForm> {
buyWithFiat: true,
); // TODO enum this or something
bool buyWithFiat = true;
static bool buyWithFiat = true;
bool _addressToggleFlag = false;
bool _hovering1 = false;
bool _hovering2 = false;
Decimal minFiat = Decimal.fromInt(50);
Decimal maxFiat = Decimal.fromInt(20000);
// TODO actually check USD min and max, these could get updated by Simplex
static Decimal minFiat = Decimal.fromInt(50);
static Decimal maxFiat = Decimal.fromInt(20000);
// We can't get crypto min and max without asking for a quote
static Decimal minCrypto = Decimal.parse((0.00000001)
.toString()); // lol how to go from double->Decimal more easily?
static Decimal maxCrypto = Decimal.parse((10000.00000000).toString());
static String boundedCryptoTicker = '';
void fiatFieldOnChanged(String value) async {}
@ -125,6 +138,13 @@ class _BuyFormState extends ConsumerState<BuyForm> {
coins: ref.read(simplexProvider).supportedCryptos,
onSelected: (crypto) {
setState(() {
if (selectedCrypto?.ticker != _BuyFormState.boundedCryptoTicker) {
// Reset crypto mins and maxes ... we don't know these bounds until we request a quote
_BuyFormState.minCrypto = Decimal.parse((0.00000001)
.toString()); // lol how to go from double->Decimal more easily?
_BuyFormState.maxCrypto =
Decimal.parse((10000.00000000).toString());
}
selectedCrypto = crypto;
});
},
@ -357,9 +377,10 @@ class _BuyFormState extends ConsumerState<BuyForm> {
}
Widget? getIconForTicker(String ticker) {
String? iconAsset = isStackCoin(ticker)
? Assets.svg.iconFor(coin: coinFromTickerCaseInsensitive(ticker))
: Assets.svg.buyIconFor(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;
@ -394,22 +415,111 @@ class _BuyFormState extends ConsumerState<BuyForm> {
buyWithFiat: buyWithFiat,
);
await _loadQuote(quote);
BuyResponse<SimplexQuote> quoteResponse = await _loadQuote(quote);
shouldPop = true;
if (mounted) {
Navigator.of(context, rootNavigator: isDesktop).pop();
}
quote = ref.read(simplexProvider).quote;
if (quoteResponse.exception == null) {
quote = quoteResponse.value as SimplexQuote;
if (quote.id != 'id' && quote.id != 'someID') {
// TODO detect default quote better
await _showFloatingBuyQuotePreviewSheet(
quote: ref.read(simplexProvider).quote,
onSelected: (quote) {
// TODO launch URL
},
);
if (quote.id != 'id' && quote.id != 'someID') {
// TODO detect default quote better
await _showFloatingBuyQuotePreviewSheet(
quote: ref.read(simplexProvider).quote,
onSelected: (quote) {
// TODO launch URL
},
);
} else {
await showDialog<dynamic>(
context: context,
barrierDismissible: true,
builder: (context) {
if (isDesktop) {
return DesktopDialog(
maxWidth: 450,
child: Padding(
padding: const EdgeInsets.all(32),
child: Column(
mainAxisSize: MainAxisSize.min,
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
"Simplex API unresponsive",
style: STextStyles.desktopH3(context),
),
const SizedBox(
height: 24,
),
Text(
"Simplex API unresponsive, please try again later",
style: STextStyles.smallMed14(context),
),
const SizedBox(
height: 56,
),
Row(
children: [
const Spacer(),
Expanded(
child: PrimaryButton(
buttonHeight: ButtonHeight.l,
label: "Ok",
onPressed: Navigator.of(context).pop,
),
),
],
)
],
),
),
);
} else {
return StackDialog(
title: "Simplex API error",
message: "${quoteResponse.exception?.errorMessage}",
rightButton: TextButton(
style: Theme.of(context)
.extension<StackColors>()!
.getSecondaryEnabledButtonStyle(context),
child: Text(
"Ok",
style: STextStyles.button(context).copyWith(
color: Theme.of(context)
.extension<StackColors>()!
.accentColorDark),
),
onPressed: () {
Navigator.of(context).pop();
},
),
);
}
},
);
}
} else {
// Error; probably amount out of bounds
String errorMessage = "${quoteResponse.exception?.errorMessage}";
if (errorMessage.contains('must be between')) {
errorMessage = errorMessage.substring(
(errorMessage.indexOf('getQuote exception: ') ?? 19) + 20,
errorMessage.indexOf(", value: null"));
_BuyFormState.boundedCryptoTicker = errorMessage.substring(
errorMessage.indexOf('The ') + 4,
errorMessage.indexOf(' amount must be between'));
_BuyFormState.minCrypto = Decimal.parse(errorMessage.substring(
errorMessage.indexOf('must be between ') + 16,
errorMessage.indexOf(' and ')));
_BuyFormState.maxCrypto = Decimal.parse(errorMessage.substring(
errorMessage.indexOf("$minCrypto and ") + "$minCrypto and ".length,
errorMessage.length));
if (Decimal.parse(_buyAmountController.text) >
_BuyFormState.maxCrypto) {
_buyAmountController.text = _BuyFormState.maxCrypto.toString();
}
}
await showDialog<dynamic>(
context: context,
barrierDismissible: true,
@ -424,14 +534,14 @@ class _BuyFormState extends ConsumerState<BuyForm> {
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
"Simplex API unresponsive",
"Simplex API error",
style: STextStyles.desktopH3(context),
),
const SizedBox(
height: 24,
),
Text(
"Simplex API unresponsive, please try again later",
errorMessage,
style: STextStyles.smallMed14(context),
),
const SizedBox(
@ -455,9 +565,9 @@ class _BuyFormState extends ConsumerState<BuyForm> {
);
} else {
return StackDialog(
title: "Simplex API unresponsive",
message:
"Unexpected response from Simplex API, please try again later",
title: "Simplex API error",
message: "${quoteResponse.exception?.errorMessage}",
// "${quoteResponse.exception?.errorMessage.substring(8, (quoteResponse.exception?.errorMessage?.length ?? 109) - (8 + 6))}",
rightButton: TextButton(
style: Theme.of(context)
.extension<StackColors>()!
@ -480,16 +590,24 @@ class _BuyFormState extends ConsumerState<BuyForm> {
}
}
Future<void> _loadQuote(SimplexQuote quote) async {
Future<BuyResponse<SimplexQuote>> _loadQuote(SimplexQuote quote) async {
final response = await SimplexAPI.instance.getQuote(quote);
if (response.value != null) {
// TODO check for error key
ref.read(simplexProvider).updateQuote(response.value!);
return BuyResponse(value: response.value!);
} else {
Logging.instance.log(
"_loadQuote: $response",
level: LogLevel.Warning,
);
return BuyResponse(
exception: BuyException(
response.toString(),
BuyExceptionType.generic,
),
);
}
}
@ -578,10 +696,8 @@ class _BuyFormState extends ConsumerState<BuyForm> {
// quote = ref.read(simplexProvider).quote;
quote = SimplexQuote(
crypto:
Crypto.fromJson({'ticker': 'BTC', 'name': 'Bitcoin', 'image': ''}),
fiat: Fiat.fromJson(
{'ticker': 'USD', 'name': 'United States Dollar', 'image': ''}),
crypto: Crypto.fromJson({'ticker': 'BTC', 'name': 'Bitcoin'}),
fiat: Fiat.fromJson({'ticker': 'USD', 'name': 'United States Dollar'}),
youPayFiatPrice: Decimal.parse("100"),
youReceiveCryptoAmount: Decimal.parse("1.0238917"),
id: "someID",
@ -590,10 +706,12 @@ class _BuyFormState extends ConsumerState<BuyForm> {
); // TODO enum this or something
// TODO set defaults better; should probably explicitly enumerate the coins & fiats used and pull the specific ones we need rather than generating them as defaults here
selectedFiat = Fiat.fromJson(
{'ticker': 'USD', 'name': 'United States Dollar', 'image': ''});
selectedCrypto =
Crypto.fromJson({'ticker': 'BTC', 'name': 'Bitcoin', 'image': ''});
selectedFiat =
Fiat.fromJson({'ticker': 'USD', 'name': 'United States Dollar'});
selectedCrypto = Crypto.fromJson({
'ticker': widget.coin?.ticker ?? 'BTC',
'name': widget.coin?.prettyName ?? 'Bitcoin'
});
// TODO set initial crypto to open wallet if a wallet is open
@ -660,7 +778,7 @@ class _BuyFormState extends ConsumerState<BuyForm> {
color: _hovering1
? Theme.of(context)
.extension<StackColors>()!
.highlight
.currencyListItemBG
.withOpacity(_hovering1 ? 0.3 : 0)
: Theme.of(context)
.extension<StackColors>()!
@ -727,7 +845,7 @@ class _BuyFormState extends ConsumerState<BuyForm> {
color: _hovering2
? Theme.of(context)
.extension<StackColors>()!
.highlight
.currencyListItemBG
.withOpacity(_hovering2 ? 0.3 : 0)
: Theme.of(context)
.extension<StackColors>()!
@ -743,7 +861,7 @@ class _BuyFormState extends ConsumerState<BuyForm> {
decoration: BoxDecoration(
color: Theme.of(context)
.extension<StackColors>()!
.highlight,
.currencyListItemBG,
borderRadius: BorderRadius.circular(4),
),
child: Text(
@ -820,7 +938,10 @@ class _BuyFormState extends ConsumerState<BuyForm> {
color: Theme.of(context).extension<StackColors>()!.textDark,
),
key: const Key("buyAmountInputFieldTextFieldKey"),
controller: _buyAmountController,
controller: _buyAmountController
..text = _BuyFormState.buyWithFiat
? _BuyFormState.minFiat.toStringAsFixed(2) ?? '50.00'
: _BuyFormState.minCrypto.toStringAsFixed(8),
focusNode: _buyAmountFocusNode,
keyboardType: Util.isDesktop
? null
@ -829,12 +950,7 @@ class _BuyFormState extends ConsumerState<BuyForm> {
decimal: true,
),
textAlign: TextAlign.left,
inputFormatters: [
// regex to validate a crypto amount with 8 decimal places or
// 2 if fiat
NumericalRangeFormatter(
min: minFiat, max: maxFiat, buyWithFiat: buyWithFiat)
],
inputFormatters: [NumericalRangeFormatter()],
decoration: InputDecoration(
contentPadding: const EdgeInsets.only(
// top: 22,
@ -864,7 +980,7 @@ class _BuyFormState extends ConsumerState<BuyForm> {
decoration: BoxDecoration(
color: Theme.of(context)
.extension<StackColors>()!
.highlight,
.currencyListItemBG,
borderRadius: BorderRadius.circular(4),
),
child: Text(
@ -905,11 +1021,20 @@ class _BuyFormState extends ConsumerState<BuyForm> {
_buyAmountController.text.isNotEmpty
? TextFieldIconButton(
key: const Key(
"buyViewClearAddressFieldButtonKey"),
"buyViewClearAmountFieldButtonKey"),
onTap: () {
_buyAmountController.text = "";
// _receiveAddress = "";
setState(() {});
if (_BuyFormState.buyWithFiat) {
_buyAmountController.text = _BuyFormState
.minFiat
.toStringAsFixed(2);
} else {
if (selectedCrypto?.ticker ==
_BuyFormState.boundedCryptoTicker) {
_buyAmountController.text = _BuyFormState
.minCrypto
.toStringAsFixed(8);
}
}
},
child: const XIcon(),
)
@ -1248,36 +1373,49 @@ class _BuyFormState extends ConsumerState<BuyForm> {
// See https://stackoverflow.com/a/68072967
class NumericalRangeFormatter extends TextInputFormatter {
final Decimal min;
final Decimal max;
final bool buyWithFiat;
NumericalRangeFormatter(
{required this.min, required this.max, required this.buyWithFiat});
NumericalRangeFormatter();
@override
TextEditingValue formatEditUpdate(
TextEditingValue oldValue,
TextEditingValue newValue,
) {
TextSelection newSelection = newValue.selection;
String newVal = _BuyFormState.buyWithFiat
? Decimal.parse(newValue.text).toStringAsFixed(2)
: Decimal.parse(newValue.text).toStringAsFixed(8);
if (newValue.text == '') {
return newValue;
} else {
if (buyWithFiat) {
if (Decimal.parse(newValue.text) < min) {
newValue =
const TextEditingValue().copyWith(text: min.toStringAsFixed(2));
} else {
newValue = Decimal.parse(newValue.text) > max ? oldValue : newValue;
if (_BuyFormState.buyWithFiat) {
if (Decimal.parse(newValue.text) < _BuyFormState.minFiat) {
newVal = _BuyFormState.minFiat.toStringAsFixed(2);
// _BuyFormState._buyAmountController.selection =
// TextSelection.collapsed(
// offset: _BuyFormState.buyWithFiat
// ? _BuyFormState._buyAmountController.text.length - 2
// : _BuyFormState._buyAmountController.text.length - 8);
} else if (Decimal.parse(newValue.text) > _BuyFormState.maxFiat) {
newVal = _BuyFormState.maxFiat.toStringAsFixed(2);
}
} else if (!_BuyFormState.buyWithFiat &&
_BuyFormState.selectedCrypto?.ticker ==
_BuyFormState.boundedCryptoTicker) {
if (Decimal.parse(newValue.text) < _BuyFormState.minCrypto) {
newVal = _BuyFormState.minCrypto.toStringAsFixed(8);
} else if (Decimal.parse(newValue.text) > _BuyFormState.maxCrypto) {
newVal = _BuyFormState.maxCrypto.toStringAsFixed(8);
}
}
}
final regexString = buyWithFiat
final regexString = _BuyFormState.buyWithFiat
? r'^([0-9]*[,.]?[0-9]{0,2}|[,.][0-9]{0,2})$'
: r'^([0-9]*[,.]?[0-9]{0,8}|[,.][0-9]{0,8})$';
// return RegExp(r'^([0-9]*[,.]?[0-9]{0,8}|[,.][0-9]{0,8})$')
return RegExp(regexString).hasMatch(newValue.text) ? newValue : oldValue;
return RegExp(regexString).hasMatch(newVal)
? TextEditingValue(text: newVal, selection: newSelection)
: oldValue;
}
}

View file

@ -0,0 +1,48 @@
import 'package:flutter/material.dart';
import 'package:stackwallet/pages/buy_view/buy_view.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/widgets/background.dart';
import 'package:stackwallet/widgets/custom_buttons/app_bar_icon_button.dart';
class BuyInWalletView extends StatefulWidget {
const BuyInWalletView({
Key? key,
required this.coin,
}) : super(key: key);
static const String routeName = "/stackBuyInWalletView";
final Coin? coin;
@override
State<BuyInWalletView> createState() => _BuyInWalletViewState();
}
class _BuyInWalletViewState extends State<BuyInWalletView> {
late final Coin? coin;
@override
Widget build(BuildContext context) {
debugPrint("BUILD: $runtimeType");
return Background(
child: Scaffold(
backgroundColor: Theme.of(context).extension<StackColors>()!.background,
appBar: AppBar(
leading: AppBarBackButton(
onPressed: () {
Navigator.of(context).pop();
},
),
title: Text(
"Buy ${widget.coin?.ticker}",
style: STextStyles.navBarTitle(context),
),
),
body: BuyView(coin: widget.coin),
),
);
}
}

View file

@ -1,28 +1,36 @@
import 'package:flutter/material.dart';
import 'package:stackwallet/pages/buy_view/buy_form.dart';
import 'package:stackwallet/utilities/enums/coin_enum.dart';
class BuyView extends StatefulWidget {
const BuyView({Key? key}) : super(key: key);
const BuyView({
Key? key,
this.coin,
}) : super(key: key);
static const String routeName = "/stackBuyView";
final Coin? coin;
@override
State<BuyView> createState() => _BuyViewState();
}
class _BuyViewState extends State<BuyView> {
late final Coin? coin;
@override
Widget build(BuildContext context) {
debugPrint("BUILD: $runtimeType");
return const SafeArea(
return SafeArea(
child: Padding(
padding: EdgeInsets.only(
padding: const EdgeInsets.only(
left: 16,
right: 16,
top: 16,
),
child: BuyForm(),
child: BuyForm(coin: widget.coin),
),
);
}

View file

@ -29,18 +29,9 @@ class BuyWarningPopup extends StatelessWidget {
SimplexOrder? order;
Future<BuyResponse<SimplexOrder>> newOrder(SimplexQuote quote) async {
final response = await SimplexAPI.instance.newOrder(quote);
final orderResponse = 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;
return orderResponse;
}
Future<BuyResponse<bool>> redirect(SimplexOrder order) async {
@ -122,13 +113,90 @@ class BuyWarningPopup extends StatelessWidget {
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();
});
BuyResponse<SimplexOrder> orderResponse = await newOrder(quote);
if (orderResponse.exception == null) {
await redirect(orderResponse.value as SimplexOrder)
.then((_response) async {
this.order = orderResponse.value as SimplexOrder;
Navigator.of(context, rootNavigator: isDesktop).pop();
Navigator.of(context, rootNavigator: isDesktop).pop();
await _buyInvoice();
});
} else {
await showDialog<dynamic>(
context: context,
barrierDismissible: true,
builder: (context) {
if (isDesktop) {
return DesktopDialog(
maxWidth: 450,
child: Padding(
padding: const EdgeInsets.all(32),
child: Column(
mainAxisSize: MainAxisSize.min,
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
"Simplex API error",
style: STextStyles.desktopH3(context),
),
const SizedBox(
height: 24,
),
Text(
"${orderResponse.exception?.errorMessage}",
style: STextStyles.smallMed14(context),
),
const SizedBox(
height: 56,
),
Row(
children: [
const Spacer(),
Expanded(
child: PrimaryButton(
buttonHeight: ButtonHeight.l,
label: "Ok",
onPressed: () {
Navigator.of(context).pop();
Navigator.of(context).pop();
Navigator.of(context).pop(); // weee
},
),
),
],
)
],
),
),
);
} else {
return StackDialog(
title: "Simplex API error",
message: "${orderResponse.exception?.errorMessage}",
// "${quoteResponse.exception?.errorMessage.substring(8, (quoteResponse.exception?.errorMessage?.length ?? 109) - (8 + 6))}",
rightButton: TextButton(
style: Theme.of(context)
.extension<StackColors>()!
.getSecondaryEnabledButtonStyle(context),
child: Text(
"Ok",
style: STextStyles.button(context).copyWith(
color: Theme.of(context)
.extension<StackColors>()!
.accentColorDark),
),
onPressed: () {
Navigator.of(context).pop();
Navigator.of(context).pop();
Navigator.of(context).pop(); // weee
},
),
);
}
},
);
}
},
),
icon: SizedBox(

View file

@ -253,9 +253,10 @@ bool isStackCoin(String? ticker) {
}
Widget? getIconForTicker(String ticker) {
String? iconAsset = isStackCoin(ticker)
? Assets.svg.iconFor(coin: coinFromTickerCaseInsensitive(ticker))
: Assets.svg.buyIconFor(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

@ -207,7 +207,7 @@ class _FiatSelectionViewState extends State<FiatSelectionView> {
decoration: BoxDecoration(
color: Theme.of(context)
.extension<StackColors>()!
.highlight,
.currencyListItemBG,
borderRadius: BorderRadius.circular(4),
),
child: Text(

View file

@ -412,47 +412,49 @@ class _WalletNavigationBarState extends State<WalletNavigationBar> {
const SizedBox(
width: 12,
),
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,
if (widget.coin.hasBuySupport)
RawMaterialButton(
constraints: const BoxConstraints(
minWidth: 66,
),
),
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(),
],
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.buy(context),
width: 24,
height: 24,
),
const SizedBox(
height: 4,
),
Text(
"Buy",
style: STextStyles.buttonSmall(context),
),
const Spacer(),
],
),
),
),
),
),
const SizedBox(
width: 12,
),
if (widget.coin.hasBuySupport)
const SizedBox(
width: 12,
),
],
),
),

View file

@ -6,6 +6,7 @@ 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/buy_view/buy_in_wallet_view.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/home_view/home_view.dart';
@ -772,7 +773,15 @@ class _WalletViewState extends ConsumerState<WalletView> {
),
);
},
onBuyPressed: () {},
onBuyPressed: () {
// TODO set default coin to currently open wallet here by passing it as an argument
// final coin = ref.read(managerProvider).coin;
unawaited(Navigator.of(context).pushNamed(
BuyInWalletView.routeName,
arguments: coin,
));
},
),
),
],

View file

@ -22,7 +22,9 @@ 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_in_wallet_view.dart';
import 'package:stackwallet/pages/buy_view/buy_quote_preview.dart';
import 'package:stackwallet/pages/buy_view/buy_view.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';
@ -1125,6 +1127,24 @@ class RouteGenerator {
builder: (_) => const DesktopExchangeView(),
settings: RouteSettings(name: settings.name));
case BuyView.routeName:
return getRoute(
shouldUseMaterialRoute: useMaterialPageRoute,
builder: (_) => const BuyView(),
settings: RouteSettings(name: settings.name));
case BuyInWalletView.routeName:
if (args is Coin) {
return getRoute(
shouldUseMaterialRoute: useMaterialPageRoute,
builder: (_) => BuyInWalletView(coin: args),
settings: RouteSettings(
name: settings.name,
),
);
}
return _routeError("${settings.name} invalid args: ${args.toString()}");
case DesktopBuyView.routeName:
return getRoute(
shouldUseMaterialRoute: useMaterialPageRoute,

View file

@ -8,6 +8,7 @@ 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/coin_enum.dart';
import 'package:stackwallet/utilities/enums/fiat_enum.dart';
import 'package:stackwallet/utilities/logger.dart';
import 'package:stackwallet/utilities/prefs.dart';
@ -15,7 +16,7 @@ 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 authority = "localhost"; // For development purposes
static const String scheme = authority == "localhost" ? "http" : "https";
final _prefs = Prefs.instance;
@ -71,13 +72,15 @@ class SimplexAPI {
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': "",
}));
if (isStackCoin("${crypto['ticker_symbol']}")) {
cryptos.add(Crypto.fromJson({
'ticker': "${crypto['ticker_symbol']}",
'name': crypto['name'],
'network': "${crypto['network']}",
'contractAddress': "${crypto['contractAddress']}",
'image': "",
}));
}
}
return BuyResponse(value: cryptos);
@ -184,6 +187,12 @@ class SimplexAPI {
throw Exception('getQuote exception: statusCode= ${res.statusCode}');
}
final jsonArray = jsonDecode(res.body);
if (jsonArray.containsKey('error') as bool) {
if (jsonArray['error'] == true || jsonArray['error'] == 'true') {
// jsonArray['error'] as bool == true?
throw Exception('getQuote exception: ${jsonArray['error']}');
}
}
jsonArray['quote'] = quote; // Add and pass this on
@ -261,13 +270,17 @@ class SimplexAPI {
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
if (jsonArray.containsKey('error') as bool) {
if (jsonArray['error'] == true || jsonArray['error'] == 'true') {
throw Exception(jsonArray['message']);
}
}
SimplexOrder _order = SimplexOrder(
quote: quote,
@ -328,3 +341,14 @@ class SimplexAPI {
String timeZoneFormatter(Duration offset) =>
"${offset.isNegative ? "-" : "+"}${offset.inHours.abs().toString().padLeft(2, "0")}:${(offset.inMinutes - offset.inHours * 60).abs().toString().padLeft(2, "0")}";
}
bool isStackCoin(String? ticker) {
if (ticker == null) return false;
try {
coinFromTickerCaseInsensitive(ticker);
return true;
} on ArgumentError catch (_) {
return false;
}
}

View file

@ -32,9 +32,8 @@ 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 buy(BuildContext context) =>
"assets/svg/${Theme.of(context).extension<StackColors>()!.themeType.name}/buy-coins-icon.svg";
String simplexLogo(BuildContext context) {
return (Theme.of(context).extension<StackColors>()!.themeType ==
@ -210,17 +209,6 @@ 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";
@ -268,33 +256,6 @@ 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

@ -195,6 +195,29 @@ extension CoinExt on Coin {
}
}
bool get hasBuySupport {
switch (this) {
case Coin.bitcoin:
case Coin.litecoin:
case Coin.bitcoincash:
case Coin.dogecoin:
return true;
case Coin.firo:
case Coin.namecoin:
case Coin.particl:
case Coin.epicCash:
case Coin.monero:
case Coin.wownero:
case Coin.dogecoinTestNet:
case Coin.bitcoinTestNet:
case Coin.litecoinTestNet:
case Coin.bitcoincashTestnet:
case Coin.firoTestNet:
return false;
}
}
int get requiredConfirmations {
switch (this) {
case Coin.bitcoin:

View file

@ -320,17 +320,6 @@ 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