change amount widget as in send views, read currency selections from API

This commit is contained in:
sneurlax 2023-01-13 18:07:27 -06:00
parent 7e3cad5a06
commit cfe40c76d9
6 changed files with 434 additions and 308 deletions

View file

@ -1,16 +1,16 @@
// 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/crypto.dart';
import 'package:stackwallet/models/buy/response_objects/fiat.dart';
// import 'package:stackwallet/models/buy/response_objects/pair.dart';
class SimplexSupportedCurrencies {
dynamic supportedCryptos = [];
dynamic supportedFiats = [];
List<Crypto> supportedCryptos = [];
List<Fiat> supportedFiats = [];
void updateSupportedCryptos(dynamic newCryptos) {
void updateSupportedCryptos(List<Crypto> newCryptos) {
supportedCryptos = newCryptos;
}
void updateSupportedFiats(dynamic newFiats) {
void updateSupportedFiats(List<Fiat> newFiats) {
supportedFiats = newFiats;
}
}

View file

@ -3,9 +3,11 @@ 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/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/providers/providers.dart';
import 'package:stackwallet/services/buy/buy_data_loading_service.dart';
import 'package:stackwallet/utilities/address_utils.dart';
import 'package:stackwallet/utilities/assets.dart';
import 'package:stackwallet/utilities/barcode_scanner_interface.dart';
@ -26,7 +28,6 @@ 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';
import 'package:stackwallet/widgets/textfields/buy_textfield.dart';
class BuyForm extends ConsumerStatefulWidget {
const BuyForm({
@ -47,14 +48,16 @@ class _BuyFormState extends ConsumerState<BuyForm> {
late final BarcodeScannerInterface scanner;
late final TextEditingController _receiveAddressController;
late final TextEditingController _fiatController;
late final TextEditingController _cryptoController;
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<Crypto>? coins;
List<Fiat>? fiats;
String? _address;
bool buyWithFiat = true;
@ -67,13 +70,11 @@ class _BuyFormState extends ConsumerState<BuyForm> {
void cryptoFieldOnChanged(String value) async {}
void selectCrypto() async {
final fromTicker = ref.read(buyFormStateProvider).fromTicker ?? "-";
final supportedCoins = ref.watch(supportedSimplexCurrenciesProvider);
List<Crypto> coins = [];
final supportedCoins =
ref.watch(supportedSimplexCurrenciesProvider).supportedCryptos;
await _showFloatingCryptoSelectionSheet(
coins: coins,
coins: supportedCoins,
onSelected: (from) =>
ref.read(buyFormStateProvider).updateFrom(from, true));
}
@ -102,7 +103,7 @@ class _BuyFormState extends ConsumerState<BuyForm> {
left: 32,
),
child: Text(
"Choose a fiat currency with which to pay",
"Choose a crypto to buy",
style: STextStyles.desktopH3(context),
),
),
@ -124,8 +125,8 @@ class _BuyFormState extends ConsumerState<BuyForm> {
borderColor: Theme.of(context)
.extension<StackColors>()!
.background,
child: FiatSelectionView(
fiats: coins,
child: CryptoSelectionView(
coins: coins,
),
),
),
@ -139,23 +140,23 @@ class _BuyFormState extends ConsumerState<BuyForm> {
})
: await Navigator.of(context).push(
MaterialPageRoute<dynamic>(
builder: (_) => FiatSelectionView(
fiats: coins,
builder: (_) => CryptoSelectionView(
coins: coins,
),
),
);
if (mounted && result is Fiat) {
if (mounted && result is Crypto) {
onSelected(result);
}
}
void selectFiat() async {
List<Fiat> fiats;
fiats = [];
final supportedFiats =
ref.watch(supportedSimplexCurrenciesProvider).supportedFiats;
await _showFloatingFiatSelectionSheet(
fiats: fiats,
fiats: supportedFiats,
onSelected: (from) =>
ref.read(buyFormStateProvider).updateFrom(from, true));
}
@ -279,13 +280,15 @@ class _BuyFormState extends ConsumerState<BuyForm> {
@override
void initState() {
_fiatController = TextEditingController();
_cryptoController = TextEditingController();
_receiveAddressController = TextEditingController();
_buyAmountController = TextEditingController();
clipboard = widget.clipboard;
scanner = widget.scanner;
coins = ref.read(supportedSimplexCurrenciesProvider).supportedCryptos;
fiats = ref.read(supportedSimplexCurrenciesProvider).supportedFiats;
// TODO set initial crypto to open wallet if a wallet is open
super.initState();
@ -325,6 +328,7 @@ class _BuyFormState extends ConsumerState<BuyForm> {
onExit: (_) => setState(() => _hovering1 = false),
child: GestureDetector(
onTap: () {
BuyDataLoadingService().loadAll(ref);
selectCrypto();
},
child: RoundedContainer(
@ -474,32 +478,57 @@ class _BuyFormState extends ConsumerState<BuyForm> {
SizedBox(
height: isDesktop ? 10 : 4,
),
BuyTextField(
controller: _fiatController,
focusNode: _fiatFocusNode,
textStyle: STextStyles.smallMed14(context).copyWith(
TextField(
autocorrect: Util.isDesktop ? false : true,
enableSuggestions: Util.isDesktop ? false : true,
style: STextStyles.smallMed14(context).copyWith(
color: Theme.of(context).extension<StackColors>()!.textDark,
),
buttonColor: Theme.of(context)
.extension<StackColors>()!
.buttonBackSecondary,
borderRadius: Constants.size.circularBorderRadius,
background: Theme.of(context)
.extension<StackColors>()!
.textFieldDefaultBG,
onTap: () {
if (_fiatController.text == "-") {
_fiatController.text = "";
}
},
onChanged: fiatFieldOnChanged,
onButtonTap: selectFiat,
// isWalletCoin: isWalletCoin(coin, true),
isWalletCoin: false,
// image: _fetchIconUrlFromTicker(ref
// .watch(buyFormStateProvider.select((value) => value.fromTicker))),
// ticker: ref
// .watch(buyFormStateProvider.select((value) => value.fromTicker)),
key: const Key("amountInputFieldCryptoTextFieldKey"),
controller: _buyAmountController,
focusNode: _buyAmountFocusNode,
keyboardType: Util.isDesktop
? null
: const TextInputType.numberWithOptions(
signed: false,
decimal: true,
),
textAlign: TextAlign.right,
inputFormatters: [
// TODO reactivate formatter
// regex to validate a crypto amount with 8 decimal places
// TextInputFormatter.withFunction((oldValue, newValue) =>
// RegExp(r'^([0-9]*[,.]?[0-9]{0,8}|[,.][0-9]{0,8})$')
// .hasMatch(newValue.text)
// ? newValue
// : oldValue),
],
decoration: InputDecoration(
contentPadding: const EdgeInsets.only(
top: 22,
right: 12,
bottom: 22,
),
hintText: "0",
hintStyle: STextStyles.desktopTextExtraSmall(context).copyWith(
color: Theme.of(context)
.extension<StackColors>()!
.textFieldDefaultText,
),
prefixIcon: FittedBox(
fit: BoxFit.scaleDown,
child: Padding(
padding: const EdgeInsets.all(12),
child: Text(
"BTC",
style: STextStyles.smallMed14(context).copyWith(
color: Theme.of(context)
.extension<StackColors>()!
.accentColorDark),
),
),
),
),
),
SizedBox(
height: isDesktop ? 20 : 12,

View file

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

View file

@ -29,10 +29,9 @@ class BuyDataLoadingService {
final response = await SimplexAPI.instance.getSupported();
if (response['supportedCryptos'] != null) {
print(response['supportedCryptos']);
ref
.read(supportedSimplexCurrenciesProvider)
.updateSupportedCryptos(response['supportedCryptos']!);
.updateSupportedCryptos(response['supportedCryptos']);
} else {
error = true;
Logging.instance.log(
@ -40,11 +39,11 @@ class BuyDataLoadingService {
level: LogLevel.Warning,
);
}
if (response['supportedFiats'] != null) {
print(response['supportedFiats']);
ref
.read(supportedSimplexCurrenciesProvider)
.updateSupportedFiats(response['supportedFiats']!);
.updateSupportedFiats(response['supportedFiats']);
} else {
error = true;
Logging.instance.log(
@ -56,8 +55,8 @@ class BuyDataLoadingService {
if (error) {
// _loadSimplexCurrencies() again?
} else {
ref.read(changeNowEstimatedInitialLoadStatusStateProvider.state).state =
ChangeNowLoadStatus.success;
ref.read(simplexLoadStatusStateProvider.state).state =
SimplexLoadStatus.success;
}
}
}

View file

@ -2,6 +2,7 @@ import 'dart:convert';
import 'package:flutter/foundation.dart';
import 'package:http/http.dart' as http;
import 'package:stackwallet/models/buy/response_objects/crypto.dart';
// import 'package:stackwallet/models/exchange/response_objects/fixed_rate_market.dart';
// import 'package:stackwallet/models/exchange/response_objects/pair.dart';
// import 'package:stackwallet/models/exchange/response_objects/range.dart';
@ -180,23 +181,30 @@ class SimplexAPI {
dynamic /*BuyResponse<List<Fiat>>*/ _parseSupported(dynamic jsonArray) {
try {
List<Fiat> cryptos = [];
List<Crypto> cryptos = [];
List<Fiat> fiats = [];
var supportedCryptos =
jsonArray['result']['supported_digital_currencies'];
// for (final ticker in supportedCryptos as List) {
// cryptos.add(Fiat.fromString(ticker as String));
// }
// TODO map List<String> supportedCryptos to List<Crypto>
for (final ticker in supportedCryptos as List) {
cryptos.add(Crypto.fromJson({
'ticker': ticker as String,
'name': ticker as String,
'image': ticker as String,
}));
}
var supportedFiats = jsonArray['result']['supported_fiat_currencies'];
// for (final ticker in supportedFiats as List) {
// fiats.add(Fiat.fromString(ticker as String));
// }
// TODO map List<String> supportedFiats to List<Fiat>
for (final ticker in supportedFiats as List) {
fiats.add(Fiat.fromJson({
'ticker': ticker as String,
'name': ticker as String,
'image': ticker as String,
}));
}
var supported = {
'supportedCryptos': supportedCryptos,
'supportedFiats': supportedFiats
};
var supported = {'supportedCryptos': cryptos, 'supportedFiats': fiats};
return supported;
} catch (e, s) {

View file

@ -1,242 +0,0 @@
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:flutter_svg/svg.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/loading_indicator.dart';
class BuyTextField extends StatefulWidget {
const BuyTextField({
Key? key,
this.borderRadius = 0,
this.background,
required this.controller,
this.buttonColor,
required this.focusNode,
this.buttonContent,
required this.textStyle,
this.onButtonTap,
this.onChanged,
this.onSubmitted,
this.onTap,
required this.isWalletCoin,
this.image,
this.ticker,
this.readOnly = false,
}) : super(key: key);
final double borderRadius;
final Color? background;
final Color? buttonColor;
final Widget? buttonContent;
final TextEditingController controller;
final FocusNode focusNode;
final TextStyle textStyle;
final VoidCallback? onTap;
final VoidCallback? onButtonTap;
final void Function(String)? onChanged;
final void Function(String)? onSubmitted;
final bool isWalletCoin;
final bool readOnly;
final String? image;
final String? ticker;
@override
State<BuyTextField> createState() => _BuyTextFieldState();
}
class _BuyTextFieldState extends State<BuyTextField> {
late final TextEditingController controller;
late final FocusNode focusNode;
late final TextStyle textStyle;
late final double borderRadius;
late final Color? background;
late final Color? buttonColor;
late final Widget? buttonContent;
late final VoidCallback? onButtonTap;
late final VoidCallback? onTap;
late final void Function(String)? onChanged;
late final void Function(String)? onSubmitted;
final isDesktop = Util.isDesktop;
@override
void initState() {
borderRadius = widget.borderRadius;
background = widget.background;
buttonColor = widget.buttonColor;
controller = widget.controller;
focusNode = widget.focusNode;
buttonContent = widget.buttonContent;
textStyle = widget.textStyle;
onButtonTap = widget.onButtonTap;
onChanged = widget.onChanged;
onSubmitted = widget.onSubmitted;
onTap = widget.onTap;
super.initState();
}
@override
Widget build(BuildContext context) {
return Container(
decoration: BoxDecoration(
color: background,
borderRadius: BorderRadius.circular(borderRadius),
),
child: IntrinsicHeight(
child: Row(
crossAxisAlignment: CrossAxisAlignment.stretch,
children: [
Expanded(
child: TextField(
style: textStyle,
controller: controller,
focusNode: focusNode,
onChanged: onChanged,
onTap: onTap,
enableSuggestions: false,
autocorrect: false,
readOnly: widget.readOnly,
keyboardType: isDesktop
? null
: const TextInputType.numberWithOptions(
signed: false,
decimal: true,
),
decoration: InputDecoration(
contentPadding: const EdgeInsets.only(
top: 12,
left: 12,
),
hintText: "0",
hintStyle: STextStyles.fieldLabel(context).copyWith(
fontSize: 14,
),
),
inputFormatters: [
// regex to validate a crypto amount with 8 decimal places
TextInputFormatter.withFunction((oldValue, newValue) =>
RegExp(r'^([0-9]*[,.]?[0-9]{0,8}|[,.][0-9]{0,8})$')
.hasMatch(newValue.text)
? newValue
: oldValue),
],
),
),
MouseRegion(
cursor: SystemMouseCursors.click,
child: GestureDetector(
onTap: () => onButtonTap?.call(),
child: Container(
decoration: BoxDecoration(
color: buttonColor,
borderRadius: BorderRadius.horizontal(
right: Radius.circular(
borderRadius,
),
),
),
child: Padding(
padding: const EdgeInsets.symmetric(
horizontal: 16,
),
child: Row(
children: [
Container(
width: 18,
height: 18,
decoration: BoxDecoration(
borderRadius: BorderRadius.circular(18),
),
child: Builder(
builder: (context) {
final image = widget.image;
if (image != null && image.isNotEmpty) {
return Center(
child: SvgPicture.network(
image,
height: 18,
placeholderBuilder: (_) => Container(
width: 18,
height: 18,
decoration: BoxDecoration(
color: Theme.of(context)
.extension<StackColors>()!
.textFieldDefaultBG,
borderRadius: BorderRadius.circular(
18,
),
),
child: ClipRRect(
borderRadius: BorderRadius.circular(
18,
),
child: const LoadingIndicator(),
),
),
),
);
} else {
return Container(
width: 18,
height: 18,
decoration: BoxDecoration(
// color: Theme.of(context).extension<StackColors>()!.accentColorDark
borderRadius: BorderRadius.circular(18),
),
child: SvgPicture.asset(
Assets.svg.circleQuestion,
width: 18,
height: 18,
color: Theme.of(context)
.extension<StackColors>()!
.textFieldDefaultBG,
),
);
}
},
),
),
const SizedBox(
width: 6,
),
Text(
widget.ticker?.toUpperCase() ?? "-",
style: STextStyles.smallMed14(context).copyWith(
color: Theme.of(context)
.extension<StackColors>()!
.textDark,
),
),
if (!widget.isWalletCoin)
const SizedBox(
width: 6,
),
if (!widget.isWalletCoin)
SvgPicture.asset(
Assets.svg.chevronDown,
width: 5,
height: 2.5,
color: Theme.of(context)
.extension<StackColors>()!
.textDark,
),
],
),
),
),
),
),
],
),
),
);
}
}