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/material.dart';
|
2022-04-15 23:51:13 -07:00
|
|
|
import 'package:flutter_form_builder/flutter_form_builder.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';
|
2022-12-29 04:06:07 -08:00
|
|
|
import 'package:warp_api/data_fb_generated.dart' hide Account;
|
2022-03-16 21:03:37 -07:00
|
|
|
import 'accounts.dart';
|
2023-03-02 01:12:02 -08:00
|
|
|
import 'contact.dart';
|
2022-03-07 06:53:18 -08:00
|
|
|
import 'dualmoneyinput.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-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>();
|
2022-08-29 20:42:08 -07:00
|
|
|
var _initialAmount = 0;
|
2021-06-26 07:30:12 -07:00
|
|
|
var _address = "";
|
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();
|
2022-04-15 23:51:13 -07:00
|
|
|
final _subjectController = TextEditingController();
|
2022-12-23 09:40:16 -08:00
|
|
|
var _fiat = settings.currency;
|
2022-03-07 18:47:41 -08:00
|
|
|
var _memoInitialized = false;
|
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;
|
2022-04-15 23:51:13 -07:00
|
|
|
var _replyTo = settings.includeReplyTo;
|
2022-12-29 04:06:07 -08:00
|
|
|
List<SendTemplateT> _templates = [];
|
|
|
|
SendTemplateT? _template;
|
2022-12-29 02:52:03 -08:00
|
|
|
var _accounts = AccountList();
|
2021-06-26 07:30:12 -07:00
|
|
|
|
2022-12-12 00:36:00 -08:00
|
|
|
void clear() {
|
|
|
|
final s = S.of(context);
|
|
|
|
setState(() {
|
|
|
|
_memoController.text = settings.memoSignature ?? s.sendFrom(APP_NAME);
|
|
|
|
_addressController.clear();
|
2022-12-23 09:40:16 -08:00
|
|
|
_fiat = settings.currency;
|
2022-12-12 00:36:00 -08:00
|
|
|
_replyTo = false;
|
|
|
|
_subjectController.clear();
|
|
|
|
_amountKey.currentState?.clear();
|
2022-12-25 03:18:11 -08:00
|
|
|
active.setDraftRecipient(null);
|
2022-12-12 00:36:00 -08:00
|
|
|
});
|
|
|
|
}
|
|
|
|
|
2021-06-26 07:30:12 -07:00
|
|
|
@override
|
|
|
|
initState() {
|
2022-08-29 20:42:08 -07:00
|
|
|
super.initState();
|
|
|
|
|
|
|
|
final draftRecipient = active.draftRecipient;
|
|
|
|
if (draftRecipient != null) {
|
2023-03-10 21:59:43 -08:00
|
|
|
_addressController.text = draftRecipient.address!;
|
2022-08-29 20:42:08 -07:00
|
|
|
_initialAmount = draftRecipient.amount;
|
2023-03-10 21:59:43 -08:00
|
|
|
_memoController.text = draftRecipient.memo ?? '';
|
|
|
|
_replyTo = draftRecipient.replyTo;
|
|
|
|
_subjectController.text = draftRecipient.subject ?? '';
|
2022-08-29 20:42:08 -07:00
|
|
|
_memoInitialized = true;
|
|
|
|
}
|
|
|
|
|
2021-10-03 08:47:44 -07:00
|
|
|
if (widget.args?.contact != null)
|
2022-12-29 04:06:07 -08:00
|
|
|
_addressController.text = widget.args!.contact!.address!;
|
2022-04-15 23:51:13 -07:00
|
|
|
if (widget.args?.address != null)
|
|
|
|
_addressController.text = widget.args!.address!;
|
|
|
|
if (widget.args?.subject != null)
|
|
|
|
_subjectController.text = widget.args!.subject!;
|
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
|
|
|
|
2023-01-04 02:40:36 -08:00
|
|
|
_newBlockAutorunDispose = autorun((_) {
|
|
|
|
syncStatus.latestHeight;
|
|
|
|
setState(() {});
|
|
|
|
});
|
|
|
|
Future.microtask(syncStatus.update);
|
|
|
|
|
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
|
|
|
|
2022-12-29 04:06:07 -08:00
|
|
|
final templateIds = active.dbReader.loadTemplates();
|
|
|
|
_templates = templateIds;
|
2021-06-26 07:30:12 -07:00
|
|
|
}
|
|
|
|
|
2022-12-25 03:18:11 -08:00
|
|
|
@override
|
|
|
|
void deactivate() {
|
|
|
|
final form = _formKey.currentState;
|
|
|
|
if (form != null) {
|
|
|
|
form.save();
|
|
|
|
final recipient = _getRecipient();
|
|
|
|
active.setDraftRecipient(recipient);
|
|
|
|
}
|
|
|
|
super.deactivate();
|
|
|
|
}
|
|
|
|
|
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;
|
2022-03-07 18:47:41 -08:00
|
|
|
if (!_memoInitialized) {
|
|
|
|
_memoController.text = settings.memoSignature ?? s.sendFrom(APP_NAME);
|
|
|
|
_memoInitialized = true;
|
|
|
|
}
|
2022-02-23 19:36:02 -08:00
|
|
|
|
2023-01-04 02:40:36 -08:00
|
|
|
active.updateBalances();
|
|
|
|
final balances = active.balances;
|
|
|
|
final sBalance = balances.shieldedBalance;
|
|
|
|
final tBalance = active.tbalance;
|
|
|
|
final excludedBalance = balances.excludedBalance;
|
|
|
|
final underConfirmedBalance = balances.underConfirmedBalance;
|
|
|
|
int? unconfirmedBalance = unconfirmedBalanceStream.value;
|
|
|
|
_sBalance = sBalance;
|
|
|
|
_tBalance = tBalance;
|
|
|
|
_excludedBalance = excludedBalance;
|
|
|
|
_underConfirmedBalance = underConfirmedBalance;
|
|
|
|
_unconfirmedBalance = unconfirmedBalance ?? 0;
|
2022-12-23 09:40:16 -08:00
|
|
|
|
2023-03-15 16:21:28 -07:00
|
|
|
var templates = _templates
|
|
|
|
.map((t) => DropdownMenuItem(child: Text(t.title!), value: t))
|
|
|
|
.toList();
|
|
|
|
final addReset = _template != null
|
|
|
|
? IconButton(onPressed: _resetTemplate, icon: Icon(Icons.close))
|
|
|
|
: IconButton(onPressed: _addTemplate, icon: Icon(Icons.add));
|
2022-12-23 09:40:16 -08:00
|
|
|
|
2021-06-26 07:30:12 -07:00
|
|
|
return Scaffold(
|
2023-03-15 16:21:28 -07:00
|
|
|
appBar: AppBar(title: Text(s.sendCointicker(active.coinDef.ticker))),
|
2021-09-26 11:44:19 -07:00
|
|
|
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(
|
2023-03-15 16:21:28 -07:00
|
|
|
labelText:
|
|
|
|
s.sendCointickerTo(active.coinDef.ticker)),
|
2021-09-27 01:57:55 -07:00
|
|
|
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,
|
2022-03-16 21:03:37 -07:00
|
|
|
onSuggestionSelected: (Suggestion suggestion) {
|
|
|
|
_addressController.text = suggestion.name;
|
2021-09-27 01:57:55 -07:00
|
|
|
},
|
|
|
|
suggestionsCallback: (String pattern) {
|
2023-03-15 16:21:28 -07:00
|
|
|
final matchingContacts = contacts.contacts
|
|
|
|
.where((c) => c.name!
|
|
|
|
.toLowerCase()
|
|
|
|
.contains(pattern.toLowerCase()))
|
|
|
|
.map((c) => ContactSuggestion(c));
|
2022-12-29 02:52:03 -08:00
|
|
|
final matchingAccounts = _accounts.list
|
2023-03-15 16:21:28 -07:00
|
|
|
.where((a) =>
|
|
|
|
a.coin == active.coin &&
|
|
|
|
a.name
|
|
|
|
.toLowerCase()
|
|
|
|
.contains(pattern.toLowerCase()))
|
|
|
|
.map((a) => AccountSuggestion(a));
|
2022-03-16 21:03:37 -07:00
|
|
|
return [...matchingContacts, ...matchingAccounts];
|
2021-09-27 01:57:55 -07:00
|
|
|
},
|
2023-03-15 16:21:28 -07:00
|
|
|
itemBuilder:
|
|
|
|
(BuildContext context, Suggestion suggestion) =>
|
|
|
|
ListTile(title: Text(suggestion.name)),
|
2021-09-27 01:57:55 -07:00
|
|
|
noItemsFoundBuilder: (_) => SizedBox(),
|
|
|
|
)),
|
2021-09-26 11:44:19 -07:00
|
|
|
IconButton(
|
|
|
|
icon: new Icon(MdiIcons.qrcodeScan),
|
2023-03-02 01:12:02 -08:00
|
|
|
onPressed: _onScan),
|
|
|
|
IconButton(
|
|
|
|
icon: new Icon(MdiIcons.contacts),
|
|
|
|
onPressed: _onAddContact),
|
2021-09-26 11:44:19 -07:00
|
|
|
]),
|
2021-11-11 17:44:46 -08:00
|
|
|
DualMoneyInputWidget(
|
|
|
|
key: _amountKey,
|
2022-12-07 08:21:01 -08:00
|
|
|
max: true,
|
2022-08-29 20:42:08 -07:00
|
|
|
initialValue: _initialAmount,
|
2021-11-11 17:44:46 -08:00
|
|
|
spendable: spendable),
|
2023-03-15 16:21:28 -07:00
|
|
|
if (!simpleMode)
|
|
|
|
BalanceTable(_sBalance, _tBalance, _excludedBalance,
|
|
|
|
_underConfirmedBalance, change, _usedBalance, _fee),
|
|
|
|
Container(
|
|
|
|
child: InputDecorator(
|
|
|
|
decoration: InputDecoration(labelText: s.memo),
|
|
|
|
child: Column(children: [
|
|
|
|
FormBuilderCheckbox(
|
|
|
|
key: UniqueKey(),
|
|
|
|
name: 'reply-to',
|
|
|
|
title: Text(s.includeReplyTo),
|
|
|
|
initialValue: _replyTo,
|
|
|
|
onChanged: (v) {
|
|
|
|
setState(() {
|
|
|
|
_replyTo = v ?? false;
|
|
|
|
});
|
|
|
|
},
|
|
|
|
),
|
|
|
|
TextFormField(
|
|
|
|
decoration:
|
|
|
|
InputDecoration(labelText: s.subject),
|
|
|
|
controller: _subjectController,
|
|
|
|
),
|
|
|
|
TextFormField(
|
|
|
|
decoration:
|
|
|
|
InputDecoration(labelText: s.body),
|
|
|
|
minLines: 4,
|
|
|
|
maxLines: null,
|
|
|
|
keyboardType: TextInputType.multiline,
|
|
|
|
controller: _memoController,
|
|
|
|
)
|
|
|
|
]))),
|
2021-09-26 11:44:19 -07:00
|
|
|
Padding(padding: EdgeInsets.all(8)),
|
2022-12-23 09:40:16 -08:00
|
|
|
Row(children: [
|
2023-03-15 16:21:28 -07:00
|
|
|
Expanded(
|
|
|
|
child: DropdownButtonFormField<SendTemplateT>(
|
|
|
|
hint: Text(s.template),
|
|
|
|
items: templates,
|
|
|
|
value: _template,
|
|
|
|
onChanged: (v) {
|
|
|
|
setState(() {
|
|
|
|
_template = v;
|
|
|
|
});
|
|
|
|
})),
|
2022-12-23 09:40:16 -08:00
|
|
|
addReset,
|
2023-03-15 16:21:28 -07:00
|
|
|
IconButton(
|
|
|
|
onPressed: _template != null ? _openTemplate : null,
|
|
|
|
icon: Icon(Icons.open_in_new)),
|
|
|
|
IconButton(
|
|
|
|
onPressed: _template != null
|
|
|
|
? () {
|
|
|
|
_saveTemplate(
|
|
|
|
_template!.id, _template!.title!, true);
|
|
|
|
}
|
|
|
|
: null,
|
|
|
|
icon: Icon(Icons.save)),
|
|
|
|
IconButton(
|
|
|
|
onPressed:
|
|
|
|
_template != null ? _deleteTemplate : null,
|
|
|
|
icon: Icon(Icons.delete)),
|
2022-12-23 09:40:16 -08:00
|
|
|
]),
|
|
|
|
Padding(padding: EdgeInsets.all(8)),
|
|
|
|
ButtonBar(children: [
|
2023-03-15 16:21:28 -07:00
|
|
|
ElevatedButton.icon(
|
|
|
|
onPressed: clear,
|
|
|
|
icon: Icon(Icons.clear),
|
|
|
|
label: Text(s.reset)),
|
|
|
|
ElevatedButton.icon(
|
|
|
|
onPressed: _onSend,
|
|
|
|
icon: Icon(MdiIcons.send),
|
|
|
|
label: Text(widget.isMulti ? s.add : s.send))
|
|
|
|
])
|
|
|
|
])))));
|
2021-06-26 07:30:12 -07:00
|
|
|
}
|
|
|
|
|
2022-12-23 09:40:16 -08:00
|
|
|
void _resetTemplate() {
|
|
|
|
setState(() {
|
|
|
|
_template = null;
|
|
|
|
});
|
|
|
|
}
|
|
|
|
|
|
|
|
Future<void> _addTemplate() async {
|
|
|
|
final s = S.of(context);
|
|
|
|
final form = _formKey.currentState!;
|
|
|
|
if (form.validate()) {
|
|
|
|
form.save();
|
|
|
|
final titleController = TextEditingController();
|
|
|
|
final confirmed = await showDialog<bool>(
|
2023-03-15 16:21:28 -07:00
|
|
|
context: context,
|
|
|
|
builder: (context) => AlertDialog(
|
|
|
|
title: Text(s.newTemplate),
|
|
|
|
content: Column(mainAxisSize: MainAxisSize.min, children: [
|
2022-12-23 09:40:16 -08:00
|
|
|
TextField(
|
|
|
|
decoration: InputDecoration(label: Text(s.name)),
|
|
|
|
controller: titleController)
|
|
|
|
]),
|
2023-03-15 16:21:28 -07:00
|
|
|
actions: confirmButtons(context, () {
|
|
|
|
Navigator.of(context).pop(true);
|
|
|
|
}))) ??
|
|
|
|
false;
|
2022-12-23 09:40:16 -08:00
|
|
|
if (!confirmed) return;
|
|
|
|
final title = titleController.text;
|
2023-01-02 14:39:17 -08:00
|
|
|
_saveTemplate(0, title, false);
|
2022-12-23 09:40:16 -08:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2023-01-02 14:39:17 -08:00
|
|
|
void _saveTemplate(int id, String title, bool validate) {
|
2022-12-23 09:40:16 -08:00
|
|
|
final form = _formKey.currentState!;
|
|
|
|
if (validate && !form.validate()) return null;
|
|
|
|
form.save();
|
|
|
|
final dualAmountController = amountInput;
|
|
|
|
if (dualAmountController == null) return null;
|
2022-12-29 04:06:07 -08:00
|
|
|
var template = SendTemplateT(
|
|
|
|
id: id,
|
|
|
|
title: title,
|
|
|
|
address: _address,
|
|
|
|
amount: stringToAmount(dualAmountController.coinAmountController.text),
|
|
|
|
fiatAmount: parseNumber(dualAmountController.fiatAmountController.text),
|
|
|
|
feeIncluded: dualAmountController.feeIncluded,
|
|
|
|
fiat: dualAmountController.inputInCoin ? null : _fiat,
|
|
|
|
includeReplyTo: _replyTo,
|
|
|
|
subject: _subjectController.text,
|
2023-01-02 14:39:17 -08:00
|
|
|
body: _memoController.text);
|
2022-12-29 04:06:07 -08:00
|
|
|
final id2 = WarpApi.saveSendTemplate(active.coin, template);
|
2023-01-02 14:39:17 -08:00
|
|
|
_loadTemplates(id2);
|
2022-12-23 09:40:16 -08:00
|
|
|
}
|
|
|
|
|
|
|
|
Future<void> _deleteTemplate() async {
|
|
|
|
final s = S.of(context);
|
2023-03-15 16:21:28 -07:00
|
|
|
final confirmed = await showConfirmDialog(
|
|
|
|
context, s.deleteTemplate, s.areYouSureYouWantToDeleteThisSendTemplate);
|
2022-12-23 09:40:16 -08:00
|
|
|
if (!confirmed) return;
|
|
|
|
WarpApi.deleteSendTemplate(active.coin, _template!.id);
|
|
|
|
_resetTemplate();
|
2023-01-02 14:39:17 -08:00
|
|
|
_loadTemplates(0);
|
2022-12-23 09:40:16 -08:00
|
|
|
}
|
|
|
|
|
2023-01-02 14:39:17 -08:00
|
|
|
void _openTemplate() {
|
2022-12-23 09:40:16 -08:00
|
|
|
final tid = _template;
|
|
|
|
if (tid == null) return;
|
2023-01-27 08:35:36 -08:00
|
|
|
final template = _template;
|
2022-12-23 09:40:16 -08:00
|
|
|
if (template == null) return;
|
2023-03-15 16:21:28 -07:00
|
|
|
amountInput?.restore(template.amount, template.fiatAmount,
|
|
|
|
template.feeIncluded, template.fiat);
|
2022-12-23 09:40:16 -08:00
|
|
|
setState(() {
|
2022-12-29 04:06:07 -08:00
|
|
|
_addressController.text = template.address!;
|
|
|
|
_replyTo = template.includeReplyTo;
|
|
|
|
_subjectController.text = template.subject!;
|
|
|
|
_memoController.text = template.body!;
|
2022-12-23 09:40:16 -08:00
|
|
|
});
|
|
|
|
}
|
|
|
|
|
2023-01-02 14:39:17 -08:00
|
|
|
void _loadTemplates(int id) {
|
|
|
|
final templates = active.dbReader.loadTemplates();
|
|
|
|
_templates = templates;
|
2023-03-15 16:21:28 -07:00
|
|
|
if (id != 0) _template = _templates.firstWhere((t) => t.id == id);
|
2023-01-02 14:39:17 -08:00
|
|
|
setState(() {});
|
2022-12-23 09:40:16 -08:00
|
|
|
}
|
|
|
|
|
2022-03-16 21:03:37 -07:00
|
|
|
Suggestion? getSuggestion(String v) {
|
|
|
|
final c = contacts.contacts.where((c) => c.name == v);
|
|
|
|
if (c.isNotEmpty) return ContactSuggestion(c.first);
|
2022-12-29 02:52:03 -08:00
|
|
|
final a = _accounts.list.where((a) => a.name == v);
|
2022-03-16 21:03:37 -07:00
|
|
|
if (a.isNotEmpty) return AccountSuggestion(a.first);
|
2022-12-05 09:45:35 -08:00
|
|
|
return null;
|
2022-03-16 21:03:37 -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;
|
2022-03-16 21:03:37 -07:00
|
|
|
final suggestion = getSuggestion(v);
|
|
|
|
if (suggestion != null) return null;
|
2022-03-07 06:53:18 -08:00
|
|
|
if (!WarpApi.validAddress(active.coin, v)) return s.invalidAddress;
|
2021-06-26 07:30:12 -07:00
|
|
|
return null;
|
|
|
|
}
|
|
|
|
|
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) {
|
2022-08-31 08:36:41 -07:00
|
|
|
_setPaymentURI(code);
|
2021-09-25 02:09:41 -07:00
|
|
|
}
|
2021-06-26 07:30:12 -07:00
|
|
|
}
|
2021-10-03 08:47:44 -07:00
|
|
|
|
2023-03-02 01:12:02 -08:00
|
|
|
Future<void> _onAddContact() async {
|
|
|
|
await addContact(context, ContactT(address: _addressController.text));
|
|
|
|
}
|
|
|
|
|
2022-08-31 08:36:41 -07:00
|
|
|
void _setPaymentURI(String uriOrAddress) {
|
2021-10-03 08:47:44 -07:00
|
|
|
try {
|
2022-08-31 08:36:41 -07:00
|
|
|
final paymentURI = decodeAddress(context, uriOrAddress);
|
2021-10-03 08:47:44 -07:00
|
|
|
setState(() {
|
2022-08-31 08:36:41 -07:00
|
|
|
_address = paymentURI.address;
|
2021-10-03 08:47:44 -07:00
|
|
|
_addressController.text = _address;
|
2023-03-15 16:21:28 -07:00
|
|
|
if (paymentURI.memo.isNotEmpty) _memoController.text = paymentURI.memo;
|
|
|
|
if (paymentURI.amount != 0) amountInput?.setAmount(paymentURI.amount);
|
2021-10-03 08:47:44 -07:00
|
|
|
});
|
2023-03-15 16:21:28 -07:00
|
|
|
} on String catch (e) {
|
2022-10-07 07:17:18 -07:00
|
|
|
showSnackBar(S.of(context).invalidQrCode(e));
|
2022-08-31 08:36:41 -07:00
|
|
|
}
|
2021-10-03 08:47:44 -07:00
|
|
|
}
|
2021-06-26 07:30:12 -07:00
|
|
|
|
2021-07-07 08:40:05 -07:00
|
|
|
void _onAddress(v) {
|
2022-03-16 21:03:37 -07:00
|
|
|
final suggestion = getSuggestion(v);
|
|
|
|
if (suggestion == null)
|
2021-09-12 04:24:40 -07:00
|
|
|
_address = v;
|
|
|
|
else {
|
2022-03-16 21:03:37 -07:00
|
|
|
_address = suggestion.address;
|
2021-09-12 04:24:40 -07:00
|
|
|
}
|
2021-06-26 07:30:12 -07:00
|
|
|
}
|
|
|
|
|
2022-12-25 03:18:11 -08:00
|
|
|
Recipient _getRecipient() {
|
|
|
|
final amount = amountInput?.amount ?? 0;
|
|
|
|
final feeIncluded = amountInput?.feeIncluded ?? false;
|
|
|
|
final memo = _memoController.text;
|
|
|
|
final subject = _subjectController.text;
|
2023-03-10 21:59:43 -08:00
|
|
|
final recipient = RecipientObjectBuilder(
|
|
|
|
address: _address,
|
|
|
|
amount: amount,
|
|
|
|
feeIncluded: feeIncluded,
|
|
|
|
replyTo: _replyTo,
|
|
|
|
subject: subject,
|
|
|
|
memo: memo,
|
|
|
|
maxAmountPerNote: 0,
|
2022-12-25 03:18:11 -08:00
|
|
|
);
|
2023-03-10 21:59:43 -08:00
|
|
|
return Recipient(recipient.toBytes());
|
2022-12-25 03:18:11 -08:00
|
|
|
}
|
|
|
|
|
2021-07-07 08:40:05 -07:00
|
|
|
void _onSend() async {
|
2021-06-26 07:30:12 -07:00
|
|
|
final form = _formKey.currentState;
|
|
|
|
if (form == null) return;
|
|
|
|
|
|
|
|
if (form.validate()) {
|
|
|
|
form.save();
|
2022-12-25 03:18:11 -08:00
|
|
|
final recipient = _getRecipient();
|
2022-11-12 19:43:09 -08:00
|
|
|
|
|
|
|
if (!widget.isMulti)
|
|
|
|
// send closes the page
|
|
|
|
await send(context, [recipient]);
|
|
|
|
else
|
|
|
|
Navigator.of(context).pop(recipient);
|
2021-06-26 07:30:12 -07:00
|
|
|
}
|
|
|
|
}
|
2021-07-07 08:40:05 -07:00
|
|
|
|
2022-04-08 04:34:24 -07:00
|
|
|
int amountInZAT(Decimal v) => (v * ZECUNIT_DECIMAL).toBigInt().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(
|
2022-11-12 19:43:09 -08:00
|
|
|
_tBalance +
|
2023-03-15 16:21:28 -07:00
|
|
|
_sBalance -
|
|
|
|
_excludedBalance -
|
|
|
|
_underConfirmedBalance -
|
|
|
|
_usedBalance,
|
2021-11-11 17:44:46 -08:00
|
|
|
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-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
|
|
|
|
2023-03-15 16:21:28 -07:00
|
|
|
BalanceTable(this.sBalance, this.tBalance, 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);
|
2022-12-07 08:21:01 -08:00
|
|
|
final s = S.of(context);
|
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: [
|
2022-12-07 08:21:01 -08:00
|
|
|
BalanceRow(Text(s.totalBalance), totalBalance),
|
|
|
|
BalanceRow(Text(s.underConfirmed), -underConfirmed),
|
|
|
|
BalanceRow(Text(s.excludedNotes), -excludedBalance),
|
|
|
|
BalanceRow(Text(s.spendableBalance), spendable,
|
|
|
|
style: TextStyle(color: theme.primaryColor)),
|
2021-11-11 17:44:46 -08:00
|
|
|
]));
|
2021-09-26 22:31:55 -07:00
|
|
|
}
|
2021-09-27 01:57:55 -07:00
|
|
|
|
2022-12-07 08:21:01 -08:00
|
|
|
get totalBalance => sBalance + tBalance + change - used;
|
2021-11-11 17:44:46 -08:00
|
|
|
|
2021-10-04 02:20:42 -07:00
|
|
|
get underConfirmed => -underConfirmedBalance - change;
|
2021-09-27 01:57:55 -07:00
|
|
|
|
|
|
|
get spendable => math.max(
|
2023-03-15 16:21:28 -07:00
|
|
|
sBalance + tBalance - excludedBalance - underConfirmedBalance - used, 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,
|
2022-08-29 20:42:08 -07:00
|
|
|
trailing: Text(amountToString(amount, MAX_PRECISION),
|
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));
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2022-11-12 19:43:09 -08:00
|
|
|
Future<void> send(BuildContext context, List<Recipient> recipients) async {
|
2021-11-11 17:44:46 -08:00
|
|
|
final s = S.of(context);
|
|
|
|
|
2023-03-15 16:21:28 -07:00
|
|
|
await showSnackBar(s.preparingTransaction, autoClose: true);
|
2021-11-11 17:44:46 -08:00
|
|
|
|
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
|
|
|
|
2023-03-15 16:21:28 -07:00
|
|
|
if (recipients.length == 1) active.setDraftRecipient(recipients[0]);
|
2022-11-16 06:16:44 -08:00
|
|
|
try {
|
2023-03-15 16:21:28 -07:00
|
|
|
final txPlan = await WarpApi.prepareTx(
|
|
|
|
active.coin, active.id, recipients, settings.anchorOffset);
|
2022-11-16 06:16:44 -08:00
|
|
|
Navigator.pushReplacementNamed(context, '/txplan', arguments: txPlan);
|
2023-03-15 16:21:28 -07:00
|
|
|
} on String catch (message) {
|
2022-11-16 06:16:44 -08:00
|
|
|
showSnackBar(message);
|
|
|
|
}
|
2021-06-26 07:30:12 -07:00
|
|
|
}
|
2022-03-09 05:19:42 -08:00
|
|
|
|
2022-03-16 21:03:37 -07:00
|
|
|
abstract class Suggestion {
|
|
|
|
String get name;
|
|
|
|
String get address;
|
|
|
|
}
|
|
|
|
|
|
|
|
class ContactSuggestion extends Suggestion {
|
2022-12-29 04:06:07 -08:00
|
|
|
final ContactT contact;
|
2022-03-16 21:03:37 -07:00
|
|
|
|
|
|
|
ContactSuggestion(this.contact);
|
|
|
|
|
2022-12-29 04:06:07 -08:00
|
|
|
String get name => contact.name!;
|
|
|
|
String get address => contact.address!;
|
2022-03-16 21:03:37 -07:00
|
|
|
}
|
|
|
|
|
|
|
|
class AccountSuggestion extends Suggestion {
|
|
|
|
final Account account;
|
|
|
|
|
|
|
|
AccountSuggestion(this.account);
|
|
|
|
|
|
|
|
String get name => account.name;
|
|
|
|
String get address => account.address;
|
|
|
|
}
|