diff --git a/assets/svg/buy/Simplex-Nuvei-Logo-light.svg b/assets/svg/buy/Simplex-Nuvei-Logo-light.svg
new file mode 100644
index 000000000..367ce0de4
--- /dev/null
+++ b/assets/svg/buy/Simplex-Nuvei-Logo-light.svg
@@ -0,0 +1,65 @@
+
+
+
+
diff --git a/assets/svg/buy/Simplex-Nuvei-Logo.svg b/assets/svg/buy/Simplex-Nuvei-Logo.svg
new file mode 100644
index 000000000..736f6fd45
--- /dev/null
+++ b/assets/svg/buy/Simplex-Nuvei-Logo.svg
@@ -0,0 +1,65 @@
+
+
+
+
diff --git a/assets/svg/coin_icons/BinanceUSD.svg b/assets/svg/coin_icons/BinanceUSD.svg
new file mode 100644
index 000000000..d2a374781
--- /dev/null
+++ b/assets/svg/coin_icons/BinanceUSD.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/assets/svg/coin_icons/Cosmos.svg b/assets/svg/coin_icons/Cosmos.svg
new file mode 100644
index 000000000..a97174439
--- /dev/null
+++ b/assets/svg/coin_icons/Cosmos.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/assets/svg/coin_icons/Dai.svg b/assets/svg/coin_icons/Dai.svg
new file mode 100644
index 000000000..75de37346
--- /dev/null
+++ b/assets/svg/coin_icons/Dai.svg
@@ -0,0 +1,10 @@
+
+
+
+
\ No newline at end of file
diff --git a/assets/svg/coin_icons/Dash.svg b/assets/svg/coin_icons/Dash.svg
new file mode 100644
index 000000000..140ab6704
--- /dev/null
+++ b/assets/svg/coin_icons/Dash.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/assets/svg/coin_icons/EOS.svg b/assets/svg/coin_icons/EOS.svg
new file mode 100644
index 000000000..df772834f
--- /dev/null
+++ b/assets/svg/coin_icons/EOS.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/assets/svg/coin_icons/Ethereum.svg b/assets/svg/coin_icons/Ethereum.svg
new file mode 100644
index 000000000..7ffd694cc
--- /dev/null
+++ b/assets/svg/coin_icons/Ethereum.svg
@@ -0,0 +1,18 @@
+
+
+
+
\ No newline at end of file
diff --git a/assets/svg/coin_icons/Litecoin.svg b/assets/svg/coin_icons/Litecoin.svg
index 2b89ca50b..13e76a40e 100644
--- a/assets/svg/coin_icons/Litecoin.svg
+++ b/assets/svg/coin_icons/Litecoin.svg
@@ -1,11 +1 @@
-
+
\ No newline at end of file
diff --git a/assets/svg/coin_icons/Ripple.svg b/assets/svg/coin_icons/Ripple.svg
new file mode 100644
index 000000000..9a2c7c632
--- /dev/null
+++ b/assets/svg/coin_icons/Ripple.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/assets/svg/coin_icons/Stellar.svg b/assets/svg/coin_icons/Stellar.svg
new file mode 100644
index 000000000..02afb7b79
--- /dev/null
+++ b/assets/svg/coin_icons/Stellar.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/assets/svg/coin_icons/Tether.svg b/assets/svg/coin_icons/Tether.svg
new file mode 100644
index 000000000..e53082240
--- /dev/null
+++ b/assets/svg/coin_icons/Tether.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/assets/svg/coin_icons/Tron.svg b/assets/svg/coin_icons/Tron.svg
new file mode 100644
index 000000000..fa87a1d7e
--- /dev/null
+++ b/assets/svg/coin_icons/Tron.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/lib/hive/db.dart b/lib/hive/db.dart
index f5e031972..5c4f252e6 100644
--- a/lib/hive/db.dart
+++ b/lib/hive/db.dart
@@ -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}) =>
diff --git a/lib/main.dart b/lib/main.dart
index b2f75705d..8296d739a 100644
--- a/lib/main.dart
+++ b/lib/main.dart
@@ -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
// 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
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);
}
diff --git a/lib/models/buy/buy_form_state.dart b/lib/models/buy/buy_form_state.dart
new file mode 100644
index 000000000..843262eb3
--- /dev/null
+++ b/lib/models/buy/buy_form_state.dart
@@ -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;
+}
diff --git a/lib/models/buy/response_objects/crypto.dart b/lib/models/buy/response_objects/crypto.dart
new file mode 100644
index 000000000..d3f3aded0
--- /dev/null
+++ b/lib/models/buy/response_objects/crypto.dart
@@ -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 json) {
+ try {
+ return Crypto(
+ ticker: "${json['ticker']}",
+ name: "${json['name']}",
+ network: "${json['network']}",
+ contractAddress: "${json['contractAddress']}",
+ );
+ } catch (e) {
+ rethrow;
+ }
+ }
+
+ Map toJson() {
+ final map = {
+ "ticker": ticker,
+ "name": name,
+ "network": network,
+ "contractAddress": contractAddress,
+ };
+
+ return map;
+ }
+}
diff --git a/lib/models/buy/response_objects/fiat.dart b/lib/models/buy/response_objects/fiat.dart
new file mode 100644
index 000000000..00cb0f945
--- /dev/null
+++ b/lib/models/buy/response_objects/fiat.dart
@@ -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 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 toJson() {
+ final map = {
+ "ticker": ticker,
+ "name": name,
+ "min_amount": minAmount,
+ "max_amount": maxAmount,
+ };
+
+ return map;
+ }
+}
diff --git a/lib/models/buy/response_objects/order.dart b/lib/models/buy/response_objects/order.dart
new file mode 100644
index 000000000..3802dd1cc
--- /dev/null
+++ b/lib/models/buy/response_objects/order.dart
@@ -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,
+ });
+}
diff --git a/lib/models/buy/response_objects/quote.dart b/lib/models/buy/response_objects/quote.dart
new file mode 100644
index 000000000..e91ca4d67
--- /dev/null
+++ b/lib/models/buy/response_objects/quote.dart
@@ -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,
+ });
+}
diff --git a/lib/models/buy/simplex/simplex.dart b/lib/models/buy/simplex/simplex.dart
new file mode 100644
index 000000000..bb75036ab
--- /dev/null
+++ b/lib/models/buy/simplex/simplex.dart
@@ -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 supportedCryptos = [];
+ List 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 newCryptos) {
+ supportedCryptos = newCryptos;
+ }
+
+ void updateSupportedFiats(List newFiats) {
+ supportedFiats = newFiats;
+ }
+
+ void updateQuote(SimplexQuote newQuote) {
+ quote = newQuote;
+ }
+
+ void updateOrder(SimplexOrder newOrder) {
+ order = newOrder;
+ }
+}
diff --git a/lib/pages/buy_view/buy_form.dart b/lib/pages/buy_view/buy_form.dart
new file mode 100644
index 000000000..83b47e136
--- /dev/null
+++ b/lib/pages/buy_view/buy_form.dart
@@ -0,0 +1,1185 @@
+import 'dart:async';
+
+import 'package:decimal/decimal.dart';
+import 'package:flutter/material.dart';
+import 'package:flutter/services.dart';
+import 'package:flutter_riverpod/flutter_riverpod.dart';
+import 'package:flutter_svg/svg.dart';
+import 'package:intl/intl.dart';
+import 'package:stackwallet/models/buy/response_objects/crypto.dart';
+import 'package:stackwallet/models/buy/response_objects/fiat.dart';
+import 'package:stackwallet/models/buy/response_objects/quote.dart';
+import 'package:stackwallet/models/contact_address_entry.dart';
+import 'package:stackwallet/pages/address_book_views/address_book_view.dart';
+import 'package:stackwallet/pages/buy_view/buy_quote_preview.dart';
+import 'package:stackwallet/pages/buy_view/sub_widgets/crypto_selection_view.dart';
+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/simplex/simplex_api.dart';
+import 'package:stackwallet/utilities/address_utils.dart';
+import 'package:stackwallet/utilities/assets.dart';
+import 'package:stackwallet/utilities/barcode_scanner_interface.dart';
+import 'package:stackwallet/utilities/clipboard_interface.dart';
+import 'package:stackwallet/utilities/constants.dart';
+import 'package:stackwallet/utilities/enums/coin_enum.dart';
+import 'package:stackwallet/utilities/logger.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/conditional_parent.dart';
+import 'package:stackwallet/widgets/custom_buttons/blue_text_button.dart';
+import 'package:stackwallet/widgets/custom_loading_overlay.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/icon_widgets/addressbook_icon.dart';
+import 'package:stackwallet/widgets/icon_widgets/clipboard_icon.dart';
+import 'package:stackwallet/widgets/icon_widgets/qrcode_icon.dart';
+import 'package:stackwallet/widgets/icon_widgets/x_icon.dart';
+import 'package:stackwallet/widgets/rounded_container.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 BuyForm extends ConsumerStatefulWidget {
+ const BuyForm({
+ Key? key,
+ this.clipboard = const ClipboardWrapper(),
+ this.scanner = const BarcodeScannerWrapper(),
+ }) : super(key: key);
+
+ final ClipboardInterface clipboard;
+ final BarcodeScannerInterface scanner;
+
+ @override
+ ConsumerState createState() => _BuyFormState();
+}
+
+class _BuyFormState extends ConsumerState {
+ late final ClipboardInterface clipboard;
+ late final BarcodeScannerInterface scanner;
+
+ late final TextEditingController _receiveAddressController;
+ late final TextEditingController _buyAmountController;
+ final FocusNode _receiveAddressFocusNode = FocusNode();
+ final FocusNode _fiatFocusNode = FocusNode();
+ final FocusNode _cryptoFocusNode = FocusNode();
+ final FocusNode _buyAmountFocusNode = FocusNode();
+
+ final isDesktop = Util.isDesktop;
+
+ List? coins;
+ List? fiats;
+ String? _address;
+
+ Fiat? selectedFiat;
+ Crypto? selectedCrypto;
+ SimplexQuote quote = SimplexQuote(
+ 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",
+ receivingAddress: '',
+ buyWithFiat: true,
+ ); // TODO enum this or something
+
+ bool buyWithFiat = true;
+ bool _addressToggleFlag = false;
+ bool _hovering1 = false;
+ bool _hovering2 = false;
+
+ void fiatFieldOnChanged(String value) async {}
+
+ void cryptoFieldOnChanged(String value) async {}
+
+ void selectCrypto() async {
+ if (ref.read(simplexProvider).supportedCryptos.isEmpty) {
+ bool shouldPop = false;
+ unawaited(
+ showDialog(
+ context: context,
+ builder: (context) => WillPopScope(
+ child: const CustomLoadingOverlay(
+ message: "Loading currency data",
+ eventBus: null,
+ ),
+ onWillPop: () async => shouldPop,
+ ),
+ ),
+ );
+ await _loadSimplexCryptos();
+ shouldPop = true;
+ if (mounted) {
+ Navigator.of(context, rootNavigator: isDesktop).pop();
+ }
+ }
+
+ await _showFloatingCryptoSelectionSheet(
+ coins: ref.read(simplexProvider).supportedCryptos,
+ onSelected: (crypto) {
+ setState(() {
+ selectedCrypto = crypto;
+ });
+ },
+ );
+ }
+
+ Future _showFloatingCryptoSelectionSheet({
+ required List coins,
+ required void Function(Crypto) onSelected,
+ }) async {
+ _fiatFocusNode.unfocus();
+ _cryptoFocusNode.unfocus();
+
+ final result = isDesktop
+ ? await showDialog(
+ context: context,
+ builder: (context) {
+ return DesktopDialog(
+ maxHeight: 700,
+ maxWidth: 580,
+ child: Column(
+ children: [
+ Row(
+ mainAxisAlignment: MainAxisAlignment.spaceBetween,
+ children: [
+ Padding(
+ padding: const EdgeInsets.only(
+ left: 32,
+ ),
+ child: Text(
+ "Choose a crypto to buy",
+ 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()!
+ .background,
+ child: CryptoSelectionView(
+ coins: coins,
+ ),
+ ),
+ ),
+ ],
+ ),
+ ),
+ ),
+ ],
+ ),
+ );
+ })
+ : await Navigator.of(context).push(
+ MaterialPageRoute(
+ builder: (_) => CryptoSelectionView(
+ coins: coins,
+ ),
+ ),
+ );
+
+ if (mounted && result is Crypto) {
+ onSelected(result);
+ }
+ }
+
+ Future selectFiat() async {
+ if (ref.read(simplexProvider).supportedFiats.isEmpty) {
+ bool shouldPop = false;
+ unawaited(
+ showDialog(
+ context: context,
+ builder: (context) => WillPopScope(
+ child: const CustomLoadingOverlay(
+ message: "Loading currency data",
+ eventBus: null,
+ ),
+ onWillPop: () async => shouldPop,
+ ),
+ ),
+ );
+ await _loadSimplexFiats();
+ shouldPop = true;
+ if (mounted) {
+ Navigator.of(context, rootNavigator: isDesktop).pop();
+ }
+ }
+
+ await _showFloatingFiatSelectionSheet(
+ fiats: ref.read(simplexProvider).supportedFiats,
+ onSelected: (fiat) {
+ setState(() {
+ selectedFiat = fiat;
+ });
+ },
+ );
+ }
+
+ Future _loadSimplexCryptos() async {
+ final response = await SimplexAPI.instance.getSupportedCryptos();
+
+ if (response.value != null) {
+ ref
+ .read(simplexProvider)
+ .updateSupportedCryptos(response.value!); // TODO validate
+ } else {
+ Logging.instance.log(
+ "_loadSimplexCurrencies: $response",
+ level: LogLevel.Warning,
+ );
+ }
+ }
+
+ Future _loadSimplexFiats() async {
+ final response = await SimplexAPI.instance.getSupportedFiats();
+
+ if (response.value != null) {
+ ref
+ .read(simplexProvider)
+ .updateSupportedFiats(response.value!); // TODO validate
+ } else {
+ Logging.instance.log(
+ "_loadSimplexCurrencies: $response",
+ level: LogLevel.Warning,
+ );
+ }
+ }
+
+ Future _showFloatingFiatSelectionSheet({
+ required List fiats,
+ required void Function(Fiat) onSelected,
+ }) async {
+ _fiatFocusNode.unfocus();
+ _cryptoFocusNode.unfocus();
+
+ final result = isDesktop
+ ? await showDialog(
+ context: context,
+ builder: (context) {
+ return DesktopDialog(
+ maxHeight: 700,
+ maxWidth: 580,
+ child: Column(
+ children: [
+ Row(
+ mainAxisAlignment: MainAxisAlignment.spaceBetween,
+ children: [
+ Padding(
+ padding: const EdgeInsets.only(
+ left: 32,
+ ),
+ child: Text(
+ "Choose a fiat with which to pay",
+ 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()!
+ .background,
+ child: FiatSelectionView(
+ fiats: fiats,
+ ),
+ ),
+ ),
+ ],
+ ),
+ ),
+ ),
+ ],
+ ),
+ );
+ })
+ : await Navigator.of(context).push(
+ MaterialPageRoute(
+ builder: (_) => FiatSelectionView(
+ fiats: fiats,
+ ),
+ ),
+ );
+
+ if (mounted && result is Fiat) {
+ onSelected(result);
+ }
+ }
+
+ String? _fetchIconUrlFromTicker(String? ticker) {
+ if (ticker == null) return null;
+
+ return null;
+ }
+
+ 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;
+ }
+
+ Future previewQuote(SimplexQuote quote) async {
+ // if (ref.read(simplexProvider).quote.id == "someID") {
+ // // TODO make a better way of detecting a default SimplexQuote
+ bool shouldPop = false;
+ unawaited(
+ showDialog(
+ context: context,
+ builder: (context) => WillPopScope(
+ child: const CustomLoadingOverlay(
+ message: "Loading quote data",
+ eventBus: null,
+ ),
+ onWillPop: () async => shouldPop,
+ ),
+ ),
+ );
+
+ quote = SimplexQuote(
+ crypto: selectedCrypto!,
+ fiat: selectedFiat!,
+ youPayFiatPrice: buyWithFiat
+ ? Decimal.parse(_buyAmountController.text)
+ : Decimal.parse("100"), // dummy value
+ youReceiveCryptoAmount: buyWithFiat
+ ? Decimal.parse("0.000420282") // dummy value
+ : Decimal.parse(_buyAmountController.text), // Ternary for this
+ id: "id", // anything; we get an ID back
+ receivingAddress: _receiveAddressController.text,
+ buyWithFiat: buyWithFiat,
+ );
+
+ await _loadQuote(quote);
+ shouldPop = true;
+ if (mounted) {
+ Navigator.of(context, rootNavigator: isDesktop).pop();
+ }
+ // }
+
+ await _showFloatingBuyQuotePreviewSheet(
+ quote: ref.read(simplexProvider).quote,
+ onSelected: (quote) {
+ // setState(() {
+ // selectedFiat = fiat;
+ // });
+ // TODO launch URL
+ },
+ );
+ }
+
+ Future _loadQuote(SimplexQuote quote) async {
+ final response = await SimplexAPI.instance.getQuote(quote);
+
+ if (response.value != null) {
+ ref.read(simplexProvider).updateQuote(response.value!);
+ } else {
+ Logging.instance.log(
+ "_loadQuote: $response",
+ level: LogLevel.Warning,
+ );
+ }
+ }
+
+ Future _showFloatingBuyQuotePreviewSheet({
+ required SimplexQuote quote,
+ required void Function(SimplexQuote) onSelected,
+ }) async {
+ _fiatFocusNode.unfocus();
+ _cryptoFocusNode.unfocus();
+
+ final result = isDesktop
+ ? await showDialog(
+ context: context,
+ builder: (context) {
+ return DesktopDialog(
+ maxHeight: 700,
+ maxWidth: 580,
+ child: Column(
+ children: [
+ Row(
+ mainAxisAlignment: MainAxisAlignment.spaceBetween,
+ children: [
+ Padding(
+ padding: const EdgeInsets.only(
+ left: 32,
+ ),
+ child: Text(
+ "Preview quote",
+ 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()!
+ .background,
+ child: BuyQuotePreviewView(
+ quote: quote,
+ ),
+ ),
+ ),
+ ],
+ ),
+ ),
+ ),
+ ],
+ ),
+ );
+ })
+ : await Navigator.of(context).push(
+ MaterialPageRoute(
+ builder: (_) => BuyQuotePreviewView(
+ quote: quote,
+ ),
+ ),
+ );
+
+ if (mounted && result is SimplexQuote) {
+ onSelected(result);
+ }
+ }
+
+ @override
+ void initState() {
+ _receiveAddressController = TextEditingController();
+ _buyAmountController = TextEditingController();
+
+ clipboard = widget.clipboard;
+ scanner = widget.scanner;
+
+ coins = ref.read(simplexProvider).supportedCryptos;
+ fiats = ref.read(simplexProvider).supportedFiats;
+ // 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': ''}),
+ youPayFiatPrice: Decimal.parse("100"),
+ youReceiveCryptoAmount: Decimal.parse("1.0238917"),
+ id: "someID",
+ receivingAddress: '',
+ buyWithFiat: true,
+ ); // 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': ''});
+
+ // TODO set initial crypto to open wallet if a wallet is open
+
+ super.initState();
+ }
+
+ @override
+ void dispose() {
+ super.dispose();
+ }
+
+ @override
+ Widget build(BuildContext context) {
+ debugPrint("BUILD: $runtimeType");
+
+ Locale locale = Localizations.localeOf(context);
+ var format = NumberFormat.simpleCurrency(locale: locale.toString());
+ // See https://stackoverflow.com/a/67055685
+
+ return ConditionalParent(
+ condition: isDesktop,
+ builder: (child) => SizedBox(
+ width: 458,
+ child: child,
+ ),
+ child: ConditionalParent(
+ condition: !isDesktop,
+ builder: (child) => LayoutBuilder(
+ builder: (context, constraints) => SingleChildScrollView(
+ child: ConstrainedBox(
+ constraints: BoxConstraints(
+ minHeight: constraints.maxHeight,
+ ),
+ child: IntrinsicHeight(
+ child: child,
+ ),
+ ),
+ ),
+ ),
+ child: Column(
+ mainAxisSize: MainAxisSize.min,
+ crossAxisAlignment: CrossAxisAlignment.stretch,
+ children: [
+ Text(
+ "I want to buy",
+ style: STextStyles.itemSubtitle(context).copyWith(
+ color: Theme.of(context).extension()!.textDark3,
+ ),
+ ),
+ SizedBox(
+ height: isDesktop ? 10 : 4,
+ ),
+ MouseRegion(
+ cursor: SystemMouseCursors.click,
+ onEnter: (_) => setState(() => _hovering1 = true),
+ onExit: (_) => setState(() => _hovering1 = false),
+ child: GestureDetector(
+ onTap: () {
+ selectCrypto();
+ },
+ child: RoundedContainer(
+ padding:
+ const EdgeInsets.symmetric(vertical: 6, horizontal: 2),
+ color: _hovering1
+ ? Theme.of(context)
+ .extension()!
+ .highlight
+ .withOpacity(_hovering1 ? 0.3 : 0)
+ : Theme.of(context)
+ .extension()!
+ .textFieldDefaultBG,
+ child: Padding(
+ padding: const EdgeInsets.all(12),
+ child: Row(
+ children: [
+ getIconForTicker(selectedCrypto?.ticker ?? "BTC")
+ as Widget,
+ const SizedBox(
+ width: 10,
+ ),
+ Expanded(
+ child: Text(
+ selectedCrypto?.ticker ?? "ERR",
+ style: STextStyles.largeMedium14(context),
+ ),
+ ),
+ SvgPicture.asset(
+ Assets.svg.chevronDown,
+ color: Theme.of(context)
+ .extension()!
+ .buttonTextSecondaryDisabled,
+ width: 10,
+ height: 5,
+ ),
+ ],
+ ),
+ ),
+ ),
+ ),
+ ),
+ SizedBox(
+ height: isDesktop ? 20 : 12,
+ ),
+ Row(
+ crossAxisAlignment: CrossAxisAlignment.end,
+ mainAxisAlignment: MainAxisAlignment.spaceBetween,
+ children: [
+ Text(
+ "I want to pay with",
+ style: STextStyles.itemSubtitle(context).copyWith(
+ color:
+ Theme.of(context).extension()!.textDark3,
+ ),
+ ),
+ ],
+ ),
+ SizedBox(
+ height: isDesktop ? 10 : 4,
+ ),
+ MouseRegion(
+ cursor: SystemMouseCursors.click,
+ onEnter: (_) => setState(() => _hovering2 = true),
+ onExit: (_) => setState(() => _hovering2 = false),
+ child: GestureDetector(
+ onTap: () {
+ selectFiat();
+ },
+ child: RoundedContainer(
+ padding:
+ const EdgeInsets.symmetric(vertical: 3, horizontal: 2),
+ color: _hovering2
+ ? Theme.of(context)
+ .extension()!
+ .highlight
+ .withOpacity(_hovering2 ? 0.3 : 0)
+ : Theme.of(context)
+ .extension()!
+ .textFieldDefaultBG,
+ child: Padding(
+ padding: const EdgeInsets.only(
+ left: 12.0, top: 12.0, right: 12.0, bottom: 12.0),
+ child: Row(
+ children: [
+ Container(
+ padding: const EdgeInsets.symmetric(
+ vertical: 3, horizontal: 6),
+ decoration: BoxDecoration(
+ color: Theme.of(context)
+ .extension()!
+ .highlight,
+ borderRadius: BorderRadius.circular(4),
+ ),
+ child: Text(
+ format.simpleCurrencySymbol(
+ selectedFiat?.ticker ?? "ERR".toUpperCase()),
+ textAlign: TextAlign.center,
+ style: STextStyles.smallMed12(context).copyWith(
+ color: Theme.of(context)
+ .extension()!
+ .accentColorDark),
+ ),
+ ),
+ const SizedBox(
+ width: 8,
+ ),
+ Text(
+ "${selectedFiat?.ticker ?? 'ERR'}",
+ style: STextStyles.largeMedium14(context),
+ ),
+ const SizedBox(
+ width: 12,
+ ),
+ Expanded(
+ child: Text(
+ "${selectedFiat?.name ?? 'Error'}",
+ style: STextStyles.largeMedium14(context),
+ ),
+ ),
+ SvgPicture.asset(
+ Assets.svg.chevronDown,
+ color: Theme.of(context)
+ .extension()!
+ .buttonTextSecondaryDisabled,
+ width: 10,
+ height: 5,
+ ),
+ ],
+ ),
+ ),
+ ),
+ ),
+ ),
+ SizedBox(
+ height: isDesktop ? 10 : 4,
+ ),
+ Row(
+ crossAxisAlignment: CrossAxisAlignment.end,
+ mainAxisAlignment: MainAxisAlignment.spaceBetween,
+ children: [
+ Text(
+ buyWithFiat ? "Enter amount" : "Enter crypto amount",
+ style: STextStyles.itemSubtitle(context).copyWith(
+ color:
+ Theme.of(context).extension()!.textDark3,
+ ),
+ ),
+ BlueTextButton(
+ text: buyWithFiat ? "Use crypto amount" : "Use fiat amount",
+ onTap: () {
+ setState(() {
+ buyWithFiat = !buyWithFiat;
+ });
+ },
+ )
+ ],
+ ),
+ SizedBox(
+ height: isDesktop ? 10 : 4,
+ ),
+ TextField(
+ autocorrect: Util.isDesktop ? false : true,
+ enableSuggestions: Util.isDesktop ? false : true,
+ style: STextStyles.smallMed14(context).copyWith(
+ color: Theme.of(context).extension()!.textDark,
+ ),
+ key: const Key("amountInputFieldCryptoTextFieldKey"),
+ controller: _buyAmountController,
+ focusNode: _buyAmountFocusNode,
+ keyboardType: Util.isDesktop
+ ? null
+ : const TextInputType.numberWithOptions(
+ signed: false,
+ decimal: true,
+ ),
+ textAlign: TextAlign.left,
+ inputFormatters: [
+ // regex to validate a crypto amount with 8 decimal places or
+ // 2 if fiat
+ TextInputFormatter.withFunction(
+ (oldValue, newValue) {
+ final regexString = 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;
+ },
+ ),
+ ],
+ decoration: InputDecoration(
+ contentPadding: const EdgeInsets.only(
+ // top: 22,
+ // right: 12,
+ // bottom: 22,
+ left: 0,
+ top: 8,
+ bottom: 10,
+ right: 5,
+ ),
+ hintText: "0",
+ hintStyle: STextStyles.desktopTextExtraSmall(context).copyWith(
+ color: Theme.of(context)
+ .extension()!
+ .textFieldDefaultText,
+ ),
+ prefixIcon: FittedBox(
+ fit: BoxFit.scaleDown,
+ child: Padding(
+ padding: const EdgeInsets.all(12),
+ child: Row(children: [
+ const SizedBox(width: 2),
+ buyWithFiat
+ ? Container(
+ padding: const EdgeInsets.symmetric(
+ vertical: 3, horizontal: 6),
+ decoration: BoxDecoration(
+ color: Theme.of(context)
+ .extension()!
+ .highlight,
+ borderRadius: BorderRadius.circular(4),
+ ),
+ child: Text(
+ format.simpleCurrencySymbol(
+ selectedFiat?.ticker ??
+ "ERR".toUpperCase()),
+ textAlign: TextAlign.center,
+ style: STextStyles.smallMed12(context).copyWith(
+ color: Theme.of(context)
+ .extension()!
+ .accentColorDark),
+ ),
+ )
+ : getIconForTicker(selectedCrypto?.ticker ?? "BTC")
+ as Widget,
+ SizedBox(
+ width: buyWithFiat
+ ? 8
+ : 10), // maybe make isDesktop-aware?
+ Text(
+ buyWithFiat
+ ? selectedFiat?.ticker ?? "ERR"
+ : selectedCrypto?.ticker ?? "ERR",
+ style: STextStyles.smallMed14(context).copyWith(
+ color: Theme.of(context)
+ .extension()!
+ .accentColorDark),
+ ),
+ ]),
+ ),
+ ),
+ suffixIcon: Padding(
+ padding: const EdgeInsets.all(0),
+ child: UnconstrainedBox(
+ child: Row(
+ mainAxisAlignment: MainAxisAlignment.spaceAround,
+ children: [
+ _buyAmountController.text.isNotEmpty
+ ? TextFieldIconButton(
+ key: const Key(
+ "buyViewClearAddressFieldButtonKey"),
+ onTap: () {
+ _buyAmountController.text = "";
+ // _receiveAddress = "";
+ setState(() {});
+ },
+ child: const XIcon(),
+ )
+ : TextFieldIconButton(
+ key: const Key(
+ "buyViewPasteAddressFieldButtonKey"),
+ onTap: () async {
+ final ClipboardData? data = await clipboard
+ .getData(Clipboard.kTextPlain);
+
+ final amountString =
+ Decimal.tryParse(data?.text ?? "");
+ if (amountString != null) {
+ _buyAmountController.text =
+ amountString.toString();
+
+ setState(() {});
+ }
+ },
+ child: _buyAmountController.text.isEmpty
+ ? const ClipboardIcon()
+ : const XIcon(),
+ ),
+ ],
+ ),
+ ),
+ ),
+ ),
+ ),
+ SizedBox(
+ height: isDesktop ? 20 : 12,
+ ),
+ Row(
+ crossAxisAlignment: CrossAxisAlignment.end,
+ mainAxisAlignment: MainAxisAlignment.spaceBetween,
+ children: [
+ Text(
+ "Enter receiving address",
+ style: STextStyles.itemSubtitle(context).copyWith(
+ color:
+ Theme.of(context).extension()!.textDark3,
+ ),
+ ),
+ if (isStackCoin(selectedCrypto?.ticker))
+ BlueTextButton(
+ text: "Choose from stack",
+ onTap: () {
+ try {
+ final coin = coinFromTickerCaseInsensitive(
+ selectedCrypto!.ticker,
+ );
+ Navigator.of(context)
+ .pushNamed(
+ ChooseFromStackView.routeName,
+ arguments: coin,
+ )
+ .then((value) async {
+ if (value is String) {
+ final manager = ref
+ .read(walletsChangeNotifierProvider)
+ .getManager(value);
+
+ // _toController.text = manager.walletName;
+ // model.recipientAddress =
+ // await manager.currentReceivingAddress;
+ _receiveAddressController.text =
+ await manager.currentReceivingAddress;
+
+ setState(() {});
+ }
+ });
+ } catch (e, s) {
+ Logging.instance.log("$e\n$s", level: LogLevel.Info);
+ }
+ },
+ ),
+ ],
+ ),
+ SizedBox(
+ height: isDesktop ? 10 : 4,
+ ),
+ ClipRRect(
+ borderRadius: BorderRadius.circular(
+ Constants.size.circularBorderRadius,
+ ),
+ child: TextField(
+ key: const Key("buyViewReceiveAddressFieldKey"),
+ controller: _receiveAddressController,
+ readOnly: false,
+ autocorrect: false,
+ enableSuggestions: false,
+ // inputFormatters: [
+ // FilteringTextInputFormatter.allow(
+ // RegExp("[a-zA-Z0-9]{34}")),
+ // ],
+ toolbarOptions: const ToolbarOptions(
+ copy: false,
+ cut: false,
+ paste: true,
+ selectAll: false,
+ ),
+ onChanged: (newValue) {
+ _address = newValue;
+ setState(() {
+ _addressToggleFlag = newValue.isNotEmpty;
+ });
+ },
+ focusNode: _receiveAddressFocusNode,
+ style: STextStyles.field(context),
+ decoration: standardInputDecoration(
+ "Enter ${selectedCrypto?.ticker} address",
+ _receiveAddressFocusNode,
+ context,
+ ).copyWith(
+ contentPadding: const EdgeInsets.only(
+ left: 13,
+ top: 6,
+ bottom: 8,
+ right: 5,
+ ),
+ suffixIcon: Padding(
+ padding: _receiveAddressController.text.isEmpty
+ ? const EdgeInsets.only(right: 8)
+ : const EdgeInsets.only(right: 0),
+ child: UnconstrainedBox(
+ child: Row(
+ mainAxisAlignment: MainAxisAlignment.spaceAround,
+ children: [
+ _addressToggleFlag
+ ? TextFieldIconButton(
+ key: const Key(
+ "buyViewClearAddressFieldButtonKey"),
+ onTap: () {
+ _receiveAddressController.text = "";
+ _address = "";
+ setState(() {
+ _addressToggleFlag = true;
+ });
+ },
+ child: const XIcon(),
+ )
+ : TextFieldIconButton(
+ key: const Key(
+ "buyViewPasteAddressFieldButtonKey"),
+ onTap: () async {
+ final ClipboardData? data = await clipboard
+ .getData(Clipboard.kTextPlain);
+ if (data?.text != null &&
+ data!.text!.isNotEmpty) {
+ String content = data.text!.trim();
+ if (content.contains("\n")) {
+ content = content.substring(
+ 0, content.indexOf("\n"));
+ }
+
+ _receiveAddressController.text = content;
+ _address = content;
+
+ setState(() {
+ _addressToggleFlag =
+ _receiveAddressController
+ .text.isNotEmpty;
+ });
+ }
+ },
+ child: _receiveAddressController.text.isEmpty
+ ? const ClipboardIcon()
+ : const XIcon(),
+ ),
+ if (_receiveAddressController.text.isEmpty &&
+ isStackCoin(selectedCrypto?.ticker) &&
+ isDesktop)
+ TextFieldIconButton(
+ key: const Key("buyViewAddressBookButtonKey"),
+ onTap: () async {
+ final entry =
+ await showDialog(
+ context: context,
+ builder: (context) => DesktopDialog(
+ maxWidth: 696,
+ maxHeight: 600,
+ child: Column(
+ mainAxisSize: MainAxisSize.min,
+ children: [
+ Row(
+ mainAxisAlignment:
+ MainAxisAlignment.spaceBetween,
+ children: [
+ Padding(
+ padding: const EdgeInsets.only(
+ left: 32,
+ ),
+ child: Text(
+ "Address book",
+ style: STextStyles.desktopH3(
+ context),
+ ),
+ ),
+ const DesktopDialogCloseButton(),
+ ],
+ ),
+ Expanded(
+ child: AddressBookAddressChooser(
+ coin: coinFromTickerCaseInsensitive(
+ selectedCrypto!.ticker
+ .toString()),
+ ),
+ ),
+ ],
+ ),
+ ),
+ );
+
+ if (entry != null) {
+ _receiveAddressController.text =
+ entry.address;
+ _address = entry.address;
+
+ setState(() {
+ _addressToggleFlag = true;
+ });
+ }
+ },
+ child: const AddressBookIcon(),
+ ),
+ if (_receiveAddressController.text.isEmpty &&
+ isStackCoin(selectedCrypto?.ticker) &&
+ !isDesktop)
+ TextFieldIconButton(
+ key: const Key("buyViewAddressBookButtonKey"),
+ onTap: () {
+ Navigator.of(context, rootNavigator: isDesktop)
+ .pushNamed(
+ AddressBookView.routeName,
+ );
+ },
+ child: const AddressBookIcon(),
+ ),
+ if (_receiveAddressController.text.isEmpty &&
+ !isDesktop)
+ TextFieldIconButton(
+ key: const Key("buyViewScanQrButtonKey"),
+ onTap: () async {
+ try {
+ if (FocusScope.of(context).hasFocus) {
+ FocusScope.of(context).unfocus();
+ await Future.delayed(
+ const Duration(milliseconds: 75));
+ }
+
+ final qrResult = await scanner.scan();
+
+ Logging.instance.log(
+ "qrResult content: ${qrResult.rawContent}",
+ level: LogLevel.Info);
+
+ final results = AddressUtils.parseUri(
+ qrResult.rawContent);
+
+ Logging.instance.log(
+ "qrResult parsed: $results",
+ level: LogLevel.Info);
+
+ if (results.isNotEmpty) {
+ // auto fill address
+ _address = results["address"] ?? "";
+ _receiveAddressController.text = _address!;
+
+ setState(() {
+ _addressToggleFlag =
+ _receiveAddressController
+ .text.isNotEmpty;
+ });
+
+ // now check for non standard encoded basic address
+ } else {
+ _address = qrResult.rawContent;
+ _receiveAddressController.text =
+ _address ?? "";
+
+ setState(() {
+ _addressToggleFlag =
+ _receiveAddressController
+ .text.isNotEmpty;
+ });
+ }
+ } on PlatformException catch (e, s) {
+ // here we ignore the exception caused by not giving permission
+ // to use the camera to scan a qr code
+ Logging.instance.log(
+ "Failed to get camera permissions while trying to scan qr code in SendView: $e\n$s",
+ level: LogLevel.Warning,
+ );
+ }
+ },
+ child: const QrCodeIcon(),
+ ),
+ ],
+ ),
+ ),
+ ),
+ ),
+ ),
+ ),
+ SizedBox(
+ height: isDesktop ? 20 : 12,
+ ),
+ MouseRegion(
+ cursor: SystemMouseCursors.click,
+ child: GestureDetector(
+ onTap: () {
+ if (_receiveAddressController.text.isNotEmpty &&
+ _buyAmountController.text.isNotEmpty) {
+ previewQuote(quote);
+ }
+ },
+ child: PrimaryButton(
+ buttonHeight: isDesktop ? ButtonHeight.l : null,
+ enabled: _receiveAddressController.text.isNotEmpty &&
+ _buyAmountController.text.isNotEmpty,
+ onPressed: () {
+ if (_receiveAddressController.text.isNotEmpty &&
+ _buyAmountController.text.isNotEmpty) {
+ previewQuote(quote);
+ }
+ },
+ label: "Preview quote",
+ )),
+ ),
+ ],
+ ),
+ ),
+ );
+ }
+}
diff --git a/lib/pages/buy_view/buy_order_details.dart b/lib/pages/buy_view/buy_order_details.dart
new file mode 100644
index 000000000..2b584578e
--- /dev/null
+++ b/lib/pages/buy_view/buy_order_details.dart
@@ -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 createState() => _BuyOrderDetailsViewState();
+}
+
+class _BuyOrderDetailsViewState extends State {
+ final isDesktop = Util.isDesktop;
+
+ @override
+ Widget build(BuildContext context) {
+ return ConditionalParent(
+ condition: !isDesktop,
+ builder: (child) {
+ return Background(
+ child: Scaffold(
+ backgroundColor:
+ Theme.of(context).extension()!.background,
+ appBar: AppBar(
+ backgroundColor:
+ Theme.of(context).extension()!.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()!.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()!.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()!.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()!.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()!.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()!.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()!.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()!.textDark,
+ ),
+ textAlign: TextAlign.center,
+ ),
+ ]),
+ const Spacer(),
+ PrimaryButton(
+ label: "Dismiss",
+ onPressed: () {
+ Navigator.of(context, rootNavigator: isDesktop).pop();
+ },
+ )
+ ],
+ ),
+ );
+ }
+}
diff --git a/lib/pages/buy_view/buy_quote_preview.dart b/lib/pages/buy_view/buy_quote_preview.dart
new file mode 100644
index 000000000..3526fed8e
--- /dev/null
+++ b/lib/pages/buy_view/buy_quote_preview.dart
@@ -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 createState() => _BuyQuotePreviewViewState();
+}
+
+class _BuyQuotePreviewViewState extends State {
+ final isDesktop = Util.isDesktop;
+
+ Future _buyWarning() async {
+ await showDialog(
+ 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()!.background,
+ appBar: AppBar(
+ backgroundColor:
+ Theme.of(context).extension()!.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()!.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()!.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()!.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()!.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()!.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,
+ )
+ ],
+ ),
+ );
+ }
+}
diff --git a/lib/pages/buy_view/buy_view.dart b/lib/pages/buy_view/buy_view.dart
index cc536dd45..0eb44e87e 100644
--- a/lib/pages/buy_view/buy_view.dart
+++ b/lib/pages/buy_view/buy_view.dart
@@ -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 createState() => _BuyViewState();
}
@@ -11,39 +13,16 @@ class BuyView extends StatefulWidget {
class _BuyViewState extends State {
@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(),
),
);
}
diff --git a/lib/pages/buy_view/sub_widgets/buy_warning_popup.dart b/lib/pages/buy_view/sub_widgets/buy_warning_popup.dart
new file mode 100644
index 000000000..082a19306
--- /dev/null
+++ b/lib/pages/buy_view/sub_widgets/buy_warning_popup.dart
@@ -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> 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> redirect(SimplexOrder order) async {
+ return SimplexAPI.instance.redirect(order);
+ }
+
+ @override
+ Widget build(BuildContext context) {
+ final isDesktop = Util.isDesktop;
+
+ Future _buyInvoice() async {
+ await showDialog(
+ 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()!
+ .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 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),
+ ),
+ ),
+ );
+ }
+}
diff --git a/lib/pages/buy_view/sub_widgets/crypto_selection_view.dart b/lib/pages/buy_view/sub_widgets/crypto_selection_view.dart
new file mode 100644
index 000000000..2d0fe3095
--- /dev/null
+++ b/lib/pages/buy_view/sub_widgets/crypto_selection_view.dart
@@ -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 coins;
+
+ @override
+ State createState() => _CryptoSelectionViewState();
+}
+
+class _CryptoSelectionViewState extends State {
+ late TextEditingController _searchController;
+ final _searchFocusNode = FocusNode();
+
+ late final List coins;
+ late List _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()!.background,
+ appBar: AppBar(
+ leading: AppBarBackButton(
+ onPressed: () async {
+ if (FocusScope.of(context).hasFocus) {
+ FocusScope.of(context).unfocus();
+ await Future.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()!
+ .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;
+}
diff --git a/lib/pages/buy_view/sub_widgets/fiat_selection_view.dart b/lib/pages/buy_view/sub_widgets/fiat_selection_view.dart
new file mode 100644
index 000000000..e777ab96c
--- /dev/null
+++ b/lib/pages/buy_view/sub_widgets/fiat_selection_view.dart
@@ -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 fiats;
+
+ @override
+ State createState() => _FiatSelectionViewState();
+}
+
+class _FiatSelectionViewState extends State {
+ late TextEditingController _searchController;
+ final _searchFocusNode = FocusNode();
+
+ late final List fiats;
+ late List _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()!.background,
+ appBar: AppBar(
+ leading: AppBarBackButton(
+ onPressed: () async {
+ if (FocusScope.of(context).hasFocus) {
+ FocusScope.of(context).unfocus();
+ await Future.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()!
+ .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()!
+ .textSubtitle1,
+ ),
+ ),
+ ],
+ ),
+ ),
+ ],
+ ),
+ ),
+ ),
+ );
+ },
+ ),
+ ),
+ ),
+ ],
+ ),
+ );
+ }
+}
diff --git a/lib/pages/exchange_view/confirm_change_now_send.dart b/lib/pages/exchange_view/confirm_change_now_send.dart
index 2abe0d347..808a0417f 100644
--- a/lib/pages/exchange_view/confirm_change_now_send.dart
+++ b/lib/pages/exchange_view/confirm_change_now_send.dart
@@ -61,6 +61,8 @@ class _ConfirmChangeNowSendViewState
late final String routeOnSuccessName;
late final Trade trade;
+ final isDesktop = Util.isDesktop;
+
Future _attemptSend(BuildContext context) async {
unawaited(
showDialog(
@@ -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()!.background,
appBar: AppBar(
backgroundColor:
- Theme.of(context).extension()!.background,
+ Theme.of(context).extension()!.backgroundAppBar,
leading: AppBarBackButton(
onPressed: () async {
// if (FocusScope.of(context).hasFocus) {
diff --git a/lib/pages/exchange_view/sub_widgets/rate_type_toggle.dart b/lib/pages/exchange_view/sub_widgets/rate_type_toggle.dart
index 284d4eddd..8c82bbbc4 100644
--- a/lib/pages/exchange_view/sub_widgets/rate_type_toggle.dart
+++ b/lib/pages/exchange_view/sub_widgets/rate_type_toggle.dart
@@ -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()!.textFieldDefaultBG,
+ onColor: isDesktop
+ ? Theme.of(context)
+ .extension()!
+ .rateTypeToggleDesktopColorOn
+ : Theme.of(context).extension()!.rateTypeToggleColorOn,
offColor: isDesktop
- ? Theme.of(context).extension()!.buttonBackSecondary
- : Theme.of(context).extension()!.popupBG,
+ ? Theme.of(context)
+ .extension()!
+ .rateTypeToggleDesktopColorOff
+ : Theme.of(context).extension()!.rateTypeToggleColorOff,
decoration: BoxDecoration(
borderRadius: BorderRadius.circular(
Constants.size.circularBorderRadius,
diff --git a/lib/pages/home_view/home_view.dart b/lib/pages/home_view/home_view.dart
index 49317193e..5a09203cb 100644
--- a/lib/pages/home_view/home_view.dart
+++ b/lib/pages/home_view/home_view.dart
@@ -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 {
bool _exitEnabled = false;
final _exchangeDataLoadingService = ExchangeDataLoadingService();
+ // final _buyDataLoadingService = BuyDataLoadingService();
Future _onWillPop() async {
// go to home view when tapping back on the main exchange view
@@ -92,6 +94,26 @@ class _HomeViewState extends ConsumerState {
}
}
+ // 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 _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 {
),
],
),
- // const BuyView(),
+ if (Constants.enableBuy)
+ // Stack(
+ // children: [
+ const BuyView(),
+ // BuyLoadingOverlayView(
+ // unawaitedLoad: _loadSimplexData,
+ // ),
+ // ],
];
ref.read(notificationsProvider).startCheckingWatchedNotifications();
@@ -301,35 +330,31 @@ class _HomeViewState extends ConsumerState {
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,
- // ),
- // ),
],
),
),
diff --git a/lib/pages/home_view/sub_widgets/home_view_button_bar.dart b/lib/pages/home_view/sub_widgets/home_view_button_bar.dart
index 79328a29e..2992be514 100644
--- a/lib/pages/home_view/sub_widgets/home_view_button_bar.dart
+++ b/lib/pages/home_view/sub_widgets/home_view_button_bar.dart
@@ -127,37 +127,48 @@ class _HomeViewButtonBarState extends ConsumerState {
),
),
),
- // TODO: Do not delete this code.
- // only temporarily disabled
- // SizedBox(
- // width: 8,
- // ),
- // Expanded(
- // child: TextButton(
- // style: ButtonStyle(
- // minimumSize: MaterialStateProperty.all(Size(46, 36)),
- // backgroundColor: MaterialStateProperty.all(
- // 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()!.accentColorDark
- // ),
- // ),
- // ),
- // ),
+ const SizedBox(
+ width: 8,
+ ),
+ Expanded(
+ child: TextButton(
+ style: selectedIndex == 2
+ ? Theme.of(context)
+ .extension()!
+ .getPrimaryEnabledButtonStyle(context)!
+ .copyWith(
+ minimumSize:
+ MaterialStateProperty.all(const Size(46, 36)),
+ )
+ : Theme.of(context)
+ .extension()!
+ .getSecondaryEnabledButtonStyle(context)!
+ .copyWith(
+ minimumSize:
+ MaterialStateProperty.all(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()!
+ .buttonTextPrimary
+ : Theme.of(context)
+ .extension()!
+ .buttonTextSecondary,
+ ),
+ ),
+ ),
+ ),
],
);
}
diff --git a/lib/pages/stack_privacy_calls.dart b/lib/pages/stack_privacy_calls.dart
index 1a5e23e98..e120d238b 100644
--- a/lib/pages/stack_privacy_calls.dart
+++ b/lib/pages/stack_privacy_calls.dart
@@ -232,6 +232,8 @@ class _StackPrivacyCalls extends ConsumerState {
if (isEasy) {
unawaited(ExchangeDataLoadingService()
.loadAll(ref));
+ // unawaited(
+ // BuyDataLoadingService().loadAll(ref));
ref
.read(priceAnd24hChangeNotifierProvider)
.start(true);
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 d8ca42192..c663b8ba0 100644
--- a/lib/pages/wallet_view/sub_widgets/wallet_navigation_bar.dart
+++ b/lib/pages/wallet_view/sub_widgets/wallet_navigation_bar.dart
@@ -416,39 +416,47 @@ class _WalletNavigationBarState extends ConsumerState {
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()!.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,
+ ),
],
),
),
diff --git a/lib/pages_desktop_specific/desktop_buy/desktop_buy_view.dart b/lib/pages_desktop_specific/desktop_buy/desktop_buy_view.dart
new file mode 100644
index 000000000..45f9cc0be
--- /dev/null
+++ b/lib/pages_desktop_specific/desktop_buy/desktop_buy_view.dart
@@ -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 createState() => _DesktopBuyViewState();
+}
+
+class _DesktopBuyViewState extends State {
+ @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(),
+ // ),
+ // ],
+ // ),
+ // ),
+ ],
+ ),
+ ),
+ );
+ }
+}
diff --git a/lib/pages_desktop_specific/desktop_home_view.dart b/lib/pages_desktop_specific/desktop_home_view.dart
index 3ef490512..e9092fe2d 100644
--- a/lib/pages_desktop_specific/desktop_home_view.dart
+++ b/lib/pages_desktop_specific/desktop_home_view.dart
@@ -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 {
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,
diff --git a/lib/pages_desktop_specific/desktop_menu.dart b/lib/pages_desktop_specific/desktop_menu.dart
index 96b4e6638..b705a33be 100644
--- a/lib/pages_desktop_specific/desktop_menu.dart
+++ b/lib/pages_desktop_specific/desktop_menu.dart
@@ -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 {
double _width = expandedWidth;
+ // final _buyDataLoadingService = BuyDataLoadingService();
+
void updateSelectedMenuItem(DesktopMenuItemId idKey) {
widget.onSelectionWillChange?.call(idKey);
@@ -73,6 +76,7 @@ class _DesktopMenuState extends ConsumerState {
DMIController(),
DMIController(),
DMIController(),
+ DMIController(),
];
super.initState();
@@ -157,13 +161,24 @@ class _DesktopMenuState extends ConsumerState {
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 {
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 {
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 {
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 {
label: "About",
value: DesktopMenuItemId.about,
onChanged: updateSelectedMenuItem,
- controller: controllers[6],
+ controller: controllers[7],
),
const Spacer(),
DesktopMenuItem(
@@ -221,7 +236,7 @@ class _DesktopMenuState extends ConsumerState {
// exit(0);
SystemNavigator.pop();
},
- controller: controllers[7],
+ controller: controllers[8],
),
],
),
diff --git a/lib/pages_desktop_specific/desktop_menu_item.dart b/lib/pages_desktop_specific/desktop_menu_item.dart
index 804268797..3e2fa015c 100644
--- a/lib/pages_desktop_specific/desktop_menu_item.dart
+++ b/lib/pages_desktop_specific/desktop_menu_item.dart
@@ -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()!.accentColorDark
+ : Theme.of(context)
+ .extension()!
+ .accentColorDark
+ .withOpacity(0.8),
+ );
+ }
+}
+
class DesktopNotificationsIcon extends ConsumerWidget {
const DesktopNotificationsIcon({Key? key}) : super(key: key);
diff --git a/lib/pages_desktop_specific/my_stack_view/wallet_view/desktop_wallet_view.dart b/lib/pages_desktop_specific/my_stack_view/wallet_view/desktop_wallet_view.dart
index 5c1e048a0..c94b61550 100644
--- a/lib/pages_desktop_specific/my_stack_view/wallet_view/desktop_wallet_view.dart
+++ b/lib/pages_desktop_specific/my_stack_view/wallet_view/desktop_wallet_view.dart
@@ -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 {
late final bool _shouldDisableAutoSyncOnLogOut;
- final _cnLoadingService = ExchangeDataLoadingService();
-
Future onBackPressed() async {
await _logout();
if (mounted) {
@@ -96,87 +88,6 @@ class _DesktopWalletViewState extends ConsumerState {
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(
- context: context,
- builder: (_) => const StackOkDialog(
- title: "Exchange not available for Epic Cash",
- ),
- );
- } else if (coin.name.endsWith("TestNet")) {
- await showDialog(
- 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 attemptAnonymize() async {
final managerProvider = ref
.read(walletsChangeNotifierProvider)
diff --git a/lib/providers/buy/buy_form_state_provider.dart b/lib/providers/buy/buy_form_state_provider.dart
new file mode 100644
index 000000000..2a0dc719c
--- /dev/null
+++ b/lib/providers/buy/buy_form_state_provider.dart
@@ -0,0 +1,6 @@
+import 'package:flutter_riverpod/flutter_riverpod.dart';
+import 'package:stackwallet/models/buy/buy_form_state.dart';
+
+final buyFormStateProvider = ChangeNotifierProvider(
+ (ref) => BuyFormState(),
+);
diff --git a/lib/providers/buy/simplex_initial_load_status.dart b/lib/providers/buy/simplex_initial_load_status.dart
new file mode 100644
index 000000000..9571f5b64
--- /dev/null
+++ b/lib/providers/buy/simplex_initial_load_status.dart
@@ -0,0 +1,11 @@
+import 'package:flutter_riverpod/flutter_riverpod.dart';
+
+enum SimplexLoadStatus {
+ waiting,
+ loading,
+ success,
+ failed,
+}
+
+final simplexLoadStatusStateProvider =
+ StateProvider((ref) => SimplexLoadStatus.waiting);
diff --git a/lib/providers/buy/simplex_provider.dart b/lib/providers/buy/simplex_provider.dart
new file mode 100644
index 000000000..d15d335a5
--- /dev/null
+++ b/lib/providers/buy/simplex_provider.dart
@@ -0,0 +1,6 @@
+import 'package:flutter_riverpod/flutter_riverpod.dart';
+import 'package:stackwallet/models/buy/simplex/simplex.dart';
+
+final simplexProvider = Provider(
+ (ref) => Simplex(),
+);
diff --git a/lib/providers/providers.dart b/lib/providers/providers.dart
index 5cef38830..210d36420 100644
--- a/lib/providers/providers.dart
+++ b/lib/providers/providers.dart
@@ -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';
diff --git a/lib/route_generator.dart b/lib/route_generator.dart
index 8ebdd36a2..af88499ba 100644
--- a/lib/route_generator.dart
+++ b/lib/route_generator.dart
@@ -2,6 +2,7 @@ import 'package:decimal/decimal.dart';
import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
+import 'package:stackwallet/models/buy/response_objects/quote.dart';
import 'package:stackwallet/models/contact_address_entry.dart';
import 'package:stackwallet/models/exchange/incomplete_exchange.dart';
import 'package:stackwallet/models/exchange/response_objects/trade.dart';
@@ -21,6 +22,7 @@ import 'package:stackwallet/pages/address_book_views/subviews/address_book_filte
import 'package:stackwallet/pages/address_book_views/subviews/contact_details_view.dart';
import 'package:stackwallet/pages/address_book_views/subviews/edit_contact_address_view.dart';
import 'package:stackwallet/pages/address_book_views/subviews/edit_contact_name_emoji_view.dart';
+import 'package:stackwallet/pages/buy_view/buy_quote_preview.dart';
import 'package:stackwallet/pages/exchange_view/choose_from_stack_view.dart';
import 'package:stackwallet/pages/exchange_view/edit_trade_note_view.dart';
import 'package:stackwallet/pages/exchange_view/exchange_loading_overlay.dart';
@@ -87,6 +89,8 @@ import 'package:stackwallet/pages/wallet_view/transaction_views/transaction_sear
import 'package:stackwallet/pages/wallet_view/wallet_view.dart';
import 'package:stackwallet/pages/wallets_view/wallets_view.dart';
import 'package:stackwallet/pages_desktop_specific/address_book_view/desktop_address_book.dart';
+// import 'package:stackwallet/pages_desktop_specific/desktop_exchange/desktop_all_buys_view.dart';
+import 'package:stackwallet/pages_desktop_specific/desktop_buy/desktop_buy_view.dart';
import 'package:stackwallet/pages_desktop_specific/desktop_exchange/desktop_all_trades_view.dart';
import 'package:stackwallet/pages_desktop_specific/desktop_exchange/desktop_exchange_view.dart';
import 'package:stackwallet/pages_desktop_specific/desktop_home_view.dart';
@@ -1045,6 +1049,20 @@ class RouteGenerator {
}
return _routeError("${settings.name} invalid args: ${args.toString()}");
+ case BuyQuotePreviewView.routeName:
+ if (args is SimplexQuote) {
+ return getRoute(
+ shouldUseMaterialRoute: useMaterialPageRoute,
+ builder: (_) => BuyQuotePreviewView(
+ quote: args,
+ ),
+ settings: RouteSettings(
+ name: settings.name,
+ ),
+ );
+ }
+ return _routeError("${settings.name} invalid args: ${args.toString()}");
+
// == Desktop specific routes ============================================
case CreatePasswordView.routeName:
if (args is bool) {
@@ -1107,6 +1125,12 @@ class RouteGenerator {
builder: (_) => const DesktopExchangeView(),
settings: RouteSettings(name: settings.name));
+ case DesktopBuyView.routeName:
+ return getRoute(
+ shouldUseMaterialRoute: useMaterialPageRoute,
+ builder: (_) => const DesktopBuyView(),
+ settings: RouteSettings(name: settings.name));
+
case DesktopAllTradesView.routeName:
return getRoute(
shouldUseMaterialRoute: useMaterialPageRoute,
diff --git a/lib/services/buy/buy.dart b/lib/services/buy/buy.dart
new file mode 100644
index 000000000..84e38487c
--- /dev/null
+++ b/lib/services/buy/buy.dart
@@ -0,0 +1,3 @@
+abstract class Buy {
+ String get name;
+}
diff --git a/lib/services/buy/buy_data_loading_service.dart b/lib/services/buy/buy_data_loading_service.dart
new file mode 100644
index 000000000..93999c221
--- /dev/null
+++ b/lib/services/buy/buy_data_loading_service.dart
@@ -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 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 _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;
+// }
+// }
+// }
diff --git a/lib/services/buy/buy_response.dart b/lib/services/buy/buy_response.dart
new file mode 100644
index 000000000..3198d0a19
--- /dev/null
+++ b/lib/services/buy/buy_response.dart
@@ -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 {
+ final T? value;
+ final BuyException? exception;
+
+ BuyResponse({this.value, this.exception});
+
+ @override
+ String toString() {
+ return "{error: $exception, value: $value}";
+ }
+}
diff --git a/lib/services/buy/simplex/simplex_api.dart b/lib/services/buy/simplex/simplex_api.dart
new file mode 100644
index 000000000..9e01609bf
--- /dev/null
+++ b/lib/services/buy/simplex/simplex_api.dart
@@ -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? params) {
+ if (scheme == "http") {
+ return Uri.http(authority, path, params);
+ }
+ return Uri.https(authority, path, params);
+ }
+
+ Future>> getSupportedCryptos() async {
+ try {
+ Map headers = {
+ 'Content-Type': 'application/x-www-form-urlencoded',
+ };
+ Map 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> _parseSupportedCryptos(dynamic jsonArray) {
+ try {
+ List cryptos = [];
+ List 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>> getSupportedFiats() async {
+ try {
+ Map headers = {
+ 'Content-Type': 'application/x-www-form-urlencoded',
+ };
+ Map 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> _parseSupportedFiats(dynamic jsonArray) {
+ try {
+ List cryptos = [];
+ List 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> getQuote(SimplexQuote quote) async {
+ try {
+ await _prefs.init();
+ String? userID = _prefs.userID;
+
+ Map headers = {
+ 'Content-Type': 'application/x-www-form-urlencoded',
+ };
+ Map 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 _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> 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 headers = {
+ 'Content-Type': 'application/x-www-form-urlencoded',
+ };
+ Map 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> redirect(SimplexOrder order) async {
+ try {
+ Map headers = {
+ 'Content-Type': 'application/x-www-form-urlencoded',
+ };
+ Map 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")}";
+}
diff --git a/lib/utilities/assets.dart b/lib/utilities/assets.dart
index 84ae19483..311a267aa 100644
--- a/lib/utilities/assets.dart
+++ b/lib/utilities/assets.dart
@@ -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()!.themeType.name}/buy.svg";
+ String get buy => "assets/svg/light/buy-coins-icon.svg";
+
+ String simplexLogo(BuildContext context) {
+ return (Theme.of(context).extension()!.themeType ==
+ ThemeType.dark ||
+ Theme.of(context).extension()!.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 {
diff --git a/lib/utilities/constants.dart b/lib/utilities/constants.dart
index e78d7a76f..ba128ab35 100644
--- a/lib/utilities/constants.dart
+++ b/lib/utilities/constants.dart
@@ -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;
diff --git a/lib/utilities/enums/coin_enum.dart b/lib/utilities/enums/coin_enum.dart
index df586ebf5..e50e51b44 100644
--- a/lib/utilities/enums/coin_enum.dart
+++ b/lib/utilities/enums/coin_enum.dart
@@ -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":
diff --git a/lib/utilities/enums/fiat_enum.dart b/lib/utilities/enums/fiat_enum.dart
new file mode 100644
index 000000000..2a7303c86
--- /dev/null
+++ b/lib/utilities/enums/fiat_enum.dart
@@ -0,0 +1,1156 @@
+enum Fiats {
+ AED,
+ AFN,
+ ALL,
+ AMD,
+ ANG,
+ AOA,
+ ARS,
+ AUD,
+ AWG,
+ AZN,
+ BAM,
+ BBD,
+ BDT,
+ BGN,
+ BHD,
+ BIF,
+ BMD,
+ BND,
+ BOB,
+ BRL,
+ BSD,
+ BTN,
+ BWP,
+ BYN,
+ BZD,
+ CAD,
+ CDF,
+ CHF,
+ CLP,
+ CNY,
+ COP,
+ CRC,
+ CUC,
+ CUP,
+ CVE,
+ CZK,
+ DJF,
+ DKK,
+ DOP,
+ DZD,
+ EGP,
+ ERN,
+ ETB,
+ EUR,
+ FJD,
+ FKP,
+ GBP,
+ GEL,
+ GGP,
+ GHS,
+ GIP,
+ GMD,
+ GNF,
+ GTQ,
+ GYD,
+ HKD,
+ HNL,
+ HRK,
+ HTG,
+ HUF,
+ IDR,
+ ILS,
+ IMP,
+ INR,
+ IQD,
+ IRR,
+ ISK,
+ JEP,
+ JMD,
+ JOD,
+ JPY,
+ KES,
+ KGS,
+ KHR,
+ KMF,
+ KPW,
+ KRW,
+ KWD,
+ KYD,
+ KZT,
+ LAK,
+ LBP,
+ LKR,
+ LRD,
+ LSL,
+ LYD,
+ MAD,
+ MDL,
+ MGA,
+ MKD,
+ MMK,
+ MNT,
+ MOP,
+ MRU,
+ MUR,
+ MVR,
+ MWK,
+ MXN,
+ MYR,
+ MZN,
+ NAD,
+ NGN,
+ NIO,
+ NOK,
+ NPR,
+ NZD,
+ OMR,
+ PAB,
+ PEN,
+ PGK,
+ PHP,
+ PKR,
+ PLN,
+ PYG,
+ QAR,
+ RON,
+ RSD,
+ RUB,
+ RWF,
+ SAR,
+ SBD,
+ SCR,
+ SDG,
+ SEK,
+ SGD,
+ SHP,
+ SLL,
+ SOS,
+ SPL,
+ SRD,
+ STN,
+ SVC,
+ SYP,
+ SZL,
+ THB,
+ TJS,
+ TMT,
+ TND,
+ TOP,
+ TRY,
+ TTD,
+ TVD,
+ TWD,
+ TZS,
+ UAH,
+ UGX,
+ USD,
+ UYU,
+ UZS,
+ VEF,
+ VND,
+ VUV,
+ WST,
+ XAF,
+ XCD,
+ XDR,
+ XOF,
+ XPF,
+ YER,
+ ZAR,
+ ZMW,
+ ZWD,
+}
+
+extension FiatExt on Fiats {
+ String get ticker {
+ switch (this) {
+ case Fiats.AED:
+ return 'AED';
+ case Fiats.AFN:
+ return 'AFN';
+ case Fiats.ALL:
+ return 'ALL';
+ case Fiats.AMD:
+ return 'AMD';
+ case Fiats.ANG:
+ return 'ANG';
+ case Fiats.AOA:
+ return 'AOA';
+ case Fiats.ARS:
+ return 'ARS';
+ case Fiats.AUD:
+ return 'AUD';
+ case Fiats.AWG:
+ return 'AWG';
+ case Fiats.AZN:
+ return 'AZN';
+ case Fiats.BAM:
+ return 'BAM';
+ case Fiats.BBD:
+ return 'BBD';
+ case Fiats.BDT:
+ return 'BDT';
+ case Fiats.BGN:
+ return 'BGN';
+ case Fiats.BHD:
+ return 'BHD';
+ case Fiats.BIF:
+ return 'BIF';
+ case Fiats.BMD:
+ return 'BMD';
+ case Fiats.BND:
+ return 'BND';
+ case Fiats.BOB:
+ return 'BOB';
+ case Fiats.BRL:
+ return 'BRL';
+ case Fiats.BSD:
+ return 'BSD';
+ case Fiats.BTN:
+ return 'BTN';
+ case Fiats.BWP:
+ return 'BWP';
+ case Fiats.BYN:
+ return 'BYN';
+ case Fiats.BZD:
+ return 'BZD';
+ case Fiats.CAD:
+ return 'CAD';
+ case Fiats.CDF:
+ return 'CDF';
+ case Fiats.CHF:
+ return 'CHF';
+ case Fiats.CLP:
+ return 'CLP';
+ case Fiats.CNY:
+ return 'CNY';
+ case Fiats.COP:
+ return 'COP';
+ case Fiats.CRC:
+ return 'CRC';
+ case Fiats.CUC:
+ return 'CUC';
+ case Fiats.CUP:
+ return 'CUP';
+ case Fiats.CVE:
+ return 'CVE';
+ case Fiats.CZK:
+ return 'CZK';
+ case Fiats.DJF:
+ return 'DJF';
+ case Fiats.DKK:
+ return 'DKK';
+ case Fiats.DOP:
+ return 'DOP';
+ case Fiats.DZD:
+ return 'DZD';
+ case Fiats.EGP:
+ return 'EGP';
+ case Fiats.ERN:
+ return 'ERN';
+ case Fiats.ETB:
+ return 'ETB';
+ case Fiats.EUR:
+ return 'EUR';
+ case Fiats.FJD:
+ return 'FJD';
+ case Fiats.FKP:
+ return 'FKP';
+ case Fiats.GBP:
+ return 'GBP';
+ case Fiats.GEL:
+ return 'GEL';
+ case Fiats.GGP:
+ return 'GGP';
+ case Fiats.GHS:
+ return 'GHS';
+ case Fiats.GIP:
+ return 'GIP';
+ case Fiats.GMD:
+ return 'GMD';
+ case Fiats.GNF:
+ return 'GNF';
+ case Fiats.GTQ:
+ return 'GTQ';
+ case Fiats.GYD:
+ return 'GYD';
+ case Fiats.HKD:
+ return 'HKD';
+ case Fiats.HNL:
+ return 'HNL';
+ case Fiats.HRK:
+ return 'HRK';
+ case Fiats.HTG:
+ return 'HTG';
+ case Fiats.HUF:
+ return 'HUF';
+ case Fiats.IDR:
+ return 'IDR';
+ case Fiats.ILS:
+ return 'ILS';
+ case Fiats.IMP:
+ return 'IMP';
+ case Fiats.INR:
+ return 'INR';
+ case Fiats.IQD:
+ return 'IQD';
+ case Fiats.IRR:
+ return 'IRR';
+ case Fiats.ISK:
+ return 'ISK';
+ case Fiats.JEP:
+ return 'JEP';
+ case Fiats.JMD:
+ return 'JMD';
+ case Fiats.JOD:
+ return 'JOD';
+ case Fiats.JPY:
+ return 'JPY';
+ case Fiats.KES:
+ return 'KES';
+ case Fiats.KGS:
+ return 'KGS';
+ case Fiats.KHR:
+ return 'KHR';
+ case Fiats.KMF:
+ return 'KMF';
+ case Fiats.KPW:
+ return 'KPW';
+ case Fiats.KRW:
+ return 'KRW';
+ case Fiats.KWD:
+ return 'KWD';
+ case Fiats.KYD:
+ return 'KYD';
+ case Fiats.KZT:
+ return 'KZT';
+ case Fiats.LAK:
+ return 'LAK';
+ case Fiats.LBP:
+ return 'LBP';
+ case Fiats.LKR:
+ return 'LKR';
+ case Fiats.LRD:
+ return 'LRD';
+ case Fiats.LSL:
+ return 'LSL';
+ case Fiats.LYD:
+ return 'LYD';
+ case Fiats.MAD:
+ return 'MAD';
+ case Fiats.MDL:
+ return 'MDL';
+ case Fiats.MGA:
+ return 'MGA';
+ case Fiats.MKD:
+ return 'MKD';
+ case Fiats.MMK:
+ return 'MMK';
+ case Fiats.MNT:
+ return 'MNT';
+ case Fiats.MOP:
+ return 'MOP';
+ case Fiats.MRU:
+ return 'MRU';
+ case Fiats.MUR:
+ return 'MUR';
+ case Fiats.MVR:
+ return 'MVR';
+ case Fiats.MWK:
+ return 'MWK';
+ case Fiats.MXN:
+ return 'MXN';
+ case Fiats.MYR:
+ return 'MYR';
+ case Fiats.MZN:
+ return 'MZN';
+ case Fiats.NAD:
+ return 'NAD';
+ case Fiats.NGN:
+ return 'NGN';
+ case Fiats.NIO:
+ return 'NIO';
+ case Fiats.NOK:
+ return 'NOK';
+ case Fiats.NPR:
+ return 'NPR';
+ case Fiats.NZD:
+ return 'NZD';
+ case Fiats.OMR:
+ return 'OMR';
+ case Fiats.PAB:
+ return 'PAB';
+ case Fiats.PEN:
+ return 'PEN';
+ case Fiats.PGK:
+ return 'PGK';
+ case Fiats.PHP:
+ return 'PHP';
+ case Fiats.PKR:
+ return 'PKR';
+ case Fiats.PLN:
+ return 'PLN';
+ case Fiats.PYG:
+ return 'PYG';
+ case Fiats.QAR:
+ return 'QAR';
+ case Fiats.RON:
+ return 'RON';
+ case Fiats.RSD:
+ return 'RSD';
+ case Fiats.RUB:
+ return 'RUB';
+ case Fiats.RWF:
+ return 'RWF';
+ case Fiats.SAR:
+ return 'SAR';
+ case Fiats.SBD:
+ return 'SBD';
+ case Fiats.SCR:
+ return 'SCR';
+ case Fiats.SDG:
+ return 'SDG';
+ case Fiats.SEK:
+ return 'SEK';
+ case Fiats.SGD:
+ return 'SGD';
+ case Fiats.SHP:
+ return 'SHP';
+ case Fiats.SLL:
+ return 'SLL';
+ case Fiats.SOS:
+ return 'SOS';
+ case Fiats.SPL:
+ return 'SPL';
+ case Fiats.SRD:
+ return 'SRD';
+ case Fiats.STN:
+ return 'STN';
+ case Fiats.SVC:
+ return 'SVC';
+ case Fiats.SYP:
+ return 'SYP';
+ case Fiats.SZL:
+ return 'SZL';
+ case Fiats.THB:
+ return 'THB';
+ case Fiats.TJS:
+ return 'TJS';
+ case Fiats.TMT:
+ return 'TMT';
+ case Fiats.TND:
+ return 'TND';
+ case Fiats.TOP:
+ return 'TOP';
+ case Fiats.TRY:
+ return 'TRY';
+ case Fiats.TTD:
+ return 'TTD';
+ case Fiats.TVD:
+ return 'TVD';
+ case Fiats.TWD:
+ return 'TWD';
+ case Fiats.TZS:
+ return 'TZS';
+ case Fiats.UAH:
+ return 'UAH';
+ case Fiats.UGX:
+ return 'UGX';
+ case Fiats.USD:
+ return 'USD';
+ case Fiats.UYU:
+ return 'UYU';
+ case Fiats.UZS:
+ return 'UZS';
+ case Fiats.VEF:
+ return 'VEF';
+ case Fiats.VND:
+ return 'VND';
+ case Fiats.VUV:
+ return 'VUV';
+ case Fiats.WST:
+ return 'WST';
+ case Fiats.XAF:
+ return 'XAF';
+ case Fiats.XCD:
+ return 'XCD';
+ case Fiats.XDR:
+ return 'XDR';
+ case Fiats.XOF:
+ return 'XOF';
+ case Fiats.XPF:
+ return 'XPF';
+ case Fiats.YER:
+ return 'YER';
+ case Fiats.ZAR:
+ return 'ZAR';
+ case Fiats.ZMW:
+ return 'ZMW';
+ case Fiats.ZWD:
+ return 'ZWD';
+ }
+ }
+
+ String get prettyName {
+ switch (this) {
+ case Fiats.AED:
+ return 'United Arab Emirates Dirham';
+ case Fiats.AFN:
+ return 'Afghanistan Afghani';
+ case Fiats.ALL:
+ return 'Albania Lek';
+ case Fiats.AMD:
+ return 'Armenia Dram';
+ case Fiats.ANG:
+ return 'Netherlands Antilles Guilder';
+ case Fiats.AOA:
+ return 'Angola Kwanza';
+ case Fiats.ARS:
+ return 'Argentina Peso';
+ case Fiats.AUD:
+ return 'Australia Dollar';
+ case Fiats.AWG:
+ return 'Aruba Guilder';
+ case Fiats.AZN:
+ return 'Azerbaijan Manat';
+ case Fiats.BAM:
+ return 'Bosnia and Herzegovina Convertible Mark';
+ case Fiats.BBD:
+ return 'Barbados Dollar';
+ case Fiats.BDT:
+ return 'Bangladesh Taka';
+ case Fiats.BGN:
+ return 'Bulgaria Lev';
+ case Fiats.BHD:
+ return 'Bahrain Dinar';
+ case Fiats.BIF:
+ return 'Burundi Franc';
+ case Fiats.BMD:
+ return 'Bermuda Dollar';
+ case Fiats.BND:
+ return 'Brunei Darussalam Dollar';
+ case Fiats.BOB:
+ return 'Bolivia Bolíviano';
+ case Fiats.BRL:
+ return 'Brazil Real';
+ case Fiats.BSD:
+ return 'Bahamas Dollar';
+ case Fiats.BTN:
+ return 'Bhutan Ngultrum';
+ case Fiats.BWP:
+ return 'Botswana Pula';
+ case Fiats.BYN:
+ return 'Belarus Ruble';
+ case Fiats.BZD:
+ return 'Belize Dollar';
+ case Fiats.CAD:
+ return 'Canada Dollar';
+ case Fiats.CDF:
+ return 'Congo/Kinshasa Franc';
+ case Fiats.CHF:
+ return 'Switzerland Franc';
+ case Fiats.CLP:
+ return 'Chile Peso';
+ case Fiats.CNY:
+ return 'China Yuan Renminbi';
+ case Fiats.COP:
+ return 'Colombia Peso';
+ case Fiats.CRC:
+ return 'Costa Rica Colon';
+ case Fiats.CUC:
+ return 'Cuba Convertible Peso';
+ case Fiats.CUP:
+ return 'Cuba Peso';
+ case Fiats.CVE:
+ return 'Cape Verde Escudo';
+ case Fiats.CZK:
+ return 'Czech Republic Koruna';
+ case Fiats.DJF:
+ return 'Djibouti Franc';
+ case Fiats.DKK:
+ return 'Denmark Krone';
+ case Fiats.DOP:
+ return 'Dominican Republic Peso';
+ case Fiats.DZD:
+ return 'Algeria Dinar';
+ case Fiats.EGP:
+ return 'Egypt Pound';
+ case Fiats.ERN:
+ return 'Eritrea Nakfa';
+ case Fiats.ETB:
+ return 'Ethiopia Birr';
+ case Fiats.EUR:
+ return 'Euro Member Countries';
+ case Fiats.FJD:
+ return 'Fiji Dollar';
+ case Fiats.FKP:
+ return 'Falkland Islands (Malvinas) Pound';
+ case Fiats.GBP:
+ return 'United Kingdom Pound';
+ case Fiats.GEL:
+ return 'Georgia Lari';
+ case Fiats.GGP:
+ return 'Guernsey Pound';
+ case Fiats.GHS:
+ return 'Ghana Cedi';
+ case Fiats.GIP:
+ return 'Gibraltar Pound';
+ case Fiats.GMD:
+ return 'Gambia Dalasi';
+ case Fiats.GNF:
+ return 'Guinea Franc';
+ case Fiats.GTQ:
+ return 'Guatemala Quetzal';
+ case Fiats.GYD:
+ return 'Guyana Dollar';
+ case Fiats.HKD:
+ return 'Hong Kong Dollar';
+ case Fiats.HNL:
+ return 'Honduras Lempira';
+ case Fiats.HRK:
+ return 'Croatia Kuna';
+ case Fiats.HTG:
+ return 'Haiti Gourde';
+ case Fiats.HUF:
+ return 'Hungary Forint';
+ case Fiats.IDR:
+ return 'Indonesia Rupiah';
+ case Fiats.ILS:
+ return 'Israel Shekel';
+ case Fiats.IMP:
+ return 'Isle of Man Pound';
+ case Fiats.INR:
+ return 'India Rupee';
+ case Fiats.IQD:
+ return 'Iraq Dinar';
+ case Fiats.IRR:
+ return 'Iran Rial';
+ case Fiats.ISK:
+ return 'Iceland Krona';
+ case Fiats.JEP:
+ return 'Jersey Pound';
+ case Fiats.JMD:
+ return 'Jamaica Dollar';
+ case Fiats.JOD:
+ return 'Jordan Dinar';
+ case Fiats.JPY:
+ return 'Japan Yen';
+ case Fiats.KES:
+ return 'Kenya Shilling';
+ case Fiats.KGS:
+ return 'Kyrgyzstan Som';
+ case Fiats.KHR:
+ return 'Cambodia Riel';
+ case Fiats.KMF:
+ return 'Comorian Franc';
+ case Fiats.KPW:
+ return 'Korea (North) Won';
+ case Fiats.KRW:
+ return 'Korea (South) Won';
+ case Fiats.KWD:
+ return 'Kuwait Dinar';
+ case Fiats.KYD:
+ return 'Cayman Islands Dollar';
+ case Fiats.KZT:
+ return 'Kazakhstan Tenge';
+ case Fiats.LAK:
+ return 'Laos Kip';
+ case Fiats.LBP:
+ return 'Lebanon Pound';
+ case Fiats.LKR:
+ return 'Sri Lanka Rupee';
+ case Fiats.LRD:
+ return 'Liberia Dollar';
+ case Fiats.LSL:
+ return 'Lesotho Loti';
+ case Fiats.LYD:
+ return 'Libya Dinar';
+ case Fiats.MAD:
+ return 'Morocco Dirham';
+ case Fiats.MDL:
+ return 'Moldova Leu';
+ case Fiats.MGA:
+ return 'Madagascar Ariary';
+ case Fiats.MKD:
+ return 'Macedonia Denar';
+ case Fiats.MMK:
+ return 'Myanmar (Burma) Kyat';
+ case Fiats.MNT:
+ return 'Mongolia Tughrik';
+ case Fiats.MOP:
+ return 'Macau Pataca';
+ case Fiats.MRU:
+ return 'Mauritania Ouguiya';
+ case Fiats.MUR:
+ return 'Mauritius Rupee';
+ case Fiats.MVR:
+ return 'Maldives (Maldive Islands) Rufiyaa';
+ case Fiats.MWK:
+ return 'Malawi Kwacha';
+ case Fiats.MXN:
+ return 'Mexico Peso';
+ case Fiats.MYR:
+ return 'Malaysia Ringgit';
+ case Fiats.MZN:
+ return 'Mozambique Metical';
+ case Fiats.NAD:
+ return 'Namibia Dollar';
+ case Fiats.NGN:
+ return 'Nigeria Naira';
+ case Fiats.NIO:
+ return 'Nicaragua Cordoba';
+ case Fiats.NOK:
+ return 'Norway Krone';
+ case Fiats.NPR:
+ return 'Nepal Rupee';
+ case Fiats.NZD:
+ return 'New Zealand Dollar';
+ case Fiats.OMR:
+ return 'Oman Rial';
+ case Fiats.PAB:
+ return 'Panama Balboa';
+ case Fiats.PEN:
+ return 'Peru Sol';
+ case Fiats.PGK:
+ return 'Papua New Guinea Kina';
+ case Fiats.PHP:
+ return 'Philippines Peso';
+ case Fiats.PKR:
+ return 'Pakistan Rupee';
+ case Fiats.PLN:
+ return 'Poland Zloty';
+ case Fiats.PYG:
+ return 'Paraguay Guarani';
+ case Fiats.QAR:
+ return 'Qatar Riyal';
+ case Fiats.RON:
+ return 'Romania Leu';
+ case Fiats.RSD:
+ return 'Serbia Dinar';
+ case Fiats.RUB:
+ return 'Russia Ruble';
+ case Fiats.RWF:
+ return 'Rwanda Franc';
+ case Fiats.SAR:
+ return 'Saudi Arabia Riyal';
+ case Fiats.SBD:
+ return 'Solomon Islands Dollar';
+ case Fiats.SCR:
+ return 'Seychelles Rupee';
+ case Fiats.SDG:
+ return 'Sudan Pound';
+ case Fiats.SEK:
+ return 'Sweden Krona';
+ case Fiats.SGD:
+ return 'Singapore Dollar';
+ case Fiats.SHP:
+ return 'Saint Helena Pound';
+ case Fiats.SLL:
+ return 'Sierra Leone Leone';
+ case Fiats.SOS:
+ return 'Somalia Shilling';
+ case Fiats.SPL:
+ return 'Seborga Luigino';
+ case Fiats.SRD:
+ return 'Suriname Dollar';
+ case Fiats.STN:
+ return 'São Tomé and Príncipe Dobra';
+ case Fiats.SVC:
+ return 'El Salvador Colon';
+ case Fiats.SYP:
+ return 'Syria Pound';
+ case Fiats.SZL:
+ return 'eSwatini Lilangeni';
+ case Fiats.THB:
+ return 'Thailand Baht';
+ case Fiats.TJS:
+ return 'Tajikistan Somoni';
+ case Fiats.TMT:
+ return 'Turkmenistan Manat';
+ case Fiats.TND:
+ return 'Tunisia Dinar';
+ case Fiats.TOP:
+ return "Tonga Pa'anga";
+ case Fiats.TRY:
+ return 'Turkey Lira';
+ case Fiats.TTD:
+ return 'Trinidad and Tobago Dollar';
+ case Fiats.TVD:
+ return 'Tuvalu Dollar';
+ case Fiats.TWD:
+ return 'Taiwan New Dollar';
+ case Fiats.TZS:
+ return 'Tanzania Shilling';
+ case Fiats.UAH:
+ return 'Ukraine Hryvnia';
+ case Fiats.UGX:
+ return 'Uganda Shilling';
+ case Fiats.USD:
+ return 'United States Dollar';
+ case Fiats.UYU:
+ return 'Uruguay Peso';
+ case Fiats.UZS:
+ return 'Uzbekistan Som';
+ case Fiats.VEF:
+ return 'Venezuela Bolívar';
+ case Fiats.VND:
+ return 'Viet Nam Dong';
+ case Fiats.VUV:
+ return 'Vanuatu Vatu';
+ case Fiats.WST:
+ return 'Samoa Tala';
+ case Fiats.XAF:
+ return 'Communauté Financière Africaine (BEAC) CFA Franc BEAC';
+ case Fiats.XCD:
+ return 'East Caribbean Dollar';
+ case Fiats.XDR:
+ return 'International Monetary Fund (IMF) Special Drawing Rights';
+ case Fiats.XOF:
+ return 'Communauté Financière Africaine (BCEAO) Franc';
+ case Fiats.XPF:
+ return 'Comptoirs Français du Pacifique (CFP) Franc';
+ case Fiats.YER:
+ return 'Yemen Rial';
+ case Fiats.ZAR:
+ return 'South Africa Rand';
+ case Fiats.ZMW:
+ return 'Zambia Kwacha';
+ case Fiats.ZWD:
+ return 'Zimbabwe Dollar';
+ }
+ }
+}
+
+Fiats fiatFromTickerCaseInsensitive(String ticker) {
+ switch (ticker.toLowerCase()) {
+ case "aed":
+ return Fiats.AED;
+ case "afn":
+ return Fiats.AFN;
+ case "all":
+ return Fiats.ALL;
+ case "amd":
+ return Fiats.AMD;
+ case "ang":
+ return Fiats.ANG;
+ case "aoa":
+ return Fiats.AOA;
+ case "ars":
+ return Fiats.ARS;
+ case "aud":
+ return Fiats.AUD;
+ case "awg":
+ return Fiats.AWG;
+ case "azn":
+ return Fiats.AZN;
+ case "bam":
+ return Fiats.BAM;
+ case "bbd":
+ return Fiats.BBD;
+ case "bdt":
+ return Fiats.BDT;
+ case "bgn":
+ return Fiats.BGN;
+ case "bhd":
+ return Fiats.BHD;
+ case "bif":
+ return Fiats.BIF;
+ case "bmd":
+ return Fiats.BMD;
+ case "bnd":
+ return Fiats.BND;
+ case "bob":
+ return Fiats.BOB;
+ case "brl":
+ return Fiats.BRL;
+ case "bsd":
+ return Fiats.BSD;
+ case "btn":
+ return Fiats.BTN;
+ case "bwp":
+ return Fiats.BWP;
+ case "byn":
+ return Fiats.BYN;
+ case "bzd":
+ return Fiats.BZD;
+ case "cad":
+ return Fiats.CAD;
+ case "cdf":
+ return Fiats.CDF;
+ case "chf":
+ return Fiats.CHF;
+ case "clp":
+ return Fiats.CLP;
+ case "cny":
+ return Fiats.CNY;
+ case "cop":
+ return Fiats.COP;
+ case "crc":
+ return Fiats.CRC;
+ case "cuc":
+ return Fiats.CUC;
+ case "cup":
+ return Fiats.CUP;
+ case "cve":
+ return Fiats.CVE;
+ case "czk":
+ return Fiats.CZK;
+ case "djf":
+ return Fiats.DJF;
+ case "dkk":
+ return Fiats.DKK;
+ case "dop":
+ return Fiats.DOP;
+ case "dzd":
+ return Fiats.DZD;
+ case "egp":
+ return Fiats.EGP;
+ case "ern":
+ return Fiats.ERN;
+ case "etb":
+ return Fiats.ETB;
+ case "eur":
+ return Fiats.EUR;
+ case "fjd":
+ return Fiats.FJD;
+ case "fkp":
+ return Fiats.FKP;
+ case "gbp":
+ return Fiats.GBP;
+ case "gel":
+ return Fiats.GEL;
+ case "ggp":
+ return Fiats.GGP;
+ case "ghs":
+ return Fiats.GHS;
+ case "gip":
+ return Fiats.GIP;
+ case "gmd":
+ return Fiats.GMD;
+ case "gnf":
+ return Fiats.GNF;
+ case "gtq":
+ return Fiats.GTQ;
+ case "gyd":
+ return Fiats.GYD;
+ case "hkd":
+ return Fiats.HKD;
+ case "hnl":
+ return Fiats.HNL;
+ case "hrk":
+ return Fiats.HRK;
+ case "htg":
+ return Fiats.HTG;
+ case "huf":
+ return Fiats.HUF;
+ case "idr":
+ return Fiats.IDR;
+ case "ils":
+ return Fiats.ILS;
+ case "imp":
+ return Fiats.IMP;
+ case "inr":
+ return Fiats.INR;
+ case "iqd":
+ return Fiats.IQD;
+ case "irr":
+ return Fiats.IRR;
+ case "isk":
+ return Fiats.ISK;
+ case "jep":
+ return Fiats.JEP;
+ case "jmd":
+ return Fiats.JMD;
+ case "jod":
+ return Fiats.JOD;
+ case "jpy":
+ return Fiats.JPY;
+ case "kes":
+ return Fiats.KES;
+ case "kgs":
+ return Fiats.KGS;
+ case "khr":
+ return Fiats.KHR;
+ case "kmf":
+ return Fiats.KMF;
+ case "kpw":
+ return Fiats.KPW;
+ case "krw":
+ return Fiats.KRW;
+ case "kwd":
+ return Fiats.KWD;
+ case "kyd":
+ return Fiats.KYD;
+ case "kzt":
+ return Fiats.KZT;
+ case "lak":
+ return Fiats.LAK;
+ case "lbp":
+ return Fiats.LBP;
+ case "lkr":
+ return Fiats.LKR;
+ case "lrd":
+ return Fiats.LRD;
+ case "lsl":
+ return Fiats.LSL;
+ case "lyd":
+ return Fiats.LYD;
+ case "mad":
+ return Fiats.MAD;
+ case "mdl":
+ return Fiats.MDL;
+ case "mga":
+ return Fiats.MGA;
+ case "mkd":
+ return Fiats.MKD;
+ case "mmk":
+ return Fiats.MMK;
+ case "mnt":
+ return Fiats.MNT;
+ case "mop":
+ return Fiats.MOP;
+ case "mru":
+ return Fiats.MRU;
+ case "mur":
+ return Fiats.MUR;
+ case "mvr":
+ return Fiats.MVR;
+ case "mwk":
+ return Fiats.MWK;
+ case "mxn":
+ return Fiats.MXN;
+ case "myr":
+ return Fiats.MYR;
+ case "mzn":
+ return Fiats.MZN;
+ case "nad":
+ return Fiats.NAD;
+ case "ngn":
+ return Fiats.NGN;
+ case "nio":
+ return Fiats.NIO;
+ case "nok":
+ return Fiats.NOK;
+ case "npr":
+ return Fiats.NPR;
+ case "nzd":
+ return Fiats.NZD;
+ case "omr":
+ return Fiats.OMR;
+ case "pab":
+ return Fiats.PAB;
+ case "pen":
+ return Fiats.PEN;
+ case "pgk":
+ return Fiats.PGK;
+ case "php":
+ return Fiats.PHP;
+ case "pkr":
+ return Fiats.PKR;
+ case "pln":
+ return Fiats.PLN;
+ case "pyg":
+ return Fiats.PYG;
+ case "qar":
+ return Fiats.QAR;
+ case "ron":
+ return Fiats.RON;
+ case "rsd":
+ return Fiats.RSD;
+ case "rub":
+ return Fiats.RUB;
+ case "rwf":
+ return Fiats.RWF;
+ case "sar":
+ return Fiats.SAR;
+ case "sbd":
+ return Fiats.SBD;
+ case "scr":
+ return Fiats.SCR;
+ case "sdg":
+ return Fiats.SDG;
+ case "sek":
+ return Fiats.SEK;
+ case "sgd":
+ return Fiats.SGD;
+ case "shp":
+ return Fiats.SHP;
+ case "sll":
+ return Fiats.SLL;
+ case "sos":
+ return Fiats.SOS;
+ case "spl":
+ return Fiats.SPL;
+ case "srd":
+ return Fiats.SRD;
+ case "stn":
+ return Fiats.STN;
+ case "svc":
+ return Fiats.SVC;
+ case "syp":
+ return Fiats.SYP;
+ case "szl":
+ return Fiats.SZL;
+ case "thb":
+ return Fiats.THB;
+ case "tjs":
+ return Fiats.TJS;
+ case "tmt":
+ return Fiats.TMT;
+ case "tnd":
+ return Fiats.TND;
+ case "top":
+ return Fiats.TOP;
+ case "try":
+ return Fiats.TRY;
+ case "ttd":
+ return Fiats.TTD;
+ case "tvd":
+ return Fiats.TVD;
+ case "twd":
+ return Fiats.TWD;
+ case "tzs":
+ return Fiats.TZS;
+ case "uah":
+ return Fiats.UAH;
+ case "ugx":
+ return Fiats.UGX;
+ case "usd":
+ return Fiats.USD;
+ case "uyu":
+ return Fiats.UYU;
+ case "uzs":
+ return Fiats.UZS;
+ case "vef":
+ return Fiats.VEF;
+ case "vnd":
+ return Fiats.VND;
+ case "vuv":
+ return Fiats.VUV;
+ case "wst":
+ return Fiats.WST;
+ case "xaf":
+ return Fiats.XAF;
+ case "xcd":
+ return Fiats.XCD;
+ case "xdr":
+ return Fiats.XDR;
+ case "xof":
+ return Fiats.XOF;
+ case "xpf":
+ return Fiats.XPF;
+ case "yer":
+ return Fiats.YER;
+ case "zar":
+ return Fiats.ZAR;
+ case "zmw":
+ return Fiats.ZMW;
+ case "zwd":
+ return Fiats.ZWD;
+ default:
+ throw ArgumentError.value(
+ ticker, "name", "No Fiat enum value with that ticker");
+ }
+}
diff --git a/lib/utilities/prefs.dart b/lib/utilities/prefs.dart
index 6b4b9821a..14f95d039 100644
--- a/lib/utilities/prefs.dart
+++ b/lib/utilities/prefs.dart
@@ -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 _getUserId() async {
+ String? userID = await DB.instance
+ .get(boxName: DB.boxNamePrefs, key: "userID") as String?;
+ if (userID == null) {
+ userID = const Uuid().v4();
+ await saveUserID(userID);
+ }
+ return userID;
+ }
+
+ Future saveUserID(String userId) async {
+ _userId = userId;
+ await DB.instance
+ .put(boxName: DB.boxNamePrefs, key: "userID", value: _userId);
+ // notifyListeners();
+ }
+
+ int? _signupEpoch;
+ int? get signupEpoch => _signupEpoch;
+
+ Future _getSignupEpoch() async {
+ int? signupEpoch = await DB.instance
+ .get(boxName: DB.boxNamePrefs, key: "signupEpoch") as int?;
+ if (signupEpoch == null) {
+ signupEpoch = DateTime.now().millisecondsSinceEpoch ~/
+ Duration.millisecondsPerSecond;
+ await saveSignupEpoch(signupEpoch);
+ }
+ return signupEpoch;
+ }
+
+ Future saveSignupEpoch(int signupEpoch) async {
+ _signupEpoch = signupEpoch;
+ await DB.instance.put(
+ boxName: DB.boxNamePrefs, key: "signupEpoch", value: _signupEpoch);
+ // notifyListeners();
+ }
}
diff --git a/lib/utilities/theme/color_theme.dart b/lib/utilities/theme/color_theme.dart
index 2eea34806..dfd62a6a0 100644
--- a/lib/utilities/theme/color_theme.dart
+++ b/lib/utilities/theme/color_theme.dart
@@ -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();
diff --git a/lib/utilities/theme/dark_colors.dart b/lib/utilities/theme/dark_colors.dart
index 298d8c86d..802717f7f 100644
--- a/lib/utilities/theme/dark_colors.dart
+++ b/lib/utilities/theme/dark_colors.dart
@@ -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,
diff --git a/lib/utilities/theme/fruit_sorbet_colors.dart b/lib/utilities/theme/fruit_sorbet_colors.dart
index 12ae33e7b..6f0a81e82 100644
--- a/lib/utilities/theme/fruit_sorbet_colors.dart
+++ b/lib/utilities/theme/fruit_sorbet_colors.dart
@@ -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,
diff --git a/lib/utilities/theme/light_colors.dart b/lib/utilities/theme/light_colors.dart
index fc825747c..b4d66d7f3 100644
--- a/lib/utilities/theme/light_colors.dart
+++ b/lib/utilities/theme/light_colors.dart
@@ -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,
diff --git a/lib/utilities/theme/ocean_breeze_colors.dart b/lib/utilities/theme/ocean_breeze_colors.dart
index e907fd2cf..1ba64843d 100644
--- a/lib/utilities/theme/ocean_breeze_colors.dart
+++ b/lib/utilities/theme/ocean_breeze_colors.dart
@@ -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,
diff --git a/lib/utilities/theme/oled_black_colors.dart b/lib/utilities/theme/oled_black_colors.dart
index 1d9f2c61f..84e2a7f96 100644
--- a/lib/utilities/theme/oled_black_colors.dart
+++ b/lib/utilities/theme/oled_black_colors.dart
@@ -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,
diff --git a/lib/utilities/theme/stack_colors.dart b/lib/utilities/theme/stack_colors.dart
index 563dd621c..e9280fee5 100644
--- a/lib/utilities/theme/stack_colors.dart
+++ b/lib/utilities/theme/stack_colors.dart
@@ -178,6 +178,12 @@ class StackColors extends ThemeExtension {
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 {
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 {
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 {
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 {
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 {
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,
+ )!,
);
}
diff --git a/pubspec.yaml b/pubspec.yaml
index 32ab70261..be53f0330 100644
--- a/pubspec.yaml
+++ b/pubspec.yaml
@@ -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
diff --git a/test/models/type_adapter_tests/lelantus_coin_adapter_test.dart b/test/models/type_adapter_tests/lelantus_coin_adapter_test.dart
index 41f5d1232..44c2a1a70 100644
--- a/test/models/type_adapter_tests/lelantus_coin_adapter_test.dart
+++ b/test/models/type_adapter_tests/lelantus_coin_adapter_test.dart
@@ -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", () {
diff --git a/test/models/type_adapter_tests/transactions_model_adapter_test.dart b/test/models/type_adapter_tests/transactions_model_adapter_test.dart
index d19446a66..a7c9d0441 100644
--- a/test/models/type_adapter_tests/transactions_model_adapter_test.dart
+++ b/test/models/type_adapter_tests/transactions_model_adapter_test.dart
@@ -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", () {
diff --git a/test/models/type_adapter_tests/utxo_model_adapter_test.dart b/test/models/type_adapter_tests/utxo_model_adapter_test.dart
index 498044976..9dfd5b0ba 100644
--- a/test/models/type_adapter_tests/utxo_model_adapter_test.dart
+++ b/test/models/type_adapter_tests/utxo_model_adapter_test.dart
@@ -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", () {