diff --git a/lib/pages/buy_view/buy_form.dart b/lib/pages/buy_view/buy_form.dart index 2c626c64b..f8f0f9f84 100644 --- a/lib/pages/buy_view/buy_form.dart +++ b/lib/pages/buy_view/buy_form.dart @@ -1,12 +1,18 @@ +import 'dart:async'; + 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:stackwallet/models/buy/response_objects/crypto.dart'; import 'package:stackwallet/models/buy/response_objects/fiat.dart'; +import 'package:stackwallet/pages/address_book_views/address_book_view.dart'; import 'package:stackwallet/pages/buy_view/sub_widgets/crypto_selection_view.dart'; import 'package:stackwallet/pages/buy_view/sub_widgets/fiat_crypto_toggle.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/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'; @@ -17,6 +23,8 @@ 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/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'; @@ -60,6 +68,9 @@ class _BuyFormState extends ConsumerState { List? fiats; String? _address; + Fiat? selectedFiat; + Crypto? selectedCrypto; + bool buyWithFiat = true; bool _addressToggleFlag = false; bool _hovering1 = false; @@ -70,11 +81,35 @@ class _BuyFormState extends ConsumerState { void cryptoFieldOnChanged(String value) async {} void selectCrypto() async { - final supportedCoins = - ref.read(supportedSimplexCurrenciesProvider).supportedCryptos; + if (ref.read(supportedSimplexCurrenciesProvider).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 _loadSimplexCurrencies(); + shouldPop = true; + if (mounted) { + Navigator.of(context).pop(); + } + } await _showFloatingCryptoSelectionSheet( - coins: supportedCoins, onSelected: (_) {}); + coins: ref.read(supportedSimplexCurrenciesProvider).supportedCryptos, + onSelected: (crypto) { + setState(() { + selectedCrypto = crypto; + }); + }, + ); } Future _showFloatingCryptoSelectionSheet({ @@ -149,12 +184,54 @@ class _BuyFormState extends ConsumerState { } } - void selectFiat() async { - final supportedFiats = - ref.read(supportedSimplexCurrenciesProvider).supportedFiats; + Future selectFiat() async { + if (ref.read(supportedSimplexCurrenciesProvider).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 _loadSimplexCurrencies(); + shouldPop = true; + if (mounted) { + Navigator.of(context).pop(); + } + } await _showFloatingFiatSelectionSheet( - fiats: supportedFiats, onSelected: (_) {}); + fiats: ref.read(supportedSimplexCurrenciesProvider).supportedFiats, + onSelected: (fiat) { + setState(() { + selectedFiat = fiat; + }); + }, + ); + } + + Future _loadSimplexCurrencies() async { + final response = await SimplexAPI.instance.getSupported(); + + if (response.value != null) { + ref + .read(supportedSimplexCurrenciesProvider) + .updateSupportedCryptos(response.value!.item1); + ref + .read(supportedSimplexCurrenciesProvider) + .updateSupportedFiats(response.value!.item2); + } else { + Logging.instance.log( + "_loadSimplexCurrencies: $response", + level: LogLevel.Warning, + ); + } } Future _showFloatingFiatSelectionSheet({ @@ -235,6 +312,17 @@ class _BuyFormState extends ConsumerState { return null; } + bool isStackCoin(String? ticker) { + if (ticker == null) return false; + + try { + coinFromTickerCaseInsensitive(ticker); + return true; + } on ArgumentError catch (_) { + return false; + } + } + @override void initState() { _receiveAddressController = TextEditingController(); @@ -246,11 +334,6 @@ class _BuyFormState extends ConsumerState { coins = ref.read(supportedSimplexCurrenciesProvider).supportedCryptos; fiats = ref.read(supportedSimplexCurrenciesProvider).supportedFiats; - // WidgetsBinding.instance.addPostFrameCallback((timeStamp) { - // BuyDataLoadingService().loadAll( - // ref); // Why does this need to be called here? Shouldn't it already be called by main.dart? - // }); - // TODO set initial crypto to open wallet if a wallet is open super.initState(); @@ -499,6 +582,39 @@ class _BuyFormState extends ConsumerState { 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( @@ -569,27 +685,25 @@ class _BuyFormState extends ConsumerState { key: const Key( "buyViewPasteAddressFieldButtonKey"), onTap: () async { - print( - "TODO paste; Error: 'ClipboardData' isn't a type."); - // 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; - // }); - // } + 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() @@ -599,11 +713,9 @@ class _BuyFormState extends ConsumerState { TextFieldIconButton( key: const Key("buyViewAddressBookButtonKey"), onTap: () { - print('TODO tapped buyViewAddressBookButtonKey'); - // Navigator.of(context).pushNamed( - // AddressBookView.routeName, - // arguments: widget.coin, - // ); + Navigator.of(context).pushNamed( + AddressBookView.routeName, + ); }, child: const AddressBookIcon(), ), @@ -613,11 +725,6 @@ class _BuyFormState extends ConsumerState { key: const Key("buyViewScanQrButtonKey"), onTap: () async { try { - // ref - // .read( - // shouldShowLockscreenOnResumeStateProvider - // .state) - // .state = false; if (FocusScope.of(context).hasFocus) { FocusScope.of(context).unfocus(); await Future.delayed( @@ -637,76 +744,40 @@ class _BuyFormState extends ConsumerState { "qrResult parsed: $results", level: LogLevel.Info); - print('TODO implement QR scanning'); - // if (results.isNotEmpty && - // results["scheme"] == coin.uriScheme) { - // // auto fill address - // _address = results["address"] ?? ""; - // sendToController.text = _address!; - // - // // autofill notes field - // if (results["message"] != null) { - // noteController.text = results["message"]!; - // } else if (results["label"] != null) { - // noteController.text = results["label"]!; - // } - // - // // autofill amount field - // if (results["amount"] != null) { - // final amount = - // Decimal.parse(results["amount"]!); - // cryptoAmountController.text = - // Format.localizedStringAsFixed( - // value: amount, - // locale: ref - // .read( - // localeServiceChangeNotifierProvider) - // .locale, - // decimalPlaces: - // Constants.decimalPlacesForCoin( - // coin), - // ); - // amount.toString(); - // _amountToSend = amount; - // } - // - // _updatePreviewButtonState( - // _address, _amountToSend); - // setState(() { - // _addressToggleFlag = - // sendToController.text.isNotEmpty; - // }); - // - // // now check for non standard encoded basic address - // } else if (ref - // .read(walletsChangeNotifierProvider) - // .getManager(walletId) - // .validateAddress(qrResult.rawContent)) { - // _address = qrResult.rawContent; - // sendToController.text = _address ?? ""; - // - // _updatePreviewButtonState( - // _address, _amountToSend); - // setState(() { - // _addressToggleFlag = - // sendToController.text.isNotEmpty; - // }); - // } - } /*on PlatformException*/ catch (e, s) { - // ref - // .read( - // shouldShowLockscreenOnResumeStateProvider - // .state) - // .state = true; + 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); + "Failed to get camera permissions while trying to scan qr code in SendView: $e\n$s", + level: LogLevel.Warning, + ); } }, child: const QrCodeIcon(), - ) + ), ], ), ), @@ -722,9 +793,9 @@ class _BuyFormState extends ConsumerState { enabled: ref.watch( exchangeFormStateProvider.select((value) => value.canExchange)), onPressed: () { - // TODO implement buy confirmation dialog + // preview buy quote }, - label: "Exchange", + label: "Preview quote", ) ], ), diff --git a/pubspec.lock b/pubspec.lock index e8f875d35..ecc0bdf8d 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -42,7 +42,7 @@ packages: name: archive url: "https://pub.dartlang.org" source: hosted - version: "3.1.11" + version: "3.3.0" args: dependency: transitive description: @@ -63,7 +63,7 @@ packages: name: async url: "https://pub.dartlang.org" source: hosted - version: "2.8.2" + version: "2.9.0" barcode_scan2: dependency: "direct main" description: @@ -190,14 +190,7 @@ packages: name: characters url: "https://pub.dartlang.org" source: hosted - version: "1.2.0" - charcode: - dependency: transitive - description: - name: charcode - url: "https://pub.dartlang.org" - source: hosted - version: "1.3.1" + version: "1.2.1" checked_yaml: dependency: transitive description: @@ -218,7 +211,7 @@ packages: name: clock url: "https://pub.dartlang.org" source: hosted - version: "1.1.0" + version: "1.1.1" code_builder: dependency: transitive description: @@ -288,7 +281,7 @@ packages: name: coverage url: "https://pub.dartlang.org" source: hosted - version: "1.2.0" + version: "1.5.0" cross_file: dependency: transitive description: @@ -442,7 +435,7 @@ packages: name: fake_async url: "https://pub.dartlang.org" source: hosted - version: "1.3.0" + version: "1.3.1" ffi: dependency: "direct main" description: @@ -871,21 +864,21 @@ packages: name: matcher url: "https://pub.dartlang.org" source: hosted - version: "0.12.11" + version: "0.12.12" material_color_utilities: dependency: transitive description: name: material_color_utilities url: "https://pub.dartlang.org" source: hosted - version: "0.1.4" + version: "0.1.5" meta: dependency: transitive description: name: meta url: "https://pub.dartlang.org" source: hosted - version: "1.7.0" + version: "1.8.0" mime: dependency: transitive description: @@ -997,7 +990,7 @@ packages: name: path url: "https://pub.dartlang.org" source: hosted - version: "1.8.1" + version: "1.8.2" path_drawing: dependency: transitive description: @@ -1373,7 +1366,7 @@ packages: name: source_span url: "https://pub.dartlang.org" source: hosted - version: "1.8.2" + version: "1.9.0" stack_trace: dependency: transitive description: @@ -1417,7 +1410,7 @@ packages: name: string_scanner url: "https://pub.dartlang.org" source: hosted - version: "1.1.0" + version: "1.1.1" string_validator: dependency: "direct main" description: @@ -1431,35 +1424,35 @@ packages: name: sync_http url: "https://pub.dartlang.org" source: hosted - version: "0.3.0" + version: "0.3.1" term_glyph: dependency: transitive description: name: term_glyph url: "https://pub.dartlang.org" source: hosted - version: "1.2.0" + version: "1.2.1" test: dependency: transitive description: name: test url: "https://pub.dartlang.org" source: hosted - version: "1.21.1" + version: "1.21.4" test_api: dependency: transitive description: name: test_api url: "https://pub.dartlang.org" source: hosted - version: "0.4.9" + version: "0.4.12" test_core: dependency: transitive description: name: test_core url: "https://pub.dartlang.org" source: hosted - version: "0.4.13" + version: "0.4.16" time: dependency: transitive description: @@ -1508,7 +1501,7 @@ packages: name: typed_data url: "https://pub.dartlang.org" source: hosted - version: "1.3.0" + version: "1.3.1" universal_io: dependency: transitive description: @@ -1592,7 +1585,7 @@ packages: name: vm_service url: "https://pub.dartlang.org" source: hosted - version: "8.2.2" + version: "9.0.0" wakelock: dependency: "direct main" description: