diff --git a/assets/svg/coin_icons/BinanceUSD.svg b/assets/svg/coin_icons/BinanceUSD.svg
deleted file mode 100644
index d2a374781..000000000
--- a/assets/svg/coin_icons/BinanceUSD.svg
+++ /dev/null
@@ -1 +0,0 @@
-
\ No newline at end of file
diff --git a/assets/svg/coin_icons/Cosmos.svg b/assets/svg/coin_icons/Cosmos.svg
deleted file mode 100644
index a97174439..000000000
--- a/assets/svg/coin_icons/Cosmos.svg
+++ /dev/null
@@ -1 +0,0 @@
-
\ No newline at end of file
diff --git a/assets/svg/coin_icons/Dai.svg b/assets/svg/coin_icons/Dai.svg
deleted file mode 100644
index 75de37346..000000000
--- a/assets/svg/coin_icons/Dai.svg
+++ /dev/null
@@ -1,10 +0,0 @@
-
-
-
-
\ No newline at end of file
diff --git a/assets/svg/coin_icons/Dash.svg b/assets/svg/coin_icons/Dash.svg
deleted file mode 100644
index 140ab6704..000000000
--- a/assets/svg/coin_icons/Dash.svg
+++ /dev/null
@@ -1 +0,0 @@
-
\ No newline at end of file
diff --git a/assets/svg/coin_icons/EOS.svg b/assets/svg/coin_icons/EOS.svg
deleted file mode 100644
index df772834f..000000000
--- a/assets/svg/coin_icons/EOS.svg
+++ /dev/null
@@ -1 +0,0 @@
-
\ No newline at end of file
diff --git a/assets/svg/coin_icons/Ethereum.svg b/assets/svg/coin_icons/Ethereum.svg
deleted file mode 100644
index 7ffd694cc..000000000
--- a/assets/svg/coin_icons/Ethereum.svg
+++ /dev/null
@@ -1,18 +0,0 @@
-
-
-
-
\ No newline at end of file
diff --git a/assets/svg/coin_icons/Ripple.svg b/assets/svg/coin_icons/Ripple.svg
deleted file mode 100644
index 9a2c7c632..000000000
--- a/assets/svg/coin_icons/Ripple.svg
+++ /dev/null
@@ -1 +0,0 @@
-
\ No newline at end of file
diff --git a/assets/svg/coin_icons/Stellar.svg b/assets/svg/coin_icons/Stellar.svg
deleted file mode 100644
index 02afb7b79..000000000
--- a/assets/svg/coin_icons/Stellar.svg
+++ /dev/null
@@ -1 +0,0 @@
-
\ No newline at end of file
diff --git a/assets/svg/coin_icons/Tether.svg b/assets/svg/coin_icons/Tether.svg
deleted file mode 100644
index e53082240..000000000
--- a/assets/svg/coin_icons/Tether.svg
+++ /dev/null
@@ -1 +0,0 @@
-
\ No newline at end of file
diff --git a/assets/svg/coin_icons/Tron.svg b/assets/svg/coin_icons/Tron.svg
deleted file mode 100644
index fa87a1d7e..000000000
--- a/assets/svg/coin_icons/Tron.svg
+++ /dev/null
@@ -1 +0,0 @@
-
\ No newline at end of file
diff --git a/lib/pages/buy_view/buy_form.dart b/lib/pages/buy_view/buy_form.dart
index c55226d6d..029bc9aa4 100644
--- a/lib/pages/buy_view/buy_form.dart
+++ b/lib/pages/buy_view/buy_form.dart
@@ -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 {
+ late final Coin? coin;
+
late final ClipboardInterface clipboard;
late final BarcodeScannerInterface scanner;
@@ -75,8 +81,8 @@ class _BuyFormState extends ConsumerState {
List? 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 {
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 {
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 {
}
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 {
buyWithFiat: buyWithFiat,
);
- await _loadQuote(quote);
+ BuyResponse 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(
+ 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()!
+ .getSecondaryEnabledButtonStyle(context),
+ child: Text(
+ "Ok",
+ style: STextStyles.button(context).copyWith(
+ color: Theme.of(context)
+ .extension()!
+ .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(
context: context,
barrierDismissible: true,
@@ -424,14 +534,14 @@ class _BuyFormState extends ConsumerState {
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 {
);
} 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()!
@@ -480,16 +590,24 @@ class _BuyFormState extends ConsumerState {
}
}
- Future _loadQuote(SimplexQuote quote) async {
+ Future> _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 {
// 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 {
); // 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 {
color: _hovering1
? Theme.of(context)
.extension()!
- .highlight
+ .currencyListItemBG
.withOpacity(_hovering1 ? 0.3 : 0)
: Theme.of(context)
.extension()!
@@ -727,7 +845,7 @@ class _BuyFormState extends ConsumerState {
color: _hovering2
? Theme.of(context)
.extension()!
- .highlight
+ .currencyListItemBG
.withOpacity(_hovering2 ? 0.3 : 0)
: Theme.of(context)
.extension()!
@@ -743,7 +861,7 @@ class _BuyFormState extends ConsumerState {
decoration: BoxDecoration(
color: Theme.of(context)
.extension()!
- .highlight,
+ .currencyListItemBG,
borderRadius: BorderRadius.circular(4),
),
child: Text(
@@ -820,7 +938,10 @@ class _BuyFormState extends ConsumerState {
color: Theme.of(context).extension()!.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 {
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 {
decoration: BoxDecoration(
color: Theme.of(context)
.extension()!
- .highlight,
+ .currencyListItemBG,
borderRadius: BorderRadius.circular(4),
),
child: Text(
@@ -905,11 +1021,20 @@ class _BuyFormState extends ConsumerState {
_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 {
// 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;
}
}
diff --git a/lib/pages/buy_view/buy_in_wallet_view.dart b/lib/pages/buy_view/buy_in_wallet_view.dart
new file mode 100644
index 000000000..09cbb6857
--- /dev/null
+++ b/lib/pages/buy_view/buy_in_wallet_view.dart
@@ -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 createState() => _BuyInWalletViewState();
+}
+
+class _BuyInWalletViewState extends State {
+ late final Coin? coin;
+
+ @override
+ Widget build(BuildContext context) {
+ debugPrint("BUILD: $runtimeType");
+
+ return Background(
+ child: Scaffold(
+ backgroundColor: Theme.of(context).extension()!.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),
+ ),
+ );
+ }
+}
diff --git a/lib/pages/buy_view/buy_view.dart b/lib/pages/buy_view/buy_view.dart
index 0eb44e87e..dca907b59 100644
--- a/lib/pages/buy_view/buy_view.dart
+++ b/lib/pages/buy_view/buy_view.dart
@@ -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 createState() => _BuyViewState();
}
class _BuyViewState extends State {
+ 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),
),
);
}
diff --git a/lib/pages/buy_view/sub_widgets/buy_warning_popup.dart b/lib/pages/buy_view/sub_widgets/buy_warning_popup.dart
index 082a19306..c98d53580 100644
--- a/lib/pages/buy_view/sub_widgets/buy_warning_popup.dart
+++ b/lib/pages/buy_view/sub_widgets/buy_warning_popup.dart
@@ -29,18 +29,9 @@ class BuyWarningPopup extends StatelessWidget {
SimplexOrder? order;
Future> 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> redirect(SimplexOrder order) async {
@@ -122,13 +113,90 @@ class BuyWarningPopup extends StatelessWidget {
rightButton: PrimaryButton(
label: "Continue",
onPressed: () async {
- BuyResponse 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 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(
+ 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()!
+ .getSecondaryEnabledButtonStyle(context),
+ child: Text(
+ "Ok",
+ style: STextStyles.button(context).copyWith(
+ color: Theme.of(context)
+ .extension()!
+ .accentColorDark),
+ ),
+ onPressed: () {
+ Navigator.of(context).pop();
+ Navigator.of(context).pop();
+ Navigator.of(context).pop(); // weee
+ },
+ ),
+ );
+ }
+ },
+ );
+ }
},
),
icon: SizedBox(
diff --git a/lib/pages/buy_view/sub_widgets/crypto_selection_view.dart b/lib/pages/buy_view/sub_widgets/crypto_selection_view.dart
index 2d0fe3095..10963df18 100644
--- a/lib/pages/buy_view/sub_widgets/crypto_selection_view.dart
+++ b/lib/pages/buy_view/sub_widgets/crypto_selection_view.dart
@@ -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;
diff --git a/lib/pages/buy_view/sub_widgets/fiat_selection_view.dart b/lib/pages/buy_view/sub_widgets/fiat_selection_view.dart
index e777ab96c..dfaec0dec 100644
--- a/lib/pages/buy_view/sub_widgets/fiat_selection_view.dart
+++ b/lib/pages/buy_view/sub_widgets/fiat_selection_view.dart
@@ -207,7 +207,7 @@ class _FiatSelectionViewState extends State {
decoration: BoxDecoration(
color: Theme.of(context)
.extension()!
- .highlight,
+ .currencyListItemBG,
borderRadius: BorderRadius.circular(4),
),
child: Text(
diff --git a/lib/pages/wallet_view/sub_widgets/wallet_navigation_bar.dart b/lib/pages/wallet_view/sub_widgets/wallet_navigation_bar.dart
index 37ad9717c..2b7441584 100644
--- a/lib/pages/wallet_view/sub_widgets/wallet_navigation_bar.dart
+++ b/lib/pages/wallet_view/sub_widgets/wallet_navigation_bar.dart
@@ -412,47 +412,49 @@ class _WalletNavigationBarState extends State {
const SizedBox(
width: 12,
),
- RawMaterialButton(
- constraints: const BoxConstraints(
- minWidth: 66,
- ),
- onPressed: widget.onBuyPressed,
- splashColor:
- Theme.of(context).extension()!.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()!.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,
+ ),
],
),
),
diff --git a/lib/pages/wallet_view/wallet_view.dart b/lib/pages/wallet_view/wallet_view.dart
index 7416e349d..bc63f0868 100644
--- a/lib/pages/wallet_view/wallet_view.dart
+++ b/lib/pages/wallet_view/wallet_view.dart
@@ -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 {
),
);
},
- 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,
+ ));
+ },
),
),
],
diff --git a/lib/route_generator.dart b/lib/route_generator.dart
index af88499ba..371b52311 100644
--- a/lib/route_generator.dart
+++ b/lib/route_generator.dart
@@ -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,
diff --git a/lib/services/buy/simplex/simplex_api.dart b/lib/services/buy/simplex/simplex_api.dart
index 9e01609bf..4060567f0 100644
--- a/lib/services/buy/simplex/simplex_api.dart
+++ b/lib/services/buy/simplex/simplex_api.dart
@@ -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;
+ }
+}
diff --git a/lib/utilities/assets.dart b/lib/utilities/assets.dart
index 311a267aa..5a0427782 100644
--- a/lib/utilities/assets.dart
+++ b/lib/utilities/assets.dart
@@ -32,9 +32,8 @@ class _BUY {
const _BUY();
// TODO: switch this to something like
- // String buy(BuildContext context) =>
- // "assets/svg/${Theme.of(context).extension()!.themeType.name}/buy.svg";
- String get buy => "assets/svg/light/buy-coins-icon.svg";
+ String buy(BuildContext context) =>
+ "assets/svg/${Theme.of(context).extension()!.themeType.name}/buy-coins-icon.svg";
String simplexLogo(BuildContext context) {
return (Theme.of(context).extension()!.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 {
diff --git a/lib/utilities/enums/coin_enum.dart b/lib/utilities/enums/coin_enum.dart
index 69e34daaf..5120cb52f 100644
--- a/lib/utilities/enums/coin_enum.dart
+++ b/lib/utilities/enums/coin_enum.dart
@@ -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:
diff --git a/pubspec.yaml b/pubspec.yaml
index 7585f77c3..e87083899 100644
--- a/pubspec.yaml
+++ b/pubspec.yaml
@@ -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