zwallet/lib/send.dart

273 lines
9.0 KiB
Dart
Raw Normal View History

2021-07-07 08:40:05 -07:00
import 'dart:isolate';
2021-06-26 07:30:12 -07:00
import 'package:barcode_scan/barcode_scan.dart';
import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart';
import 'package:flutter_masked_text/flutter_masked_text.dart';
import 'package:material_design_icons_flutter/material_design_icons_flutter.dart';
import 'package:warp_api/warp_api.dart';
2021-07-07 08:40:05 -07:00
import 'package:decimal/decimal.dart';
import 'dart:math' as math;
2021-06-26 07:30:12 -07:00
import 'main.dart';
2021-07-07 08:40:05 -07:00
import 'store.dart';
2021-06-26 07:30:12 -07:00
class SendPage extends StatefulWidget {
SendPage();
@override
SendState createState() => SendState();
}
class SendState extends State<SendPage> {
final _formKey = GlobalKey<FormState>();
var _address = "";
2021-07-07 08:40:05 -07:00
var _amount = Decimal.zero;
var _maxAmountPerNote = Decimal.zero;
2021-06-26 07:30:12 -07:00
var _balance = 0;
final _addressController = TextEditingController();
2021-07-07 08:40:05 -07:00
var _mZEC = true;
var _currencyController = _makeMoneyMaskedTextController(true);
var _maxAmountPerNoteController = _makeMoneyMaskedTextController(true);
var _includeFee = false;
var _isExpanded = false;
2021-06-26 07:30:12 -07:00
@override
initState() {
Future.microtask(() async {
2021-07-07 08:40:05 -07:00
final balance = await accountManager
2021-07-09 06:33:39 -07:00
.getBalanceSpendable(syncStatus.latestHeight - settings.anchorOffset);
2021-06-26 07:30:12 -07:00
setState(() {
2021-07-07 08:40:05 -07:00
_balance = math.max(balance - DEFAULT_FEE, 0);
2021-06-26 07:30:12 -07:00
});
});
super.initState();
}
@override
Widget build(BuildContext context) {
return Scaffold(
2021-07-07 08:40:05 -07:00
appBar: AppBar(title: Text('Send ZEC')),
body: Form(
key: _formKey,
child: SingleChildScrollView(
padding: EdgeInsets.all(20),
child: Column(children: <Widget>[
Row(children: <Widget>[
Expanded(
child: TextFormField(
decoration:
InputDecoration(labelText: 'Send ZEC to...'),
minLines: 4,
maxLines: null,
keyboardType: TextInputType.multiline,
controller: _addressController,
onSaved: _onAddress,
validator: _checkAddress,
),
2021-06-26 07:30:12 -07:00
),
2021-07-07 08:40:05 -07:00
IconButton(
icon: new Icon(MdiIcons.qrcodeScan), onPressed: _onScan)
]),
Row(children: [
Expanded(
child: TextFormField(
decoration: InputDecoration(labelText: 'Amount'),
keyboardType: TextInputType.number,
controller: _currencyController,
validator: _checkAmount,
onSaved: _onAmount)),
TextButton(child: Text('MAX'), onPressed: _onMax),
]),
ExpansionPanelList(
expansionCallback: (_, isExpanded) {
setState(() {
_isExpanded = !isExpanded;
});
},
children: [
ExpansionPanel(
headerBuilder: (_, __) =>
ListTile(title: Text('Advanced Options')),
body: Column(children: [
CheckboxListTile(
title: Text('Round to mZEC'),
value: _mZEC,
onChanged: _onChangedmZEC),
CheckboxListTile(
title: Text('Include Fee in Amount'),
value: _includeFee,
onChanged: _onChangedIncludeFee),
ListTile(
title: TextFormField(
decoration: InputDecoration(
labelText: 'Max Amount per Note'),
keyboardType: TextInputType.number,
controller: _maxAmountPerNoteController,
validator: _checkMaxAmountPerNote,
onSaved: _onSavedMaxAmountPerNote,
))
]),
isExpanded: _isExpanded)
]),
Padding(padding: EdgeInsets.all(8)),
Text("Spendable: ${_balance / ZECUNIT} ZEC"),
ButtonBar(children: [
IconButton(
icon: new Icon(MdiIcons.cancel), onPressed: _onCancel),
IconButton(
icon: new Icon(MdiIcons.send), onPressed: _onSend),
])
]))));
2021-06-26 07:30:12 -07:00
}
2021-07-07 08:40:05 -07:00
String _checkAddress(String v) {
2021-06-26 07:30:12 -07:00
if (v.isEmpty) return 'Address is empty';
if (!WarpApi.validAddress(v)) return 'Invalid Address';
return null;
}
2021-07-07 08:40:05 -07:00
String _checkAmount(String vs) {
2021-06-26 07:30:12 -07:00
final v = double.tryParse(vs);
if (v == null) return 'Amount must be a number';
if (v <= 0.0) return 'Amount must be positive';
if (v > _balance) return 'Not enough balance';
return null;
}
2021-07-07 08:40:05 -07:00
String _checkMaxAmountPerNote(String vs) {
final v = double.tryParse(vs);
if (v == null) return 'Amount must be a number';
if (v < 0.0) return 'Amount must be positive';
return null;
}
void _onMax() {
setState(() {
_mZEC = false;
_currencyController = _makeMoneyMaskedTextController(false);
_includeFee = false;
_currencyController.updateValue(
(Decimal.fromInt(_balance) / ZECUNIT_DECIMAL).toDouble());
});
}
void _onChangedIncludeFee(bool v) {
setState(() {
_includeFee = v;
});
}
void _onChangedmZEC(bool v) {
setState(() {
_mZEC = v;
final amount = _currencyController.numberValue;
_currencyController = _makeMoneyMaskedTextController(v);
_currencyController.updateValue(amount);
_maxAmountPerNoteController = _makeMoneyMaskedTextController(v);
});
}
void _onCancel() {
2021-06-26 07:30:12 -07:00
Navigator.of(context).pop();
}
2021-07-07 08:40:05 -07:00
void _onScan() async {
2021-06-26 07:30:12 -07:00
var code = await BarcodeScanner.scan();
setState(() {
_address = code.rawContent;
_addressController.text = _address;
});
}
2021-07-07 08:40:05 -07:00
void _onAmount(v) {
_amount = Decimal.parse(v);
2021-06-26 07:30:12 -07:00
}
2021-07-07 08:40:05 -07:00
void _onAddress(v) {
2021-06-26 07:30:12 -07:00
_address = v;
}
2021-07-07 08:40:05 -07:00
void _onSavedMaxAmountPerNote(v) {
_maxAmountPerNote = Decimal.parse(v);
}
void _onSend() async {
2021-06-26 07:30:12 -07:00
final form = _formKey.currentState;
if (form == null) return;
if (form.validate()) {
form.save();
2021-07-07 08:40:05 -07:00
final approved = await showDialog(
context: context,
barrierDismissible: false,
builder: (BuildContext context) => AlertDialog(
title: Text('Please Confirm'),
content: SingleChildScrollView(
child: Text("Sending $_amount ZEC to $_address")),
actions: <Widget>[
TextButton(
child: Text('Cancel'),
onPressed: () {
Navigator.of(context).pop(false);
}),
TextButton(
child: Text('Approve'),
onPressed: () {
Navigator.of(context).pop(true);
}),
],
));
2021-06-26 07:30:12 -07:00
if (approved) {
Navigator.of(context).pop();
2021-07-07 08:40:05 -07:00
final snackBar1 = SnackBar(content: Text("Preparing transaction..."));
2021-06-26 07:30:12 -07:00
rootScaffoldMessengerKey.currentState.showSnackBar(snackBar1);
2021-07-07 08:40:05 -07:00
int amount = (_amount * ZECUNIT_DECIMAL).toInt();
if (_includeFee) amount -= DEFAULT_FEE;
int maxAmountPerNote = (_maxAmountPerNote * ZECUNIT_DECIMAL).toInt();
final tx = await compute(
sendPayment,
SendPaymentParam(
2021-07-09 06:33:39 -07:00
accountManager.active.id,
_address,
amount,
maxAmountPerNote,
settings.anchorOffset,
progressPort.sendPort));
2021-06-26 07:30:12 -07:00
2021-07-07 08:40:05 -07:00
final snackBar2 = SnackBar(content: Text("TX ID: $tx"));
2021-06-26 07:30:12 -07:00
rootScaffoldMessengerKey.currentState.showSnackBar(snackBar2);
}
}
}
2021-07-07 08:40:05 -07:00
static MoneyMaskedTextController _makeMoneyMaskedTextController(bool mZEC) =>
MoneyMaskedTextController(
decimalSeparator: '.',
thousandSeparator: ',',
precision: mZEC ? 3 : 8);
2021-06-26 07:30:12 -07:00
}
class SendPaymentParam {
int account;
String address;
int amount;
2021-07-07 08:40:05 -07:00
int maxAmountPerNote;
2021-07-09 06:33:39 -07:00
int anchorOffset;
2021-07-07 08:40:05 -07:00
SendPort sendPort;
2021-06-26 07:30:12 -07:00
2021-07-09 06:33:39 -07:00
SendPaymentParam(this.account, this.address, this.amount,
this.maxAmountPerNote, this.anchorOffset, this.sendPort);
2021-06-26 07:30:12 -07:00
}
2021-07-07 08:40:05 -07:00
sendPayment(SendPaymentParam param) async {
param.sendPort.send(0);
2021-07-09 06:33:39 -07:00
final tx = await WarpApi.sendPayment(param.account, param.address,
param.amount, param.maxAmountPerNote, param.anchorOffset, (percent) {
2021-07-07 08:40:05 -07:00
param.sendPort.send(percent);
});
param.sendPort.send(0);
2021-06-26 07:30:12 -07:00
return tx;
}