2021-09-25 02:09:41 -07:00
|
|
|
import 'dart:convert';
|
2021-08-05 06:43:05 -07:00
|
|
|
import 'dart:io';
|
2021-09-26 22:31:55 -07:00
|
|
|
import 'dart:ui';
|
2021-08-05 06:43:05 -07:00
|
|
|
|
2021-06-26 07:30:12 -07:00
|
|
|
import 'package:flutter/foundation.dart';
|
|
|
|
import 'package:flutter/material.dart';
|
2021-08-06 00:53:54 -07:00
|
|
|
import 'package:mobx/mobx.dart';
|
2021-06-26 07:30:12 -07:00
|
|
|
import 'package:material_design_icons_flutter/material_design_icons_flutter.dart';
|
2021-10-09 07:17:27 -07:00
|
|
|
import 'package:warp/dualmoneyinput.dart';
|
2021-11-11 17:44:46 -08:00
|
|
|
import 'package:warp_api/types.dart';
|
2021-06-26 07:30:12 -07:00
|
|
|
import 'package:warp_api/warp_api.dart';
|
2021-07-07 08:40:05 -07:00
|
|
|
import 'package:decimal/decimal.dart';
|
2021-08-05 06:43:05 -07:00
|
|
|
import 'package:path_provider/path_provider.dart';
|
|
|
|
import 'package:share_plus/share_plus.dart';
|
2021-09-12 04:24:40 -07:00
|
|
|
import 'package:flutter_typeahead/flutter_typeahead.dart';
|
2021-07-07 08:40:05 -07:00
|
|
|
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-08-15 09:18:09 -07:00
|
|
|
import 'generated/l10n.dart';
|
2021-06-26 07:30:12 -07:00
|
|
|
|
|
|
|
class SendPage extends StatefulWidget {
|
2021-10-03 08:47:44 -07:00
|
|
|
final SendPageArgs? args;
|
2021-08-06 00:53:54 -07:00
|
|
|
|
2021-10-03 08:47:44 -07:00
|
|
|
SendPage(this.args);
|
2021-06-26 07:30:12 -07:00
|
|
|
|
|
|
|
@override
|
|
|
|
SendState createState() => SendState();
|
2021-11-11 17:44:46 -08:00
|
|
|
|
|
|
|
bool get isMulti => args?.isMulti ?? false;
|
2021-06-26 07:30:12 -07:00
|
|
|
}
|
|
|
|
|
|
|
|
class SendState extends State<SendPage> {
|
2021-09-21 05:52:52 -07:00
|
|
|
static final zero = decimalFormat(0, 3);
|
2021-06-26 07:30:12 -07:00
|
|
|
final _formKey = GlobalKey<FormState>();
|
2021-10-09 07:17:27 -07:00
|
|
|
final _amountKey = GlobalKey<DualMoneyInputState>();
|
2021-06-26 07:30:12 -07:00
|
|
|
var _address = "";
|
2021-07-07 08:40:05 -07:00
|
|
|
var _maxAmountPerNote = Decimal.zero;
|
2021-09-27 01:57:55 -07:00
|
|
|
var _sBalance = 0;
|
|
|
|
var _tBalance = 0;
|
2021-09-26 22:31:55 -07:00
|
|
|
var _excludedBalance = 0;
|
|
|
|
var _underConfirmedBalance = 0;
|
2021-10-04 02:20:42 -07:00
|
|
|
var _unconfirmedSpentBalance = 0;
|
|
|
|
var _unconfirmedBalance = 0;
|
2021-06-26 07:30:12 -07:00
|
|
|
final _addressController = TextEditingController();
|
2021-07-09 22:44:34 -07:00
|
|
|
final _memoController = TextEditingController();
|
2021-09-21 05:52:52 -07:00
|
|
|
final _maxAmountController = TextEditingController(text: zero);
|
2021-07-07 08:40:05 -07:00
|
|
|
var _isExpanded = false;
|
2021-10-09 07:17:27 -07:00
|
|
|
var _useMillis = true;
|
2021-10-11 02:16:35 -07:00
|
|
|
var _useTransparent = settings.shieldBalance;
|
2021-09-26 22:31:55 -07:00
|
|
|
ReactionDisposer? _newBlockAutorunDispose;
|
2021-11-11 17:44:46 -08:00
|
|
|
final _fee = DEFAULT_FEE;
|
|
|
|
var _usedBalance = 0;
|
2021-06-26 07:30:12 -07:00
|
|
|
|
|
|
|
@override
|
|
|
|
initState() {
|
2021-10-03 08:47:44 -07:00
|
|
|
if (widget.args?.contact != null)
|
|
|
|
_addressController.text = widget.args!.contact!.address;
|
2021-11-11 17:44:46 -08:00
|
|
|
final recipients = widget.args?.recipients ?? [];
|
|
|
|
_usedBalance = recipients.fold(0, (acc, r) => acc + r.amount);
|
2021-10-03 08:47:44 -07:00
|
|
|
|
2021-10-09 19:11:40 -07:00
|
|
|
final uri = widget.args?.uri;
|
|
|
|
if (uri != null)
|
|
|
|
Future.microtask(() {
|
|
|
|
_setPaymentURI(uri);
|
|
|
|
});
|
2021-09-26 22:31:55 -07:00
|
|
|
|
2021-06-26 07:30:12 -07:00
|
|
|
super.initState();
|
2021-08-06 00:53:54 -07:00
|
|
|
|
2021-09-26 22:31:55 -07:00
|
|
|
_newBlockAutorunDispose = autorun((_) async {
|
2021-09-28 05:08:43 -07:00
|
|
|
final _ = syncStatus.latestHeight;
|
2021-09-27 01:57:55 -07:00
|
|
|
final sBalance = await accountManager.getShieldedBalance();
|
|
|
|
final tBalance = accountManager.tbalance;
|
2021-09-26 22:31:55 -07:00
|
|
|
final excludedBalance = await accountManager.getExcludedBalance();
|
2021-09-27 01:57:55 -07:00
|
|
|
final underConfirmedBalance =
|
|
|
|
await accountManager.getUnderConfirmedBalance();
|
2021-11-11 17:44:46 -08:00
|
|
|
final unconfirmedSpentBalance =
|
|
|
|
await accountManager.getUnconfirmedSpentBalance();
|
2021-10-04 02:20:42 -07:00
|
|
|
final unconfirmedBalance = accountManager.unconfirmedBalance;
|
2021-09-26 22:31:55 -07:00
|
|
|
setState(() {
|
2021-09-27 01:57:55 -07:00
|
|
|
_sBalance = sBalance;
|
|
|
|
_tBalance = tBalance;
|
2021-09-26 22:31:55 -07:00
|
|
|
_excludedBalance = excludedBalance;
|
|
|
|
_underConfirmedBalance = underConfirmedBalance;
|
2021-10-04 02:20:42 -07:00
|
|
|
_unconfirmedSpentBalance = unconfirmedSpentBalance;
|
|
|
|
_unconfirmedBalance = unconfirmedBalance;
|
2021-09-26 22:31:55 -07:00
|
|
|
});
|
|
|
|
});
|
2021-06-26 07:30:12 -07:00
|
|
|
}
|
|
|
|
|
2021-08-06 19:18:20 -07:00
|
|
|
@override
|
|
|
|
void dispose() {
|
2021-09-26 22:31:55 -07:00
|
|
|
_newBlockAutorunDispose?.call();
|
2021-08-06 19:18:20 -07:00
|
|
|
super.dispose();
|
|
|
|
}
|
|
|
|
|
2021-06-26 07:30:12 -07:00
|
|
|
@override
|
|
|
|
Widget build(BuildContext context) {
|
2021-09-26 11:44:19 -07:00
|
|
|
final s = S.of(context);
|
2022-02-22 21:20:45 -08:00
|
|
|
final simpleMode = settings.simpleMode;
|
2021-06-26 07:30:12 -07:00
|
|
|
return Scaffold(
|
2021-09-26 11:44:19 -07:00
|
|
|
appBar: AppBar(title: Text(s.sendCointicker(coin.ticker))),
|
|
|
|
body: GestureDetector(
|
|
|
|
onTap: () {
|
|
|
|
FocusScope.of(context).unfocus();
|
|
|
|
},
|
|
|
|
child: Form(
|
|
|
|
key: _formKey,
|
|
|
|
child: SingleChildScrollView(
|
|
|
|
padding: EdgeInsets.all(20),
|
|
|
|
child: Column(children: <Widget>[
|
|
|
|
Row(children: <Widget>[
|
|
|
|
Expanded(
|
2021-09-27 01:57:55 -07:00
|
|
|
child: TypeAheadFormField(
|
|
|
|
textFieldConfiguration: TextFieldConfiguration(
|
|
|
|
controller: _addressController,
|
|
|
|
decoration: InputDecoration(
|
|
|
|
labelText: s.sendCointickerTo(coin.ticker)),
|
|
|
|
minLines: 4,
|
|
|
|
maxLines: 10,
|
|
|
|
keyboardType: TextInputType.multiline,
|
2021-09-26 11:44:19 -07:00
|
|
|
),
|
2021-09-27 01:57:55 -07:00
|
|
|
onSaved: _onAddress,
|
|
|
|
validator: _checkAddress,
|
|
|
|
onSuggestionSelected: (Contact contact) {
|
|
|
|
_addressController.text = contact.name;
|
|
|
|
},
|
|
|
|
suggestionsCallback: (String pattern) {
|
|
|
|
return contacts.contacts.where((c) => c.name
|
|
|
|
.toLowerCase()
|
|
|
|
.contains(pattern.toLowerCase()));
|
|
|
|
},
|
|
|
|
itemBuilder: (BuildContext context, Contact c) =>
|
|
|
|
ListTile(title: Text(c.name)),
|
|
|
|
noItemsFoundBuilder: (_) => SizedBox(),
|
|
|
|
)),
|
2021-09-26 11:44:19 -07:00
|
|
|
IconButton(
|
|
|
|
icon: new Icon(MdiIcons.qrcodeScan),
|
|
|
|
onPressed: _onScan)
|
|
|
|
]),
|
2021-11-11 17:44:46 -08:00
|
|
|
DualMoneyInputWidget(
|
|
|
|
key: _amountKey,
|
|
|
|
child:
|
|
|
|
TextButton(child: Text(s.max), onPressed: _onMax),
|
|
|
|
spendable: spendable),
|
2022-02-22 21:20:45 -08:00
|
|
|
if (!simpleMode) BalanceTable(_sBalance, _tBalance, _useTransparent,
|
2021-11-11 17:44:46 -08:00
|
|
|
_excludedBalance, _underConfirmedBalance, change, _usedBalance, _fee),
|
2022-02-22 21:20:45 -08:00
|
|
|
if (!simpleMode) ExpansionPanelList(
|
2021-09-26 11:44:19 -07:00
|
|
|
expansionCallback: (_, isExpanded) {
|
|
|
|
setState(() {
|
|
|
|
_isExpanded = !isExpanded;
|
|
|
|
});
|
|
|
|
},
|
|
|
|
children: [
|
|
|
|
ExpansionPanel(
|
|
|
|
headerBuilder: (_, __) =>
|
2021-09-27 01:57:55 -07:00
|
|
|
ListTile(title: Text(s.advancedOptions)),
|
2021-09-26 11:44:19 -07:00
|
|
|
body: Column(children: [
|
|
|
|
ListTile(
|
2021-08-06 00:53:54 -07:00
|
|
|
title: TextFormField(
|
2021-09-27 01:57:55 -07:00
|
|
|
decoration:
|
|
|
|
InputDecoration(labelText: s.memo),
|
2021-09-26 11:44:19 -07:00
|
|
|
minLines: 4,
|
|
|
|
maxLines: null,
|
|
|
|
keyboardType: TextInputType.multiline,
|
|
|
|
controller: _memoController,
|
|
|
|
),
|
|
|
|
),
|
|
|
|
CheckboxListTile(
|
|
|
|
title: Text(s.roundToMillis),
|
2021-10-09 07:17:27 -07:00
|
|
|
value: _useMillis,
|
|
|
|
onChanged: _setUseMillis),
|
2021-11-11 17:44:46 -08:00
|
|
|
if (accountManager.canPay && !widget.isMulti)
|
2021-09-26 11:44:19 -07:00
|
|
|
CheckboxListTile(
|
2021-10-11 02:16:35 -07:00
|
|
|
title: Text(s.useTransparentBalance),
|
|
|
|
value: _useTransparent,
|
|
|
|
onChanged: _onChangedUseTransparent,
|
2021-09-26 11:44:19 -07:00
|
|
|
),
|
|
|
|
ListTile(
|
|
|
|
title: TextFormField(
|
2021-09-27 01:57:55 -07:00
|
|
|
decoration: InputDecoration(
|
|
|
|
labelText: s.maxAmountPerNote),
|
|
|
|
keyboardType: TextInputType.number,
|
|
|
|
controller: _maxAmountController,
|
2021-11-11 17:44:46 -08:00
|
|
|
inputFormatters: [
|
|
|
|
makeInputFormatter(amountInput?.useMillis)
|
|
|
|
],
|
2021-09-27 01:57:55 -07:00
|
|
|
validator: _checkMaxAmountPerNote,
|
|
|
|
onSaved: _onSavedMaxAmountPerNote,
|
|
|
|
)),
|
2021-09-26 11:44:19 -07:00
|
|
|
]),
|
|
|
|
isExpanded: _isExpanded,
|
|
|
|
)
|
|
|
|
]),
|
|
|
|
Padding(padding: EdgeInsets.all(8)),
|
|
|
|
ButtonBar(
|
|
|
|
children: confirmButtons(context, _onSend,
|
2021-11-11 17:44:46 -08:00
|
|
|
okLabel: widget.isMulti ? s.add : s.send,
|
|
|
|
okIcon: Icon(MdiIcons.send)))
|
2021-09-26 11:44:19 -07:00
|
|
|
])))));
|
2021-06-26 07:30:12 -07:00
|
|
|
}
|
|
|
|
|
2021-09-10 02:56:15 -07:00
|
|
|
String? _checkAddress(String? v) {
|
2021-09-26 11:44:19 -07:00
|
|
|
final s = S.of(context);
|
|
|
|
if (v == null || v.isEmpty) return s.addressIsEmpty;
|
2021-09-12 04:24:40 -07:00
|
|
|
final c = contacts.contacts.where((c) => c.name == v);
|
|
|
|
if (c.isNotEmpty) return null;
|
2021-08-27 02:51:34 -07:00
|
|
|
final zaddr = WarpApi.getSaplingFromUA(v);
|
|
|
|
if (zaddr.isNotEmpty) return null;
|
2021-09-26 11:44:19 -07:00
|
|
|
if (!WarpApi.validAddress(v)) return s.invalidAddress;
|
2021-06-26 07:30:12 -07:00
|
|
|
return null;
|
|
|
|
}
|
|
|
|
|
2021-09-10 02:56:15 -07:00
|
|
|
String? _checkMaxAmountPerNote(String? vs) {
|
2021-09-26 11:44:19 -07:00
|
|
|
final s = S.of(context);
|
|
|
|
if (vs == null) return s.amountMustBeANumber;
|
|
|
|
if (!checkNumber(vs)) return s.amountMustBeANumber;
|
2021-09-10 02:56:15 -07:00
|
|
|
final v = parseNumber(vs);
|
2021-09-26 11:44:19 -07:00
|
|
|
if (v < 0.0) return s.amountMustBePositive;
|
2021-07-07 08:40:05 -07:00
|
|
|
return null;
|
|
|
|
}
|
|
|
|
|
|
|
|
void _onMax() {
|
|
|
|
setState(() {
|
2021-10-09 07:17:27 -07:00
|
|
|
_useMillis = false;
|
|
|
|
amountInput?.setAmount(spendable);
|
2021-07-07 08:40:05 -07:00
|
|
|
});
|
|
|
|
}
|
|
|
|
|
2021-10-11 02:16:35 -07:00
|
|
|
void _onChangedUseTransparent(bool? v) {
|
2021-09-10 02:56:15 -07:00
|
|
|
if (v == null) return;
|
2021-08-23 05:47:48 -07:00
|
|
|
setState(() {
|
2021-10-11 02:16:35 -07:00
|
|
|
_useTransparent = v;
|
2021-08-23 05:47:48 -07:00
|
|
|
});
|
|
|
|
}
|
|
|
|
|
2021-07-07 08:40:05 -07:00
|
|
|
void _onScan() async {
|
2021-09-10 02:56:15 -07:00
|
|
|
final code = await scanCode(context);
|
2021-09-25 02:09:41 -07:00
|
|
|
if (code != null) {
|
|
|
|
if (_checkAddress(code) != null) {
|
2021-10-03 08:47:44 -07:00
|
|
|
_setPaymentURI(code); // not an address
|
2021-09-26 11:44:19 -07:00
|
|
|
} else {
|
2021-09-25 02:09:41 -07:00
|
|
|
setState(() {
|
|
|
|
_address = code;
|
|
|
|
_addressController.text = _address;
|
|
|
|
});
|
|
|
|
}
|
|
|
|
}
|
2021-06-26 07:30:12 -07:00
|
|
|
}
|
2021-10-03 08:47:44 -07:00
|
|
|
|
2021-10-09 07:17:27 -07:00
|
|
|
void _setUseMillis(bool? vv) {
|
|
|
|
final v = vv ?? false;
|
|
|
|
amountInput?.setMillis(v);
|
|
|
|
setState(() {
|
|
|
|
_useMillis = v;
|
|
|
|
});
|
|
|
|
}
|
|
|
|
|
2021-10-03 08:47:44 -07:00
|
|
|
void _setPaymentURI(String uri) {
|
|
|
|
final json = WarpApi.parsePaymentURI(uri);
|
|
|
|
try {
|
|
|
|
final payment = DecodedPaymentURI.fromJson(jsonDecode(json));
|
|
|
|
setState(() {
|
|
|
|
_address = payment.address;
|
|
|
|
_addressController.text = _address;
|
|
|
|
_memoController.text = payment.memo;
|
2021-10-09 07:17:27 -07:00
|
|
|
amountInput?.setAmount(payment.amount);
|
2021-10-03 08:47:44 -07:00
|
|
|
});
|
|
|
|
} on FormatException {}
|
|
|
|
}
|
2021-06-26 07:30:12 -07:00
|
|
|
|
2021-07-07 08:40:05 -07:00
|
|
|
void _onAddress(v) {
|
2021-09-12 04:24:40 -07:00
|
|
|
final c = contacts.contacts.where((c) => c.name == v);
|
|
|
|
if (c.isEmpty)
|
|
|
|
_address = v;
|
|
|
|
else {
|
|
|
|
_address = c.first.address;
|
|
|
|
}
|
2021-06-26 07:30:12 -07:00
|
|
|
}
|
|
|
|
|
2021-09-21 07:16:31 -07:00
|
|
|
void _onSavedMaxAmountPerNote(vs) {
|
|
|
|
final v = parseNumber(vs);
|
|
|
|
_maxAmountPerNote = Decimal.parse(v.toString());
|
2021-07-07 08:40:05 -07:00
|
|
|
}
|
|
|
|
|
|
|
|
void _onSend() async {
|
2021-09-26 11:44:19 -07:00
|
|
|
final s = S.of(context);
|
2021-06-26 07:30:12 -07:00
|
|
|
final form = _formKey.currentState;
|
|
|
|
if (form == null) return;
|
|
|
|
|
|
|
|
if (form.validate()) {
|
|
|
|
form.save();
|
2021-10-19 18:22:37 -07:00
|
|
|
final amount = amountInput?.amount ?? 0;
|
|
|
|
final aZEC = amountToString(amount);
|
2021-11-11 17:44:46 -08:00
|
|
|
final approved = widget.isMulti ||
|
|
|
|
await showDialog(
|
|
|
|
context: context,
|
|
|
|
barrierDismissible: false,
|
|
|
|
builder: (BuildContext context) => AlertDialog(
|
|
|
|
title: Text(s.pleaseConfirm),
|
|
|
|
content: SingleChildScrollView(
|
|
|
|
child: Text(s.sendingAzecCointickerToAddress(
|
|
|
|
aZEC, coin.ticker, _address))),
|
|
|
|
actions: confirmButtons(
|
|
|
|
context, () => Navigator.of(context).pop(true),
|
|
|
|
okLabel: s.approve, cancelValue: false)));
|
2021-06-26 07:30:12 -07:00
|
|
|
if (approved) {
|
2021-07-07 08:40:05 -07:00
|
|
|
int maxAmountPerNote = (_maxAmountPerNote * ZECUNIT_DECIMAL).toInt();
|
2021-07-09 22:44:34 -07:00
|
|
|
final memo = _memoController.text;
|
2021-08-27 02:51:34 -07:00
|
|
|
final address = unwrapUA(_address);
|
2021-11-11 17:44:46 -08:00
|
|
|
final recipient = Recipient(
|
|
|
|
address,
|
|
|
|
amount,
|
|
|
|
memo,
|
|
|
|
maxAmountPerNote,
|
|
|
|
);
|
|
|
|
|
|
|
|
if (!widget.isMulti)
|
|
|
|
// send closes the page
|
|
|
|
await send(context, [recipient], _useTransparent);
|
|
|
|
else
|
|
|
|
Navigator.of(context).pop(recipient);
|
2021-06-26 07:30:12 -07:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
2021-07-07 08:40:05 -07:00
|
|
|
|
2021-11-11 17:44:46 -08:00
|
|
|
|
2021-09-14 09:08:42 -07:00
|
|
|
int amountInZAT(Decimal v) => (v * ZECUNIT_DECIMAL).toInt();
|
2021-09-26 11:44:19 -07:00
|
|
|
|
|
|
|
String amountFromZAT(int v) =>
|
|
|
|
(Decimal.fromInt(v) / ZECUNIT_DECIMAL).toString();
|
2021-08-06 00:53:54 -07:00
|
|
|
|
2021-11-11 17:44:46 -08:00
|
|
|
get spendable => math.max(
|
|
|
|
(_useTransparent ? _tBalance : 0) +
|
|
|
|
_sBalance -
|
|
|
|
_excludedBalance -
|
|
|
|
_underConfirmedBalance -
|
|
|
|
_usedBalance -
|
|
|
|
_fee,
|
|
|
|
0);
|
2021-10-04 02:20:42 -07:00
|
|
|
|
|
|
|
get change => _unconfirmedSpentBalance + _unconfirmedBalance;
|
2021-10-09 07:17:27 -07:00
|
|
|
|
|
|
|
DualMoneyInputState? get amountInput => _amountKey.currentState;
|
2021-06-26 07:30:12 -07:00
|
|
|
}
|
|
|
|
|
2021-09-26 22:31:55 -07:00
|
|
|
class BalanceTable extends StatelessWidget {
|
2021-09-27 01:57:55 -07:00
|
|
|
final int sBalance;
|
|
|
|
final int tBalance;
|
2021-10-11 02:16:35 -07:00
|
|
|
final bool useTBalance;
|
2021-09-26 22:31:55 -07:00
|
|
|
final int excludedBalance;
|
|
|
|
final int underConfirmedBalance;
|
2021-10-04 02:20:42 -07:00
|
|
|
final int change;
|
2021-11-11 17:44:46 -08:00
|
|
|
final int used;
|
|
|
|
final int fee;
|
2021-09-26 22:31:55 -07:00
|
|
|
|
2021-11-11 17:44:46 -08:00
|
|
|
BalanceTable(this.sBalance, this.tBalance, this.useTBalance,
|
|
|
|
this.excludedBalance, this.underConfirmedBalance, this.change, this.used, this.fee);
|
2021-09-26 22:31:55 -07:00
|
|
|
|
|
|
|
@override
|
|
|
|
Widget build(BuildContext context) {
|
2021-09-27 01:57:55 -07:00
|
|
|
final theme = Theme.of(context);
|
|
|
|
final tBalanceLabel = Text.rich(TextSpan(children: [
|
|
|
|
TextSpan(text: S.of(context).unshieldedBalance + ' '),
|
|
|
|
WidgetSpan(
|
|
|
|
child: GestureDetector(
|
|
|
|
child: Icon(Icons.shield_outlined),
|
|
|
|
onTap: () {
|
|
|
|
shieldTAddr(context);
|
|
|
|
},
|
|
|
|
),
|
2021-09-26 22:31:55 -07:00
|
|
|
)
|
2021-09-27 01:57:55 -07:00
|
|
|
]));
|
|
|
|
|
|
|
|
return Container(
|
2021-11-11 17:44:46 -08:00
|
|
|
decoration: BoxDecoration(
|
|
|
|
border: Border.all(color: theme.dividerColor, width: 1),
|
|
|
|
borderRadius: BorderRadius.circular(8)),
|
2021-09-27 01:57:55 -07:00
|
|
|
child: Column(crossAxisAlignment: CrossAxisAlignment.center, children: [
|
2021-11-11 17:44:46 -08:00
|
|
|
BalanceRow(Text(S.of(context).totalBalance), totalBalance),
|
|
|
|
BalanceRow(Text(S.of(context).underConfirmed), -underConfirmed),
|
|
|
|
BalanceRow(Text(S.of(context).excludedNotes), -excludedBalance),
|
|
|
|
if (!useTBalance) BalanceRow(tBalanceLabel, -tBalance),
|
|
|
|
BalanceRow(Text(S.of(context).spendableBalance), spendable,
|
|
|
|
style: TextStyle(color: Theme.of(context).primaryColor)),
|
|
|
|
]));
|
2021-09-26 22:31:55 -07:00
|
|
|
}
|
2021-09-27 01:57:55 -07:00
|
|
|
|
2021-11-11 17:44:46 -08:00
|
|
|
get totalBalance => sBalance + tBalance + change - used - fee;
|
|
|
|
|
2021-10-04 02:20:42 -07:00
|
|
|
get underConfirmed => -underConfirmedBalance - change;
|
2021-09-27 01:57:55 -07:00
|
|
|
|
|
|
|
get spendable => math.max(
|
2021-11-11 17:44:46 -08:00
|
|
|
sBalance +
|
|
|
|
(useTBalance ? tBalance : 0) -
|
|
|
|
excludedBalance -
|
|
|
|
underConfirmedBalance -
|
|
|
|
used -
|
|
|
|
fee,
|
|
|
|
0);
|
2021-09-26 22:31:55 -07:00
|
|
|
}
|
|
|
|
|
|
|
|
class BalanceRow extends StatelessWidget {
|
|
|
|
final label;
|
|
|
|
final amount;
|
2021-09-27 01:57:55 -07:00
|
|
|
final style;
|
|
|
|
|
|
|
|
BalanceRow(this.label, this.amount, {this.style});
|
|
|
|
|
2021-09-26 22:31:55 -07:00
|
|
|
@override
|
|
|
|
Widget build(BuildContext context) {
|
2021-09-27 01:57:55 -07:00
|
|
|
return ListTile(
|
|
|
|
title: label,
|
|
|
|
trailing: Text(amountToString(amount),
|
2021-11-11 17:44:46 -08:00
|
|
|
style: TextStyle(fontFeatures: [FontFeature.tabularFigures()])
|
|
|
|
.merge(style)),
|
2021-09-26 22:31:55 -07:00
|
|
|
visualDensity: VisualDensity(horizontal: 0, vertical: -4));
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2021-11-11 17:44:46 -08:00
|
|
|
Future<void> send(BuildContext context, List<Recipient> recipients, bool useTransparent) async {
|
|
|
|
final s = S.of(context);
|
|
|
|
|
|
|
|
String address = "";
|
|
|
|
int amount = 0;
|
|
|
|
for (var r in recipients) {
|
|
|
|
amount += r.amount;
|
|
|
|
if (address.isEmpty)
|
|
|
|
address = r.address;
|
|
|
|
else
|
2021-11-17 20:57:52 -08:00
|
|
|
address = '*';
|
2021-11-11 17:44:46 -08:00
|
|
|
}
|
|
|
|
|
|
|
|
final snackBar1 = SnackBar(content: Text(s.preparingTransaction));
|
|
|
|
rootScaffoldMessengerKey.currentState?.showSnackBar(snackBar1);
|
|
|
|
|
2021-11-17 20:57:52 -08:00
|
|
|
if (settings.protectSend &&
|
|
|
|
!await authenticate(context, s.pleaseAuthenticateToSend)) return;
|
2021-11-11 17:44:46 -08:00
|
|
|
|
2021-11-17 20:57:52 -08:00
|
|
|
if (accountManager.canPay) {
|
2021-11-11 17:44:46 -08:00
|
|
|
Navigator.of(context).pop();
|
|
|
|
final tx = await WarpApi.sendPayment(accountManager.active.id, recipients,
|
|
|
|
useTransparent, settings.anchorOffset, (progress) {
|
|
|
|
progressPort.sendPort.send(progress);
|
|
|
|
});
|
|
|
|
progressPort.sendPort.send(0);
|
|
|
|
|
|
|
|
final snackBar2 = SnackBar(content: Text("${s.txId}: $tx"));
|
|
|
|
rootScaffoldMessengerKey.currentState?.showSnackBar(snackBar2);
|
|
|
|
await accountManager.fetchAccountData(true);
|
|
|
|
} else {
|
|
|
|
Directory tempDir = await getTemporaryDirectory();
|
|
|
|
String filename = "${tempDir.path}/tx.json";
|
|
|
|
|
|
|
|
final txjson = WarpApi.prepareTx(accountManager.active.id, recipients,
|
|
|
|
useTransparent, settings.anchorOffset, filename);
|
|
|
|
|
|
|
|
if (coin.supportsMultisig && accountManager.active.share != null) {
|
|
|
|
final txSummary = TxSummary(address, amount, txjson);
|
|
|
|
Navigator.of(context).pushReplacementNamed('/multisign', arguments: txSummary);
|
|
|
|
} else {
|
|
|
|
final file = File(filename);
|
|
|
|
await file.writeAsString(txjson);
|
|
|
|
Share.shareFiles([filename], subject: s.unsignedTransactionFile);
|
|
|
|
|
|
|
|
final snackBar2 = SnackBar(content: Text(s.fileSaved));
|
|
|
|
rootScaffoldMessengerKey.currentState?.showSnackBar(snackBar2);
|
|
|
|
Navigator.of(context).pop();
|
|
|
|
}
|
|
|
|
}
|
2021-06-26 07:30:12 -07:00
|
|
|
}
|