| 1 | import 'package:cake_wallet/bitcoin/bitcoin.dart'; |
| 2 | import 'package:cake_wallet/src/widgets/standard_switch.dart'; |
| 3 | import 'package:cake_wallet/utils/date_picker.dart'; |
| 4 | import 'package:cake_wallet/wownero/wownero.dart'; |
| 5 | import 'package:cake_wallet/zcash/zcash.dart'; |
| 6 | import 'package:cw_core/wallet_type.dart'; |
| 7 | import 'package:flutter/material.dart'; |
| 8 | import 'package:intl/intl.dart'; |
| 9 | import 'package:cake_wallet/generated/i18n.dart'; |
| 10 | import 'package:cake_wallet/monero/monero.dart'; |
| 11 | import 'package:cake_wallet/src/widgets/base_text_form_field.dart'; |
| 12 | import 'package:cake_wallet/decred/decred.dart'; |
| 13 | |
| 14 | class BlockchainHeightWidget extends StatefulWidget { |
| 15 | BlockchainHeightWidget({ |
| 16 | GlobalKey? key, |
| 17 | this.onHeightChange, |
| 18 | this.focusNode, |
| 19 | this.onHeightOrDateEntered, |
| 20 | this.hasDatePicker = true, |
| 21 | this.isSilentPaymentsScan = false, |
| 22 | this.isMwebScan = false, |
| 23 | this.toggleSingleScan, |
| 24 | this.doSingleScan = false, |
| 25 | this.bitcoinMempoolAPIEnabled, |
| 26 | required this.walletType, |
| 27 | this.blockHeightTextFieldKey, |
| 28 | this.heightController, |
| 29 | }) : super(key: key); |
| 30 | |
| 31 | final Function(int)? onHeightChange; |
| 32 | final Function(bool)? onHeightOrDateEntered; |
| 33 | final FocusNode? focusNode; |
| 34 | final bool hasDatePicker; |
| 35 | final bool isSilentPaymentsScan; |
| 36 | final bool isMwebScan; |
| 37 | final TextEditingController? heightController; |
| 38 | final bool doSingleScan; |
| 39 | final Future<bool>? bitcoinMempoolAPIEnabled; |
| 40 | final Function()? toggleSingleScan; |
| 41 | final WalletType walletType; |
| 42 | final Key? blockHeightTextFieldKey; |
| 43 | @override |
| 44 | State<StatefulWidget> createState() => BlockchainHeightState(); |
| 45 | } |
| 46 | |
| 47 | class BlockchainHeightState extends State<BlockchainHeightWidget> { |
| 48 | final dateController = TextEditingController(); |
| 49 | late final TextEditingController restoreHeightController; |
| 50 | |
| 51 | int get height => _height; |
| 52 | int _height = 0; |
| 53 | |
| 54 | @override |
| 55 | void initState() { |
| 56 | restoreHeightController = widget.heightController ?? TextEditingController(); |
| 57 | |
| 58 | restoreHeightController.addListener(() { |
| 59 | if (restoreHeightController.text.isNotEmpty) { |
| 60 | widget.onHeightOrDateEntered?.call(true); |
| 61 | } else { |
| 62 | widget.onHeightOrDateEntered?.call(false); |
| 63 | dateController.text = ''; |
| 64 | } |
| 65 | try { |
| 66 | final int _height; |
| 67 | if (restoreHeightController.text.isNotEmpty) { |
| 68 | final digits = restoreHeightController.text.replaceAll(RegExp(r'[^0-9]'), ''); |
| 69 | _height = int.tryParse(digits) ?? 0; |
| 70 | } else { |
| 71 | _height = 0; |
| 72 | } |
| 73 | _changeHeight(_height); |
| 74 | } catch (_) { |
| 75 | _changeHeight(0); |
| 76 | } |
| 77 | }); |
| 78 | |
| 79 | super.initState(); |
| 80 | } |
| 81 | |
| 82 | @override |
| 83 | Widget build(BuildContext context) { |
| 84 | return Column( |
| 85 | crossAxisAlignment: CrossAxisAlignment.start, |
| 86 | children: <Widget>[ |
| 87 | Row( |
| 88 | children: <Widget>[ |
| 89 | Flexible( |
| 90 | child: Container( |
| 91 | padding: EdgeInsets.only(top: 20.0, bottom: 10.0), |
| 92 | child: BaseTextFormField( |
| 93 | key: widget.blockHeightTextFieldKey, |
| 94 | focusNode: widget.focusNode, |
| 95 | controller: restoreHeightController, |
| 96 | keyboardType: TextInputType.numberWithOptions( |
| 97 | signed: false, |
| 98 | decimal: false, |
| 99 | ), |
| 100 | hintText: widget.isSilentPaymentsScan |
| 101 | ? S.of(context).silent_payments_scan_from_height |
| 102 | : S.of(context).widgets_restore_from_blockheight, |
| 103 | ), |
| 104 | ), |
| 105 | ) |
| 106 | ], |
| 107 | ), |
| 108 | if (widget.hasDatePicker) ...[ |
| 109 | Padding( |
| 110 | padding: EdgeInsets.only(top: 15, bottom: 15), |
| 111 | child: Text( |
| 112 | S.of(context).widgets_or, |
| 113 | style: Theme.of(context).textTheme.bodyMedium?.copyWith( |
| 114 | fontSize: 16, |
| 115 | fontWeight: FontWeight.w500, |
| 116 | color: Theme.of(context).colorScheme.onSurfaceVariant, |
| 117 | ), |
| 118 | ), |
| 119 | ), |
| 120 | Row( |
| 121 | children: <Widget>[ |
| 122 | Flexible( |
| 123 | child: Container( |
| 124 | child: InkWell( |
| 125 | onTap: () => _selectDate(context), |
| 126 | child: IgnorePointer( |
| 127 | child: BaseTextFormField( |
| 128 | controller: dateController, |
| 129 | hintText: widget.isSilentPaymentsScan |
| 130 | ? S.of(context).silent_payments_scan_from_date |
| 131 | : S.of(context).widgets_restore_from_date, |
| 132 | ), |
| 133 | ), |
| 134 | ), |
| 135 | )) |
| 136 | ], |
| 137 | ), |
| 138 | if (widget.isSilentPaymentsScan) |
| 139 | Padding( |
| 140 | padding: EdgeInsets.only(top: 24), |
| 141 | child: Row( |
| 142 | mainAxisAlignment: MainAxisAlignment.spaceBetween, |
| 143 | children: [ |
| 144 | Text( |
| 145 | S.of(context).scan_one_block, |
| 146 | style: Theme.of(context).textTheme.bodyMedium?.copyWith( |
| 147 | color: Theme.of(context).colorScheme.onSurfaceVariant, |
| 148 | ), |
| 149 | ), |
| 150 | Padding( |
| 151 | padding: const EdgeInsets.only(right: 8), |
| 152 | child: StandardSwitch( |
| 153 | value: widget.doSingleScan, |
| 154 | onTapped: () => widget.toggleSingleScan?.call(), |
| 155 | ), |
| 156 | ) |
| 157 | ], |
| 158 | ), |
| 159 | ), |
| 160 | Padding( |
| 161 | padding: EdgeInsets.only(left: 40, right: 40, top: 24), |
| 162 | child: Text( |
| 163 | widget.isSilentPaymentsScan |
| 164 | ? S.of(context).silent_payments_scan_from_date_or_blockheight |
| 165 | : S.of(context).restore_from_date_or_blockheight, |
| 166 | textAlign: TextAlign.center, |
| 167 | style: Theme.of(context).textTheme.bodySmall?.copyWith( |
| 168 | color: Theme.of(context).colorScheme.onSurfaceVariant, |
| 169 | ), |
| 170 | ), |
| 171 | ) |
| 172 | ] |
| 173 | ], |
| 174 | ); |
| 175 | } |
| 176 | |
| 177 | Future<void> _selectDate(BuildContext context) async { |
| 178 | final now = DateTime.now(); |
| 179 | final date = await getDate( |
| 180 | context: context, |
| 181 | initialDate: now.subtract(Duration(days: 1)), |
| 182 | firstDate: DateTime(2014, DateTime.may), |
| 183 | lastDate: now); |
| 184 | |
| 185 | if (date != null) { |
| 186 | int height; |
| 187 | if (widget.isMwebScan) { |
| 188 | height = bitcoin!.getLitecoinHeightByDate(date: date); |
| 189 | } else if (widget.isSilentPaymentsScan) { |
| 190 | height = await bitcoin!.getHeightByDate( |
| 191 | date: date, |
| 192 | bitcoinMempoolAPIEnabled: await widget.bitcoinMempoolAPIEnabled, |
| 193 | ); |
| 194 | } else { |
| 195 | if (widget.walletType == WalletType.decred) { |
| 196 | height = decred!.heightByDate(date); |
| 197 | } else if (widget.walletType == WalletType.monero) { |
| 198 | height = monero!.getHeightByDate(date: date); |
| 199 | } else if (widget.walletType == WalletType.wownero) { |
| 200 | height = wownero!.getHeightByDate(date: date); |
| 201 | } else if (widget.walletType == WalletType.zcash) { |
| 202 | height = await zcash!.getHeightByDate(date); |
| 203 | } else { |
| 204 | throw Exception("unknown currency in BlockchainHeightWidget"); |
| 205 | } |
| 206 | } |
| 207 | if (mounted) { |
| 208 | setState(() { |
| 209 | dateController.text = DateFormat('yyyy-MM-dd').format(date); |
| 210 | restoreHeightController.text = '$height'; |
| 211 | _changeHeight(height); |
| 212 | }); |
| 213 | } |
| 214 | } |
| 215 | } |
| 216 | |
| 217 | void _changeHeight(int height) { |
| 218 | _height = height; |
| 219 | widget.onHeightChange?.call(height); |
| 220 | } |
| 221 | } |