mirror of
https://github.com/cypherstack/stack_wallet.git
synced 2025-03-21 22:58:49 +00:00
scrollable buy form on mobile if keyboard covers part of it
This commit is contained in:
parent
a23c17d83e
commit
0c9dcf6403
1 changed files with 446 additions and 421 deletions
|
@ -23,6 +23,7 @@ import 'package:stackwallet/utilities/logger.dart';
|
||||||
import 'package:stackwallet/utilities/text_styles.dart';
|
import 'package:stackwallet/utilities/text_styles.dart';
|
||||||
import 'package:stackwallet/utilities/theme/stack_colors.dart';
|
import 'package:stackwallet/utilities/theme/stack_colors.dart';
|
||||||
import 'package:stackwallet/utilities/util.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_buttons/blue_text_button.dart';
|
||||||
import 'package:stackwallet/widgets/custom_loading_overlay.dart';
|
import 'package:stackwallet/widgets/custom_loading_overlay.dart';
|
||||||
import 'package:stackwallet/widgets/desktop/desktop_dialog.dart';
|
import 'package:stackwallet/widgets/desktop/desktop_dialog.dart';
|
||||||
|
@ -351,352 +352,438 @@ class _BuyFormState extends ConsumerState<BuyForm> {
|
||||||
buyWithFiat = ref.watch(
|
buyWithFiat = ref.watch(
|
||||||
prefsChangeNotifierProvider.select((value) => value.buyWithFiat));
|
prefsChangeNotifierProvider.select((value) => value.buyWithFiat));
|
||||||
|
|
||||||
return SizedBox(
|
return ConditionalParent(
|
||||||
width:
|
condition: isDesktop,
|
||||||
458, // TODO test that this displays well on mobile or else put in a ternary or something else appropriate to switch here
|
builder: (child) => SizedBox(
|
||||||
child: Column(
|
width: 458,
|
||||||
mainAxisSize: MainAxisSize.min,
|
child: child,
|
||||||
crossAxisAlignment: CrossAxisAlignment.stretch,
|
),
|
||||||
children: [
|
child: ConditionalParent(
|
||||||
Text(
|
condition: !isDesktop,
|
||||||
"I want to buy",
|
builder: (child) => LayoutBuilder(
|
||||||
style: STextStyles.itemSubtitle(context).copyWith(
|
builder: (context, constraints) => SingleChildScrollView(
|
||||||
color: Theme.of(context).extension<StackColors>()!.textDark3,
|
child: ConstrainedBox(
|
||||||
|
constraints: BoxConstraints(
|
||||||
|
minHeight: constraints.maxHeight,
|
||||||
|
),
|
||||||
|
child: IntrinsicHeight(
|
||||||
|
child: child,
|
||||||
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
SizedBox(
|
),
|
||||||
height: isDesktop ? 10 : 4,
|
child: Column(
|
||||||
),
|
mainAxisSize: MainAxisSize.min,
|
||||||
MouseRegion(
|
crossAxisAlignment: CrossAxisAlignment.stretch,
|
||||||
cursor: SystemMouseCursors.click,
|
children: [
|
||||||
onEnter: (_) => setState(() => _hovering1 = true),
|
Text(
|
||||||
onExit: (_) => setState(() => _hovering1 = false),
|
"I want to buy",
|
||||||
child: GestureDetector(
|
style: STextStyles.itemSubtitle(context).copyWith(
|
||||||
onTap: () {
|
color: Theme.of(context).extension<StackColors>()!.textDark3,
|
||||||
selectCrypto();
|
),
|
||||||
},
|
),
|
||||||
child: RoundedContainer(
|
SizedBox(
|
||||||
padding: const EdgeInsets.symmetric(vertical: 6, horizontal: 6),
|
height: isDesktop ? 10 : 4,
|
||||||
color: _hovering1
|
),
|
||||||
? Theme.of(context)
|
MouseRegion(
|
||||||
.extension<StackColors>()!
|
cursor: SystemMouseCursors.click,
|
||||||
.highlight
|
onEnter: (_) => setState(() => _hovering1 = true),
|
||||||
.withOpacity(_hovering1 ? 0.3 : 0)
|
onExit: (_) => setState(() => _hovering1 = false),
|
||||||
: Theme.of(context)
|
child: GestureDetector(
|
||||||
.extension<StackColors>()!
|
onTap: () {
|
||||||
.textFieldDefaultBG,
|
selectCrypto();
|
||||||
child: Padding(
|
},
|
||||||
padding: const EdgeInsets.all(12),
|
child: RoundedContainer(
|
||||||
child: Row(children: <Widget>[
|
padding:
|
||||||
SvgPicture.asset(
|
const EdgeInsets.symmetric(vertical: 6, horizontal: 6),
|
||||||
Assets.svg.iconFor(
|
color: _hovering1
|
||||||
coin: coinFromTickerCaseInsensitive("BTC"),
|
? Theme.of(context)
|
||||||
),
|
|
||||||
height: 18,
|
|
||||||
width: 18,
|
|
||||||
),
|
|
||||||
const SizedBox(
|
|
||||||
width: 10,
|
|
||||||
),
|
|
||||||
Expanded(
|
|
||||||
child: Text(
|
|
||||||
"BTC",
|
|
||||||
style: STextStyles.largeMedium14(context),
|
|
||||||
)),
|
|
||||||
SvgPicture.asset(
|
|
||||||
Assets.svg.chevronDown,
|
|
||||||
color: Theme.of(context)
|
|
||||||
.extension<StackColors>()!
|
.extension<StackColors>()!
|
||||||
.buttonTextSecondaryDisabled,
|
.highlight
|
||||||
width: 10,
|
.withOpacity(_hovering1 ? 0.3 : 0)
|
||||||
height: 5,
|
: Theme.of(context)
|
||||||
),
|
|
||||||
]),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
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<StackColors>()!.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: 6),
|
|
||||||
color: _hovering2
|
|
||||||
? Theme.of(context)
|
|
||||||
.extension<StackColors>()!
|
|
||||||
.highlight
|
|
||||||
.withOpacity(_hovering2 ? 0.3 : 0)
|
|
||||||
: Theme.of(context)
|
|
||||||
.extension<StackColors>()!
|
|
||||||
.textFieldDefaultBG,
|
|
||||||
child: Padding(
|
|
||||||
padding: const EdgeInsets.all(12),
|
|
||||||
child: Row(children: <Widget>[
|
|
||||||
RoundedContainer(
|
|
||||||
radiusMultiplier: 0.5,
|
|
||||||
padding: const EdgeInsets.symmetric(
|
|
||||||
vertical: 2, horizontal: 4),
|
|
||||||
color:
|
|
||||||
Theme.of(context).extension<StackColors>()!.highlight,
|
|
||||||
child: Text(
|
|
||||||
"\$",
|
|
||||||
style: STextStyles.itemSubtitle12(context),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
// SvgPicture.asset(
|
|
||||||
// Assets.svg.iconFor(
|
|
||||||
// coin: coinFromTickerCaseInsensitive("BTC"),
|
|
||||||
// ),
|
|
||||||
// height: 18,
|
|
||||||
// width: 18,
|
|
||||||
// ),
|
|
||||||
const SizedBox(
|
|
||||||
width: 10,
|
|
||||||
),
|
|
||||||
Expanded(
|
|
||||||
child: Text(
|
|
||||||
"USD",
|
|
||||||
style: STextStyles.largeMedium14(context),
|
|
||||||
)),
|
|
||||||
SvgPicture.asset(
|
|
||||||
Assets.svg.chevronDown,
|
|
||||||
color: Theme.of(context)
|
|
||||||
.extension<StackColors>()!
|
.extension<StackColors>()!
|
||||||
.buttonTextSecondaryDisabled,
|
.textFieldDefaultBG,
|
||||||
width: 10,
|
child: Padding(
|
||||||
height: 5,
|
padding: const EdgeInsets.all(12),
|
||||||
),
|
child: Row(children: <Widget>[
|
||||||
]),
|
SvgPicture.asset(
|
||||||
),
|
Assets.svg.iconFor(
|
||||||
),
|
coin: coinFromTickerCaseInsensitive("BTC"),
|
||||||
),
|
),
|
||||||
),
|
height: 18,
|
||||||
SizedBox(
|
width: 18,
|
||||||
height: isDesktop ? 10 : 4,
|
),
|
||||||
),
|
const SizedBox(
|
||||||
Row(
|
width: 10,
|
||||||
crossAxisAlignment: CrossAxisAlignment.end,
|
),
|
||||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
Expanded(
|
||||||
children: [
|
child: Text(
|
||||||
Text(
|
"BTC",
|
||||||
buyWithFiat ? "Enter amount" : "Enter crypto amount",
|
style: STextStyles.largeMedium14(context),
|
||||||
style: STextStyles.itemSubtitle(context).copyWith(
|
)),
|
||||||
color: Theme.of(context).extension<StackColors>()!.textDark3,
|
SvgPicture.asset(
|
||||||
),
|
Assets.svg.chevronDown,
|
||||||
),
|
|
||||||
const FiatCryptoToggle(),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
// // these reads should be watch
|
|
||||||
// if (ref.watch(buyFormStateProvider).fromAmount != null &&
|
|
||||||
// ref.watch(buyFormStateProvider).fromAmount != Decimal.zero)
|
|
||||||
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<StackColors>()!.textDark,
|
|
||||||
),
|
|
||||||
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)
|
color: Theme.of(context)
|
||||||
.extension<StackColors>()!
|
.extension<StackColors>()!
|
||||||
.accentColorDark),
|
.buttonTextSecondaryDisabled,
|
||||||
|
width: 10,
|
||||||
|
height: 5,
|
||||||
|
),
|
||||||
|
]),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
SizedBox(
|
||||||
SizedBox(
|
height: isDesktop ? 20 : 12,
|
||||||
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<StackColors>()!.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(
|
Row(
|
||||||
key: const Key("buyViewReceiveAddressFieldKey"),
|
crossAxisAlignment: CrossAxisAlignment.end,
|
||||||
controller: _receiveAddressController,
|
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||||
readOnly: false,
|
children: [
|
||||||
autocorrect: false,
|
Text(
|
||||||
enableSuggestions: false,
|
"I want to pay with",
|
||||||
// inputFormatters: <TextInputFormatter>[
|
style: STextStyles.itemSubtitle(context).copyWith(
|
||||||
// FilteringTextInputFormatter.allow(
|
color:
|
||||||
// RegExp("[a-zA-Z0-9]{34}")),
|
Theme.of(context).extension<StackColors>()!.textDark3,
|
||||||
// ],
|
),
|
||||||
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 ${coin.ticker} address",*/
|
|
||||||
"Enter address",
|
|
||||||
_receiveAddressFocusNode,
|
|
||||||
context,
|
|
||||||
).copyWith(
|
|
||||||
contentPadding: const EdgeInsets.only(
|
|
||||||
left: 16,
|
|
||||||
top: 6,
|
|
||||||
bottom: 8,
|
|
||||||
right: 5,
|
|
||||||
),
|
),
|
||||||
suffixIcon: Padding(
|
],
|
||||||
padding: _receiveAddressController.text.isEmpty
|
),
|
||||||
? const EdgeInsets.only(right: 8)
|
SizedBox(
|
||||||
: const EdgeInsets.only(right: 0),
|
height: isDesktop ? 10 : 4,
|
||||||
child: UnconstrainedBox(
|
),
|
||||||
child: Row(
|
|
||||||
mainAxisAlignment: MainAxisAlignment.spaceAround,
|
|
||||||
children: [
|
|
||||||
_addressToggleFlag
|
|
||||||
? TextFieldIconButton(
|
|
||||||
key: const Key(
|
|
||||||
"buyViewClearAddressFieldButtonKey"),
|
|
||||||
onTap: () {
|
|
||||||
_receiveAddressController.text = "";
|
|
||||||
// _receiveAddress = "";
|
|
||||||
setState(() {
|
|
||||||
_addressToggleFlag = false;
|
|
||||||
});
|
|
||||||
},
|
|
||||||
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;
|
MouseRegion(
|
||||||
_address = content;
|
cursor: SystemMouseCursors.click,
|
||||||
|
onEnter: (_) => setState(() => _hovering2 = true),
|
||||||
|
onExit: (_) => setState(() => _hovering2 = false),
|
||||||
|
child: GestureDetector(
|
||||||
|
onTap: () {
|
||||||
|
selectFiat();
|
||||||
|
},
|
||||||
|
child: RoundedContainer(
|
||||||
|
padding:
|
||||||
|
const EdgeInsets.symmetric(vertical: 3, horizontal: 6),
|
||||||
|
color: _hovering2
|
||||||
|
? Theme.of(context)
|
||||||
|
.extension<StackColors>()!
|
||||||
|
.highlight
|
||||||
|
.withOpacity(_hovering2 ? 0.3 : 0)
|
||||||
|
: Theme.of(context)
|
||||||
|
.extension<StackColors>()!
|
||||||
|
.textFieldDefaultBG,
|
||||||
|
child: Padding(
|
||||||
|
padding: const EdgeInsets.all(12),
|
||||||
|
child: Row(children: <Widget>[
|
||||||
|
RoundedContainer(
|
||||||
|
radiusMultiplier: 0.5,
|
||||||
|
padding: const EdgeInsets.symmetric(
|
||||||
|
vertical: 2, horizontal: 4),
|
||||||
|
color: Theme.of(context)
|
||||||
|
.extension<StackColors>()!
|
||||||
|
.highlight,
|
||||||
|
child: Text(
|
||||||
|
"\$",
|
||||||
|
style: STextStyles.itemSubtitle12(context),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
// SvgPicture.asset(
|
||||||
|
// Assets.svg.iconFor(
|
||||||
|
// coin: coinFromTickerCaseInsensitive("BTC"),
|
||||||
|
// ),
|
||||||
|
// height: 18,
|
||||||
|
// width: 18,
|
||||||
|
// ),
|
||||||
|
const SizedBox(
|
||||||
|
width: 10,
|
||||||
|
),
|
||||||
|
Expanded(
|
||||||
|
child: Text(
|
||||||
|
"USD",
|
||||||
|
style: STextStyles.largeMedium14(context),
|
||||||
|
)),
|
||||||
|
SvgPicture.asset(
|
||||||
|
Assets.svg.chevronDown,
|
||||||
|
color: Theme.of(context)
|
||||||
|
.extension<StackColors>()!
|
||||||
|
.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<StackColors>()!.textDark3,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
const FiatCryptoToggle(),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
// // these reads should be watch
|
||||||
|
// if (ref.watch(buyFormStateProvider).fromAmount != null &&
|
||||||
|
// ref.watch(buyFormStateProvider).fromAmount != Decimal.zero)
|
||||||
|
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<StackColors>()!.textDark,
|
||||||
|
),
|
||||||
|
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,
|
||||||
|
),
|
||||||
|
Row(
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.end,
|
||||||
|
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||||
|
children: [
|
||||||
|
Text(
|
||||||
|
"Enter receiving address",
|
||||||
|
style: STextStyles.itemSubtitle(context).copyWith(
|
||||||
|
color:
|
||||||
|
Theme.of(context).extension<StackColors>()!.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: <TextInputFormatter>[
|
||||||
|
// 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 ${coin.ticker} address",*/
|
||||||
|
"Enter address",
|
||||||
|
_receiveAddressFocusNode,
|
||||||
|
context,
|
||||||
|
).copyWith(
|
||||||
|
contentPadding: const EdgeInsets.only(
|
||||||
|
left: 16,
|
||||||
|
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 = "";
|
||||||
|
// _receiveAddress = "";
|
||||||
|
setState(() {
|
||||||
|
_addressToggleFlag = false;
|
||||||
|
});
|
||||||
|
},
|
||||||
|
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)
|
||||||
|
TextFieldIconButton(
|
||||||
|
key: const Key("buyViewAddressBookButtonKey"),
|
||||||
|
onTap: () {
|
||||||
|
Navigator.of(context).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<void>.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(() {
|
setState(() {
|
||||||
_addressToggleFlag =
|
_addressToggleFlag =
|
||||||
|
@ -704,100 +791,38 @@ class _BuyFormState extends ConsumerState<BuyForm> {
|
||||||
.text.isNotEmpty;
|
.text.isNotEmpty;
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
},
|
} on PlatformException catch (e, s) {
|
||||||
child: _receiveAddressController.text.isEmpty
|
// here we ignore the exception caused by not giving permission
|
||||||
? const ClipboardIcon()
|
// to use the camera to scan a qr code
|
||||||
: const XIcon(),
|
Logging.instance.log(
|
||||||
),
|
"Failed to get camera permissions while trying to scan qr code in SendView: $e\n$s",
|
||||||
if (_receiveAddressController.text.isEmpty)
|
level: LogLevel.Warning,
|
||||||
TextFieldIconButton(
|
);
|
||||||
key: const Key("buyViewAddressBookButtonKey"),
|
|
||||||
onTap: () {
|
|
||||||
Navigator.of(context).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<void>.delayed(
|
|
||||||
const Duration(milliseconds: 75));
|
|
||||||
}
|
}
|
||||||
|
},
|
||||||
final qrResult = await scanner.scan();
|
child: const QrCodeIcon(),
|
||||||
|
),
|
||||||
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(
|
||||||
SizedBox(
|
height: isDesktop ? 20 : 12,
|
||||||
height: isDesktop ? 20 : 12,
|
),
|
||||||
),
|
PrimaryButton(
|
||||||
PrimaryButton(
|
buttonHeight: isDesktop ? ButtonHeight.l : null,
|
||||||
buttonHeight: isDesktop ? ButtonHeight.l : null,
|
enabled: ref.watch(exchangeFormStateProvider
|
||||||
enabled: ref.watch(
|
.select((value) => value.canExchange)),
|
||||||
exchangeFormStateProvider.select((value) => value.canExchange)),
|
onPressed: () {
|
||||||
onPressed: () {
|
// preview buy quote
|
||||||
// preview buy quote
|
},
|
||||||
},
|
label: "Preview quote",
|
||||||
label: "Preview quote",
|
)
|
||||||
)
|
],
|
||||||
],
|
),
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in a new issue