Snackbar
This commit is contained in:
parent
35734222ee
commit
42f5f2c377
|
@ -321,7 +321,7 @@ class QRAddressState extends State<QRAddressWidget> {
|
|||
await active.refreshTAddr();
|
||||
active.updateTBalance();
|
||||
} on String catch (message) {
|
||||
showSnackBar(message);
|
||||
showSnackBar(message, error: true);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -226,7 +226,7 @@ class AccountManagerState extends State<AccountManagerPage> {
|
|||
_onNewSubaccount() async {
|
||||
final s = S.of(context);
|
||||
if (active.id == 0) {
|
||||
showSnackBar(s.noActiveAccount);
|
||||
showSnackBar(s.noActiveAccount, error: true);
|
||||
return;
|
||||
}
|
||||
final newName = s.subAccountOf(active.account.name);
|
||||
|
|
121
lib/contact.dart
121
lib/contact.dart
|
@ -11,7 +11,7 @@ import 'generated/l10n.dart';
|
|||
import 'store.dart';
|
||||
|
||||
class ContactsTab extends StatefulWidget {
|
||||
ContactsTab({key = Key}): super(key: key);
|
||||
ContactsTab({key = Key}) : super(key: key);
|
||||
@override
|
||||
State<StatefulWidget> createState() => ContactsState();
|
||||
}
|
||||
|
@ -21,41 +21,53 @@ class ContactsState extends State<ContactsTab> {
|
|||
Widget build(BuildContext context) {
|
||||
final s = S.of(context);
|
||||
final theme = Theme.of(context);
|
||||
return Observer(builder: (context) =>
|
||||
Padding(padding: EdgeInsets.all(8), child: contacts.contacts.isEmpty
|
||||
? NoContact()
|
||||
: Column(children: [
|
||||
if (!settings.coins[active.coin].contactsSaved) OutlinedButton(onPressed: _onCommit, child: Text(s.saveToBlockchain), style: OutlinedButton.styleFrom(
|
||||
side: BorderSide(
|
||||
width: 1, color: theme.primaryColor))),
|
||||
Expanded(child: ListView.builder(
|
||||
itemCount: contacts.contacts.length,
|
||||
itemBuilder: (BuildContext context, int index) {
|
||||
final c = contacts.contacts[index];
|
||||
return Card(
|
||||
child: Dismissible(
|
||||
key: Key("${c.id}"),
|
||||
child: ListTile(
|
||||
title: Text(c.name!,
|
||||
style: theme.textTheme.headlineSmall),
|
||||
subtitle: Text(c.address!),
|
||||
trailing: Icon(Icons.chevron_right),
|
||||
onTap: () { _onContact(c); },
|
||||
onLongPress: () { _editContact(c); },
|
||||
),
|
||||
confirmDismiss: (_) async {
|
||||
return await _onConfirmDelContact(c);
|
||||
},
|
||||
onDismissed: (_) {
|
||||
_delContact(c);
|
||||
}));
|
||||
})
|
||||
)]))
|
||||
);
|
||||
return Observer(
|
||||
builder: (context) => Padding(
|
||||
padding: EdgeInsets.all(8),
|
||||
child: contacts.contacts.isEmpty
|
||||
? NoContact()
|
||||
: Column(children: [
|
||||
if (!settings.coins[active.coin].contactsSaved)
|
||||
OutlinedButton(
|
||||
onPressed: _onCommit,
|
||||
child: Text(s.saveToBlockchain),
|
||||
style: OutlinedButton.styleFrom(
|
||||
side: BorderSide(
|
||||
width: 1, color: theme.primaryColor))),
|
||||
Expanded(
|
||||
child: ListView.builder(
|
||||
itemCount: contacts.contacts.length,
|
||||
itemBuilder: (BuildContext context, int index) {
|
||||
final c = contacts.contacts[index];
|
||||
return Card(
|
||||
child: Dismissible(
|
||||
key: Key("${c.id}"),
|
||||
child: ListTile(
|
||||
title: Text(c.name!,
|
||||
style:
|
||||
theme.textTheme.headlineSmall),
|
||||
subtitle: Text(c.address!),
|
||||
trailing: Icon(Icons.chevron_right),
|
||||
onTap: () {
|
||||
_onContact(c);
|
||||
},
|
||||
onLongPress: () {
|
||||
_editContact(c);
|
||||
},
|
||||
),
|
||||
confirmDismiss: (_) async {
|
||||
return await _onConfirmDelContact(c);
|
||||
},
|
||||
onDismissed: (_) {
|
||||
_delContact(c);
|
||||
}));
|
||||
}))
|
||||
])));
|
||||
}
|
||||
|
||||
_onContact(ContactT c) {
|
||||
Navigator.of(context).pushNamed('/send', arguments: SendPageArgs(contact: c));
|
||||
Navigator.of(context)
|
||||
.pushNamed('/send', arguments: SendPageArgs(contact: c));
|
||||
}
|
||||
|
||||
_editContact(ContactT c) async {
|
||||
|
@ -63,7 +75,9 @@ class ContactsState extends State<ContactsTab> {
|
|||
}
|
||||
|
||||
Future<bool> _onConfirmDelContact(ContactT c) async {
|
||||
final confirm = await showMessageBox(context, S.of(context).deleteContact,
|
||||
final confirm = await showMessageBox(
|
||||
context,
|
||||
S.of(context).deleteContact,
|
||||
S.of(context).areYouSureYouWantToDeleteThisContact,
|
||||
S.of(context).delete);
|
||||
return confirm;
|
||||
|
@ -77,9 +91,8 @@ class ContactsState extends State<ContactsTab> {
|
|||
try {
|
||||
final txPlan = WarpApi.commitUnsavedContacts(settings.anchorOffset);
|
||||
Navigator.of(context).pushNamed('/txplan', arguments: txPlan);
|
||||
}
|
||||
on String catch (msg) {
|
||||
showSnackBar(msg);
|
||||
} on String catch (msg) {
|
||||
showSnackBar(msg, error: true);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -93,7 +106,8 @@ class NoContact extends StatelessWidget {
|
|||
return Column(mainAxisAlignment: MainAxisAlignment.center, children: [
|
||||
SizedBox(child: contact, height: 150, width: 150),
|
||||
Padding(padding: EdgeInsets.symmetric(vertical: 16)),
|
||||
Text(S.of(context).noContacts, style: Theme.of(context).textTheme.headlineSmall),
|
||||
Text(S.of(context).noContacts,
|
||||
style: Theme.of(context).textTheme.headlineSmall),
|
||||
Padding(padding: EdgeInsets.symmetric(vertical: 8)),
|
||||
Text(S.of(context).createANewContactAndItWillShowUpHere,
|
||||
style: Theme.of(context).textTheme.bodyLarge),
|
||||
|
@ -133,9 +147,10 @@ class ContactState extends State<ContactForm> {
|
|||
controller: nameController,
|
||||
validator: _checkName,
|
||||
),
|
||||
AddressInput(widget.contact.id, S.of(context).address, address, (addr) {
|
||||
address = addr ?? "";
|
||||
})
|
||||
AddressInput(widget.contact.id, S.of(context).address, address,
|
||||
(addr) {
|
||||
address = addr ?? "";
|
||||
})
|
||||
])));
|
||||
}
|
||||
|
||||
|
@ -143,7 +158,8 @@ class ContactState extends State<ContactForm> {
|
|||
final state = formKey.currentState!;
|
||||
if (state.validate()) {
|
||||
state.save();
|
||||
final contact = ContactT(id: widget.contact.id, name: nameController.text, address: address);
|
||||
final contact = ContactT(
|
||||
id: widget.contact.id, name: nameController.text, address: address);
|
||||
Navigator.of(context).pop(contact);
|
||||
active.update();
|
||||
}
|
||||
|
@ -196,8 +212,11 @@ class AddressState extends State<AddressInput> {
|
|||
|
||||
String? _checkAddress(String? v) {
|
||||
if (v == null || v.isEmpty) return S.of(context).addressIsEmpty;
|
||||
if (!WarpApi.validAddress(active.coin, v)) return S.of(context).invalidAddress;
|
||||
if (contacts.contacts.where((c) => c.address == v && c.id != widget.id).isNotEmpty) return S.of(context).duplicateContact;
|
||||
if (!WarpApi.validAddress(active.coin, v))
|
||||
return S.of(context).invalidAddress;
|
||||
if (contacts.contacts
|
||||
.where((c) => c.address == v && c.id != widget.id)
|
||||
.isNotEmpty) return S.of(context).duplicateContact;
|
||||
return null;
|
||||
}
|
||||
|
||||
|
@ -217,13 +236,13 @@ Future<void> addContact(BuildContext context, ContactT? c) async {
|
|||
final contact = await showDialog<ContactT>(
|
||||
context: context,
|
||||
builder: (context) => AlertDialog(
|
||||
contentPadding: EdgeInsets.all(16),
|
||||
title: Text(c?.name != null ? s.editContact : s.addContact),
|
||||
content: ContactForm(c ?? ContactT(), key: key),
|
||||
actions: confirmButtons(context, () {
|
||||
key.currentState!.onOK();
|
||||
}),
|
||||
));
|
||||
contentPadding: EdgeInsets.all(16),
|
||||
title: Text(c?.name != null ? s.editContact : s.addContact),
|
||||
content: ContactForm(c ?? ContactT(), key: key),
|
||||
actions: confirmButtons(context, () {
|
||||
key.currentState!.onOK();
|
||||
}),
|
||||
));
|
||||
if (contact != null) {
|
||||
contacts.add(contact);
|
||||
}
|
||||
|
|
|
@ -1,4 +1,3 @@
|
|||
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:shared_preferences/shared_preferences.dart';
|
||||
import 'package:warp_api/warp_api.dart';
|
||||
|
@ -17,20 +16,37 @@ class DevPageState extends State<DevPage> {
|
|||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Scaffold(
|
||||
appBar: AppBar(title: Text('Developer Menu')),
|
||||
body: ListView(
|
||||
padding: EdgeInsets.all(8),
|
||||
children: [
|
||||
ListTile(title: Text('Sync Stats'), trailing: Icon(Icons.chevron_right), onTap: _syncStats),
|
||||
ListTile(title: Text('Reset Scan State'), trailing: Icon(Icons.chevron_right), onTap: _resetScan),
|
||||
ListTile(title: Text('Reset App'), trailing: Icon(Icons.chevron_right), onTap: resetApp),
|
||||
ListTile(title: Text('Trigger Reorg'), trailing: Icon(Icons.chevron_right), onTap: _reorg),
|
||||
ListTile(title: Text('Mark as Synced'), trailing: Icon(Icons.chevron_right), onTap: _markAsSynced),
|
||||
ListTile(title: Text('Clear Tx Details'), trailing: Icon(Icons.chevron_right), onTap: _clearTxDetails),
|
||||
ListTile(title: Text('Revoke Dev mode'), trailing: Icon(Icons.chevron_right), onTap: _resetDevMode),
|
||||
]
|
||||
)
|
||||
);
|
||||
appBar: AppBar(title: Text('Developer Menu')),
|
||||
body: ListView(padding: EdgeInsets.all(8), children: [
|
||||
ListTile(
|
||||
title: Text('Sync Stats'),
|
||||
trailing: Icon(Icons.chevron_right),
|
||||
onTap: _syncStats),
|
||||
ListTile(
|
||||
title: Text('Reset Scan State'),
|
||||
trailing: Icon(Icons.chevron_right),
|
||||
onTap: _resetScan),
|
||||
ListTile(
|
||||
title: Text('Reset App'),
|
||||
trailing: Icon(Icons.chevron_right),
|
||||
onTap: resetApp),
|
||||
ListTile(
|
||||
title: Text('Trigger Reorg'),
|
||||
trailing: Icon(Icons.chevron_right),
|
||||
onTap: _reorg),
|
||||
ListTile(
|
||||
title: Text('Mark as Synced'),
|
||||
trailing: Icon(Icons.chevron_right),
|
||||
onTap: _markAsSynced),
|
||||
ListTile(
|
||||
title: Text('Clear Tx Details'),
|
||||
trailing: Icon(Icons.chevron_right),
|
||||
onTap: _clearTxDetails),
|
||||
ListTile(
|
||||
title: Text('Revoke Dev mode'),
|
||||
trailing: Icon(Icons.chevron_right),
|
||||
onTap: _resetDevMode),
|
||||
]));
|
||||
}
|
||||
|
||||
void _syncStats() {
|
||||
|
@ -55,7 +71,7 @@ class DevPageState extends State<DevPage> {
|
|||
}
|
||||
|
||||
_resetDevMode() {
|
||||
showSnackBar('App reset');
|
||||
showSnackBar('Dev mode disabled');
|
||||
settings.resetDevMode();
|
||||
}
|
||||
}
|
||||
|
|
|
@ -495,7 +495,7 @@ class HomeInnerState extends State<HomeInnerPage>
|
|||
final res = WarpApi.broadcast(rawTx);
|
||||
showSnackBar(res);
|
||||
} on String catch (e) {
|
||||
showSnackBar(e);
|
||||
showSnackBar(e, error: true);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -516,51 +516,54 @@ class HomeInnerState extends State<HomeInnerPage>
|
|||
final oldPasswdController = TextEditingController();
|
||||
final newPasswdController = TextEditingController();
|
||||
final repeatPasswdController = TextEditingController();
|
||||
final formKey = GlobalKey<FormBuilderState>();
|
||||
final confirmed = await showDialog<bool>(
|
||||
context: context,
|
||||
barrierDismissible: false,
|
||||
builder: (context) => AlertDialog(
|
||||
title: Text(s.encryptDatabase),
|
||||
contentPadding: EdgeInsets.all(16),
|
||||
content: Form(
|
||||
child: SingleChildScrollView(
|
||||
child: Column(children: [
|
||||
if (hasPasswd)
|
||||
TextFormField(
|
||||
decoration:
|
||||
InputDecoration(labelText: s.currentPassword),
|
||||
obscureText: true,
|
||||
controller: oldPasswdController),
|
||||
TextFormField(
|
||||
decoration: InputDecoration(labelText: s.newPassword),
|
||||
obscureText: true,
|
||||
controller: newPasswdController),
|
||||
TextFormField(
|
||||
decoration:
|
||||
InputDecoration(labelText: s.repeatNewPassword),
|
||||
obscureText: true,
|
||||
controller: repeatPasswdController),
|
||||
]))),
|
||||
actions: confirmButtons(
|
||||
context, () => Navigator.of(context).pop(true),
|
||||
cancelValue: false))) ??
|
||||
content: FormBuilder(
|
||||
key: formKey,
|
||||
child: Column(children: [
|
||||
if (hasPasswd)
|
||||
TextFormField(
|
||||
decoration:
|
||||
InputDecoration(labelText: s.currentPassword),
|
||||
obscureText: true,
|
||||
validator: (v) =>
|
||||
oldPasswdController.text != settings.dbPasswd
|
||||
? s.invalidPassword
|
||||
: null,
|
||||
controller: oldPasswdController),
|
||||
TextFormField(
|
||||
decoration: InputDecoration(labelText: s.newPassword),
|
||||
obscureText: true,
|
||||
controller: newPasswdController),
|
||||
TextFormField(
|
||||
decoration:
|
||||
InputDecoration(labelText: s.repeatNewPassword),
|
||||
obscureText: true,
|
||||
validator: (v) => newPasswdController.text != v
|
||||
? s.newPasswordsDoNotMatch
|
||||
: null,
|
||||
controller: repeatPasswdController),
|
||||
])),
|
||||
actions: confirmButtons(context, () {
|
||||
if (formKey.currentState!.validate())
|
||||
Navigator.of(context).pop(true);
|
||||
}, cancelValue: false))) ??
|
||||
false;
|
||||
if (confirmed) {
|
||||
if (oldPasswdController.text != settings.dbPasswd)
|
||||
showSnackBar(s.currentPasswordIncorrect);
|
||||
else if (newPasswdController.text != repeatPasswdController.text) {
|
||||
showSnackBar(s.newPasswordsDoNotMatch);
|
||||
} else {
|
||||
final passwd = newPasswdController.text;
|
||||
for (var c in coins) {
|
||||
final tempPath = p.join(settings.tempDir, c.dbName);
|
||||
WarpApi.cloneDbWithPasswd(c.coin, tempPath, passwd);
|
||||
}
|
||||
final prefs = await SharedPreferences.getInstance();
|
||||
prefs.setBool('recover', true);
|
||||
showSnackBar(s.databaseEncrypted);
|
||||
await showRestartMessage();
|
||||
final passwd = newPasswdController.text;
|
||||
for (var c in coins) {
|
||||
final tempPath = p.join(settings.tempDir, c.dbName);
|
||||
WarpApi.cloneDbWithPasswd(c.coin, tempPath, passwd);
|
||||
}
|
||||
final prefs = await SharedPreferences.getInstance();
|
||||
prefs.setBool('recover', true);
|
||||
showSnackBar(s.databaseEncrypted);
|
||||
await showRestartMessage();
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -5,6 +5,7 @@ import 'dart:io';
|
|||
import 'dart:math';
|
||||
import 'dart:ui';
|
||||
|
||||
import 'package:another_flushbar/flushbar_helper.dart';
|
||||
import 'package:app_links/app_links.dart';
|
||||
import 'package:camera/camera.dart';
|
||||
import 'package:flutter/foundation.dart';
|
||||
|
@ -705,17 +706,18 @@ String trailing(String v, int n) {
|
|||
return v.substring(v.length - len);
|
||||
}
|
||||
|
||||
void showSnackBar(String msg, {bool autoClose = false, bool quick = false}) {
|
||||
final duration = quick ? Duration(seconds: 1) : Duration(seconds: 4);
|
||||
final snackBar = SnackBar(
|
||||
content: SelectableText(msg),
|
||||
duration: autoClose ? duration : Duration(minutes: 1),
|
||||
action: SnackBarAction(
|
||||
label: S.current.close,
|
||||
onPressed: () {
|
||||
rootScaffoldMessengerKey.currentState?.hideCurrentSnackBar();
|
||||
}));
|
||||
rootScaffoldMessengerKey.currentState?.showSnackBar(snackBar);
|
||||
void showSnackBar(String msg,
|
||||
{bool autoClose = false, bool quick = false, bool error = false}) {
|
||||
final duration = !autoClose
|
||||
? Duration(minutes: 1)
|
||||
: quick
|
||||
? Duration(seconds: 1)
|
||||
: Duration(seconds: 4);
|
||||
|
||||
final bar = error
|
||||
? FlushbarHelper.createError(message: msg, duration: duration)
|
||||
: FlushbarHelper.createInformation(message: msg, duration: duration);
|
||||
bar.show(navigatorKey.currentContext!);
|
||||
}
|
||||
|
||||
void showBalanceNotification(int prevBalances, int curBalances) {
|
||||
|
@ -835,7 +837,7 @@ Future<void> shieldTAddr(BuildContext context) async {
|
|||
active.coin, active.id, active.tbalance, settings.anchorOffset);
|
||||
Navigator.of(context).pushNamed('/txplan', arguments: txPlan);
|
||||
} on String catch (msg) {
|
||||
showSnackBar(msg);
|
||||
showSnackBar(msg, error: true);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
163
lib/pools.dart
163
lib/pools.dart
|
@ -15,7 +15,8 @@ class PoolsState extends State<PoolsPage> {
|
|||
int _fromPool = 0;
|
||||
int _toPool = 1;
|
||||
final _amountKey = GlobalKey<DualMoneyInputState>();
|
||||
var _maxAmountController = TextEditingController(text: amountToString(0, precision(settings.useMillis)));
|
||||
var _maxAmountController = TextEditingController(
|
||||
text: amountToString(0, precision(settings.useMillis)));
|
||||
var _memoController = TextEditingController();
|
||||
|
||||
@override
|
||||
|
@ -30,79 +31,93 @@ class PoolsState extends State<PoolsPage> {
|
|||
var pools = ['Transparent', 'Sapling', 'Orchard'];
|
||||
|
||||
final b = active.poolBalances;
|
||||
final balances = [b.transparent, b.sapling, b.orchard].map((v) => v / ZECUNIT).toList();
|
||||
final balances =
|
||||
[b.transparent, b.sapling, b.orchard].map((v) => v / ZECUNIT).toList();
|
||||
if (!active.coinDef.supportsUA) {
|
||||
pools.removeAt(2);
|
||||
balances.removeAt(2);
|
||||
}
|
||||
return Scaffold(
|
||||
appBar: AppBar(title: Text(s.pools)),
|
||||
body: SingleChildScrollView(
|
||||
child: Padding(
|
||||
padding: EdgeInsets.all(16),
|
||||
child: Form(key: _formKey, child: Column(children: [
|
||||
HorizontalBarChart(balances, height: 40),
|
||||
Padding(padding: EdgeInsets.symmetric(vertical: 8.0)),
|
||||
DropdownButtonFormField<int>(
|
||||
decoration:
|
||||
InputDecoration(labelText: s.fromPool),
|
||||
value: _fromPool,
|
||||
items: pools.asMap().entries
|
||||
.map((e) => DropdownMenuItem(
|
||||
child: Text(e.value), value: e.key))
|
||||
.toList(),
|
||||
onChanged: (v) {
|
||||
setState(() {
|
||||
_fromPool = v!;
|
||||
});
|
||||
}
|
||||
),
|
||||
DropdownButtonFormField<int>(
|
||||
decoration:
|
||||
InputDecoration(labelText: s.toPool),
|
||||
value: _toPool,
|
||||
items: pools.asMap().entries
|
||||
.map((e) => DropdownMenuItem(
|
||||
child: Text(e.value), value: e.key))
|
||||
.toList(),
|
||||
onChanged: (v) {
|
||||
setState(() {
|
||||
_toPool = v!;
|
||||
});
|
||||
}
|
||||
),
|
||||
DualMoneyInputWidget(key: _amountKey, initialValue: 0, spendable: _spendable, max: true),
|
||||
Text(s.maxSpendableAmount(amountToString(_spendable, MAX_PRECISION), active.coinDef.ticker)),
|
||||
Padding(padding: EdgeInsets.symmetric(vertical: 8.0)),
|
||||
TextFormField(
|
||||
decoration:
|
||||
InputDecoration(labelText: s.memo),
|
||||
minLines: 4,
|
||||
maxLines: null,
|
||||
keyboardType: TextInputType.multiline,
|
||||
controller: _memoController),
|
||||
Padding(padding: EdgeInsets.symmetric(vertical: 8.0)),
|
||||
TextFormField(
|
||||
decoration: InputDecoration(
|
||||
labelText: s.maxAmountPerNote),
|
||||
keyboardType: TextInputType.number,
|
||||
controller: _maxAmountController,
|
||||
inputFormatters: [
|
||||
makeInputFormatter(settings.useMillis)
|
||||
],
|
||||
validator: _checkMaxAmountPerNote),
|
||||
Padding(padding: EdgeInsets.symmetric(vertical: 8.0)),
|
||||
ElevatedButton.icon(onPressed: _transfer, icon: Icon(Icons.currency_exchange), label: Text(s.transfer))
|
||||
]))),
|
||||
));
|
||||
appBar: AppBar(title: Text(s.pools)),
|
||||
body: SingleChildScrollView(
|
||||
child: Padding(
|
||||
padding: EdgeInsets.all(16),
|
||||
child: Form(
|
||||
key: _formKey,
|
||||
child: Column(children: [
|
||||
HorizontalBarChart(balances, height: 40),
|
||||
Padding(padding: EdgeInsets.symmetric(vertical: 8.0)),
|
||||
DropdownButtonFormField<int>(
|
||||
decoration: InputDecoration(labelText: s.fromPool),
|
||||
value: _fromPool,
|
||||
items: pools
|
||||
.asMap()
|
||||
.entries
|
||||
.map((e) => DropdownMenuItem(
|
||||
child: Text(e.value), value: e.key))
|
||||
.toList(),
|
||||
onChanged: (v) {
|
||||
setState(() {
|
||||
_fromPool = v!;
|
||||
});
|
||||
}),
|
||||
DropdownButtonFormField<int>(
|
||||
decoration: InputDecoration(labelText: s.toPool),
|
||||
value: _toPool,
|
||||
items: pools
|
||||
.asMap()
|
||||
.entries
|
||||
.map((e) => DropdownMenuItem(
|
||||
child: Text(e.value), value: e.key))
|
||||
.toList(),
|
||||
onChanged: (v) {
|
||||
setState(() {
|
||||
_toPool = v!;
|
||||
});
|
||||
}),
|
||||
DualMoneyInputWidget(
|
||||
key: _amountKey,
|
||||
initialValue: 0,
|
||||
spendable: _spendable,
|
||||
max: true),
|
||||
Text(s.maxSpendableAmount(
|
||||
amountToString(_spendable, MAX_PRECISION),
|
||||
active.coinDef.ticker)),
|
||||
Padding(padding: EdgeInsets.symmetric(vertical: 8.0)),
|
||||
TextFormField(
|
||||
decoration: InputDecoration(labelText: s.memo),
|
||||
minLines: 4,
|
||||
maxLines: null,
|
||||
keyboardType: TextInputType.multiline,
|
||||
controller: _memoController),
|
||||
Padding(padding: EdgeInsets.symmetric(vertical: 8.0)),
|
||||
TextFormField(
|
||||
decoration:
|
||||
InputDecoration(labelText: s.maxAmountPerNote),
|
||||
keyboardType: TextInputType.number,
|
||||
controller: _maxAmountController,
|
||||
inputFormatters: [
|
||||
makeInputFormatter(settings.useMillis)
|
||||
],
|
||||
validator: _checkMaxAmountPerNote),
|
||||
Padding(padding: EdgeInsets.symmetric(vertical: 8.0)),
|
||||
ElevatedButton.icon(
|
||||
onPressed: _transfer,
|
||||
icon: Icon(Icons.currency_exchange),
|
||||
label: Text(s.transfer))
|
||||
]))),
|
||||
));
|
||||
}
|
||||
|
||||
int get _spendable {
|
||||
final b = active.poolBalances;
|
||||
switch (_fromPool) {
|
||||
case 0: return b.transparent;
|
||||
case 1: return b.sapling;
|
||||
case 2: return b.orchard;
|
||||
case 0:
|
||||
return b.transparent;
|
||||
case 1:
|
||||
return b.sapling;
|
||||
case 2:
|
||||
return b.orchard;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
@ -116,12 +131,20 @@ class PoolsState extends State<PoolsPage> {
|
|||
final amount = _amountKey.currentState?.amount ?? 0;
|
||||
final includeFee = _amountKey.currentState?.feeIncluded ?? false;
|
||||
try {
|
||||
final txPlan = await WarpApi.transferPools(active.coin, active.id, 1 << _fromPool, 1 << _toPool, amount,
|
||||
includeFee, _memoController.text, stringToAmount(_maxAmountController.text), settings.anchorOffset);
|
||||
Navigator.of(context).pushReplacementNamed('/txplan', arguments: txPlan);
|
||||
}
|
||||
on String catch (message) {
|
||||
showSnackBar(message);
|
||||
final txPlan = await WarpApi.transferPools(
|
||||
active.coin,
|
||||
active.id,
|
||||
1 << _fromPool,
|
||||
1 << _toPool,
|
||||
amount,
|
||||
includeFee,
|
||||
_memoController.text,
|
||||
stringToAmount(_maxAmountController.text),
|
||||
settings.anchorOffset);
|
||||
Navigator.of(context)
|
||||
.pushReplacementNamed('/txplan', arguments: txPlan);
|
||||
} on String catch (message) {
|
||||
showSnackBar(message, error: true);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
117
lib/reset.dart
117
lib/reset.dart
|
@ -85,19 +85,22 @@ class FullBackupState extends State<FullBackupPage> {
|
|||
return Observer(builder: (context) {
|
||||
key.text = settings.backupEncKey;
|
||||
return Scaffold(
|
||||
appBar: AppBar(title: Text(s.fullBackup)),
|
||||
body: Card(
|
||||
child: Column(children: [
|
||||
QRInput(s.backupEncryptionKey, key, hint: 'age'),
|
||||
Padding(padding: EdgeInsets.symmetric(vertical: 8)),
|
||||
ButtonBar(children: [
|
||||
ElevatedButton.icon(onPressed: _onGenerate, icon: Icon(Icons.key), label: Text('Generate')),
|
||||
ElevatedButton.icon(
|
||||
onPressed: () => _onSave(context),
|
||||
icon: Icon(Icons.save),
|
||||
label: Text(s.saveBackup))
|
||||
])
|
||||
])));
|
||||
appBar: AppBar(title: Text(s.fullBackup)),
|
||||
body: Card(
|
||||
child: Column(children: [
|
||||
QRInput(s.backupEncryptionKey, key, hint: 'age'),
|
||||
Padding(padding: EdgeInsets.symmetric(vertical: 8)),
|
||||
ButtonBar(children: [
|
||||
ElevatedButton.icon(
|
||||
onPressed: _onGenerate,
|
||||
icon: Icon(Icons.key),
|
||||
label: Text('Generate')),
|
||||
ElevatedButton.icon(
|
||||
onPressed: () => _onSave(context),
|
||||
icon: Icon(Icons.save),
|
||||
label: Text(s.saveBackup))
|
||||
])
|
||||
])));
|
||||
});
|
||||
}
|
||||
|
||||
|
@ -109,9 +112,8 @@ class FullBackupState extends State<FullBackupPage> {
|
|||
final backupPath = p.join(settings.tempDir, BACKUP_NAME);
|
||||
await exportFile(context, backupPath, BACKUP_NAME);
|
||||
Navigator.of(context).pop();
|
||||
}
|
||||
on String catch (msg) {
|
||||
showSnackBar(msg);
|
||||
} on String catch (msg) {
|
||||
showSnackBar(msg, error: true);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -121,31 +123,33 @@ class FullBackupState extends State<FullBackupPage> {
|
|||
final navigator = Navigator.of(context);
|
||||
final state = GlobalKey<AGEKeysState>();
|
||||
final assign = await showDialog<bool?>(
|
||||
context: context,
|
||||
builder: (context) => AlertDialog(
|
||||
contentPadding: EdgeInsets.all(16),
|
||||
title: Text('Backup Keys'),
|
||||
content: AGEKeysForm(keys, key: state),
|
||||
actions: [
|
||||
ElevatedButton.icon(
|
||||
icon: Icon(Icons.cancel),
|
||||
label: Text(S.current.cancel),
|
||||
onPressed: () { navigator.pop(null); }
|
||||
),
|
||||
ElevatedButton.icon(
|
||||
icon: Icon(Icons.check),
|
||||
label: Text(S.current.set),
|
||||
onPressed: () { navigator.pop(true); }
|
||||
)]
|
||||
)) ?? false;
|
||||
if (assign)
|
||||
settings.setBackupEncKey(state.currentState!.pk.text);
|
||||
context: context,
|
||||
builder: (context) => AlertDialog(
|
||||
contentPadding: EdgeInsets.all(16),
|
||||
title: Text('Backup Keys'),
|
||||
content: AGEKeysForm(keys, key: state),
|
||||
actions: [
|
||||
ElevatedButton.icon(
|
||||
icon: Icon(Icons.cancel),
|
||||
label: Text(S.current.cancel),
|
||||
onPressed: () {
|
||||
navigator.pop(null);
|
||||
}),
|
||||
ElevatedButton.icon(
|
||||
icon: Icon(Icons.check),
|
||||
label: Text(S.current.set),
|
||||
onPressed: () {
|
||||
navigator.pop(true);
|
||||
})
|
||||
])) ??
|
||||
false;
|
||||
if (assign) settings.setBackupEncKey(state.currentState!.pk.text);
|
||||
}
|
||||
}
|
||||
|
||||
class AGEKeysForm extends StatefulWidget {
|
||||
final Agekeys keys;
|
||||
AGEKeysForm(this.keys, {Key? key}): super(key: key);
|
||||
AGEKeysForm(this.keys, {Key? key}) : super(key: key);
|
||||
|
||||
@override
|
||||
State<StatefulWidget> createState() => AGEKeysState();
|
||||
|
@ -165,7 +169,9 @@ class AGEKeysState extends State<AGEKeysForm> {
|
|||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final s = S.of(context);
|
||||
return Form(child: SingleChildScrollView(child: Column(children: [
|
||||
return Form(
|
||||
child: SingleChildScrollView(
|
||||
child: Column(children: [
|
||||
QRDisplay(s.encryptionKey, widget.keys.pk!),
|
||||
QRDisplay(s.secretKey, widget.keys.sk!)
|
||||
])));
|
||||
|
@ -185,7 +191,8 @@ class _FullRestoreState extends State<FullRestorePage> {
|
|||
final s = S.of(context);
|
||||
return Scaffold(
|
||||
appBar: AppBar(title: Text(s.fullRestore)),
|
||||
body: Card(child: Column(children: [
|
||||
body: Card(
|
||||
child: Column(children: [
|
||||
QRInput(s.secretKey, controller, hint: 'AGE-SECRET-KEY-'),
|
||||
Padding(padding: EdgeInsets.symmetric(vertical: 8)),
|
||||
ElevatedButton.icon(
|
||||
|
@ -205,8 +212,7 @@ class _FullRestoreState extends State<FullRestorePage> {
|
|||
try {
|
||||
if (key.isNotEmpty) {
|
||||
WarpApi.unzipBackup(key, filename, settings.tempDir);
|
||||
}
|
||||
else {
|
||||
} else {
|
||||
final file = File(filename);
|
||||
final backup = await file.readAsString();
|
||||
WarpApi.importFromZWL(active.coin, "ZWL Imported Account", backup);
|
||||
|
@ -215,9 +221,8 @@ class _FullRestoreState extends State<FullRestorePage> {
|
|||
await prefs.setBool('recover', true);
|
||||
showSnackBar(s.databaseRestored);
|
||||
await showRestartMessage(); // This doesn't return
|
||||
}
|
||||
on String catch (message) {
|
||||
showSnackBar(message);
|
||||
} on String catch (message) {
|
||||
showSnackBar(message, error: true);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -225,20 +230,24 @@ class _FullRestoreState extends State<FullRestorePage> {
|
|||
|
||||
Future<void> showRestartMessage() async {
|
||||
final context = navigatorKey.currentContext!;
|
||||
await showDialog(context: context, barrierDismissible: false, builder:
|
||||
(context) => AlertDialog(
|
||||
content: Text(S.of(context).pleaseQuitAndRestartTheAppNow)
|
||||
)
|
||||
);
|
||||
await showDialog(
|
||||
context: context,
|
||||
barrierDismissible: false,
|
||||
builder: (context) => AlertDialog(
|
||||
content: Text(S.of(context).pleaseQuitAndRestartTheAppNow)));
|
||||
}
|
||||
|
||||
Future<void> clearApp(BuildContext context) async {
|
||||
final reset = await showDialog<bool>(context: context, barrierDismissible: false, builder:
|
||||
(context) => AlertDialog(
|
||||
title: Text('Reset Database'),
|
||||
content: Text('Are you sure you want to DELETE ALL your data?'),
|
||||
actions: confirmButtons(context, () => Navigator.of(context).pop(true)),
|
||||
)) ?? false;
|
||||
final reset = await showDialog<bool>(
|
||||
context: context,
|
||||
barrierDismissible: false,
|
||||
builder: (context) => AlertDialog(
|
||||
title: Text('Reset Database'),
|
||||
content: Text('Are you sure you want to DELETE ALL your data?'),
|
||||
actions: confirmButtons(
|
||||
context, () => Navigator.of(context).pop(true)),
|
||||
)) ??
|
||||
false;
|
||||
if (reset) {
|
||||
final c = coins.first;
|
||||
File(c.dbDir).deleteSync(recursive: true);
|
||||
|
|
|
@ -36,8 +36,9 @@ class ServerSelection {
|
|||
|
||||
ServerSelection(this.coin, this.selected, this.custom);
|
||||
factory ServerSelection.load(int coin) {
|
||||
final selected = WarpApi.getProperty(coin, lwdChoiceKey);
|
||||
var selected = WarpApi.getProperty(coin, lwdChoiceKey);
|
||||
final custom = WarpApi.getProperty(coin, lwdCustomKey);
|
||||
if (selected.isEmpty) selected = 'auto';
|
||||
return ServerSelection(coin, selected, custom);
|
||||
}
|
||||
|
||||
|
@ -769,7 +770,7 @@ abstract class _SyncStatus with Store {
|
|||
}
|
||||
}
|
||||
} on String catch (e) {
|
||||
showSnackBar(e);
|
||||
showSnackBar(e, error: true);
|
||||
paused = true;
|
||||
} finally {
|
||||
syncing = false;
|
||||
|
|
155
lib/txplan.dart
155
lib/txplan.dart
|
@ -40,40 +40,70 @@ class TxPlanPage extends StatelessWidget {
|
|||
appBar: AppBar(title: Text('Transaction Plan')),
|
||||
body: Padding(
|
||||
padding: EdgeInsets.all(8),
|
||||
child: SingleChildScrollView(child: Column(
|
||||
children: [
|
||||
Card(
|
||||
child: DataTable(columnSpacing: 32, columns: [
|
||||
DataColumn(label: Text('Address')),
|
||||
DataColumn(label: Text('Pool')),
|
||||
DataColumn(label: Expanded(child: Text('Amount'))),
|
||||
], rows: rows)),
|
||||
Divider(height: 16, thickness: 2, color: theme.primaryColor,),
|
||||
ListTile(title: Text('Transparent Input'), trailing: Text(amountToString(report.transparent, MAX_PRECISION))),
|
||||
ListTile(title: Text('Sapling Input'), trailing: Text(
|
||||
amountToString(report.sapling, MAX_PRECISION))),
|
||||
if (supportsUA) ListTile(title: Text('Orchard Input'), trailing: Text(
|
||||
amountToString(report.orchard, MAX_PRECISION))),
|
||||
ListTile(title: Text('Net Sapling Change'), trailing: Text(
|
||||
amountToString(report.netSapling, MAX_PRECISION))),
|
||||
if (supportsUA) ListTile(title: Text('Net Orchard Change'), trailing: Text(
|
||||
amountToString(report.netOrchard, MAX_PRECISION))),
|
||||
ListTile(title: Text('Fee'), trailing: Text(amountToString(report.fee, MAX_PRECISION))),
|
||||
privacyToString(context, report.privacyLevel)!,
|
||||
if (invalidPrivacy) Padding(padding: EdgeInsets.only(top: 8), child: Text(s.privacyLevelTooLow, style: t.textTheme.bodyLarge)),
|
||||
ButtonBar(children:
|
||||
<ElevatedButton>[
|
||||
ElevatedButton.icon(
|
||||
child: SingleChildScrollView(
|
||||
child: Column(children: [
|
||||
Card(
|
||||
child: DataTable(
|
||||
columnSpacing: 32,
|
||||
columns: [
|
||||
DataColumn(label: Text('Address')),
|
||||
DataColumn(label: Text('Pool')),
|
||||
DataColumn(label: Expanded(child: Text('Amount'))),
|
||||
],
|
||||
rows: rows)),
|
||||
Divider(
|
||||
height: 16,
|
||||
thickness: 2,
|
||||
color: theme.primaryColor,
|
||||
),
|
||||
ListTile(
|
||||
title: Text('Transparent Input'),
|
||||
trailing:
|
||||
Text(amountToString(report.transparent, MAX_PRECISION))),
|
||||
ListTile(
|
||||
title: Text('Sapling Input'),
|
||||
trailing:
|
||||
Text(amountToString(report.sapling, MAX_PRECISION))),
|
||||
if (supportsUA)
|
||||
ListTile(
|
||||
title: Text('Orchard Input'),
|
||||
trailing:
|
||||
Text(amountToString(report.orchard, MAX_PRECISION))),
|
||||
ListTile(
|
||||
title: Text('Net Sapling Change'),
|
||||
trailing:
|
||||
Text(amountToString(report.netSapling, MAX_PRECISION))),
|
||||
if (supportsUA)
|
||||
ListTile(
|
||||
title: Text('Net Orchard Change'),
|
||||
trailing:
|
||||
Text(amountToString(report.netOrchard, MAX_PRECISION))),
|
||||
ListTile(
|
||||
title: Text('Fee'),
|
||||
trailing: Text(amountToString(report.fee, MAX_PRECISION))),
|
||||
privacyToString(context, report.privacyLevel)!,
|
||||
if (invalidPrivacy)
|
||||
Padding(
|
||||
padding: EdgeInsets.only(top: 8),
|
||||
child: Text(s.privacyLevelTooLow,
|
||||
style: t.textTheme.bodyLarge)),
|
||||
ButtonBar(
|
||||
children: <ElevatedButton>[
|
||||
ElevatedButton.icon(
|
||||
icon: Icon(Icons.cancel),
|
||||
label: Text(s.cancel),
|
||||
onPressed: () { Navigator.of(context).pop(); }),
|
||||
ElevatedButton.icon(
|
||||
icon: Icon(Icons.done),
|
||||
label: Text(s.send),
|
||||
onPressed: invalidPrivacy ? null: _onSend,
|
||||
onLongPress: _onSend,
|
||||
)],
|
||||
)]))));
|
||||
onPressed: () {
|
||||
Navigator.of(context).pop();
|
||||
}),
|
||||
ElevatedButton.icon(
|
||||
icon: Icon(Icons.done),
|
||||
label: Text(s.send),
|
||||
onPressed: invalidPrivacy ? null : _onSend,
|
||||
onLongPress: _onSend,
|
||||
)
|
||||
],
|
||||
)
|
||||
]))));
|
||||
}
|
||||
|
||||
_onSend() async {
|
||||
|
@ -85,28 +115,26 @@ class TxPlanPage extends StatelessWidget {
|
|||
active.setBanner(S.current.paymentInProgress);
|
||||
final player = AudioPlayer();
|
||||
try {
|
||||
final txid = await WarpApi.signAndBroadcast(active.coin, active.id, plan);
|
||||
final txid =
|
||||
await WarpApi.signAndBroadcast(active.coin, active.id, plan);
|
||||
showSnackBar(S.current.txId(txid));
|
||||
await player.play(AssetSource("success.mp3"));
|
||||
active.setDraftRecipient(null);
|
||||
active.update();
|
||||
}
|
||||
on String catch (message) {
|
||||
showSnackBar(message);
|
||||
} on String catch (message) {
|
||||
showSnackBar(message, error: true);
|
||||
await player.play(AssetSource("fail.mp3"));
|
||||
}
|
||||
finally {
|
||||
} finally {
|
||||
active.setBanner("");
|
||||
}
|
||||
});
|
||||
navigatorKey.currentState?.pop();
|
||||
}
|
||||
}
|
||||
else {
|
||||
} else {
|
||||
if (settings.qrOffline) {
|
||||
navigatorKey.currentState?.pushReplacementNamed('/qroffline', arguments: plan);
|
||||
}
|
||||
else {
|
||||
navigatorKey.currentState
|
||||
?.pushReplacementNamed('/qroffline', arguments: plan);
|
||||
} else {
|
||||
await saveFile(plan, "tx.json", S.current.unsignedTransactionFile);
|
||||
showSnackBar(S.current.fileSaved);
|
||||
}
|
||||
|
@ -116,8 +144,8 @@ class TxPlanPage extends StatelessWidget {
|
|||
_sign() async {
|
||||
try {
|
||||
showSnackBar(S.current.signingPleaseWait);
|
||||
final res = await WarpApi.signOnly(
|
||||
active.coin, active.id, plan, (progress) {
|
||||
final res =
|
||||
await WarpApi.signOnly(active.coin, active.id, plan, (progress) {
|
||||
// TODO progressPort.sendPort.send(progress);
|
||||
// Orchard builder does not support progress reporting yet
|
||||
});
|
||||
|
@ -125,14 +153,13 @@ class TxPlanPage extends StatelessWidget {
|
|||
active.setBanner("");
|
||||
active.setDraftRecipient(null);
|
||||
if (settings.qrOffline) {
|
||||
navigatorKey.currentState?.pushReplacementNamed(
|
||||
'/showRawTx', arguments: res);
|
||||
navigatorKey.currentState
|
||||
?.pushReplacementNamed('/showRawTx', arguments: res);
|
||||
} else {
|
||||
await saveFile(res, 'tx.raw', S.current.rawTransaction);
|
||||
}
|
||||
}
|
||||
on String catch (error) {
|
||||
showSnackBar(error);
|
||||
} on String catch (error) {
|
||||
showSnackBar(error, error: true);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -148,20 +175,30 @@ String poolToString(int pool) {
|
|||
}
|
||||
|
||||
Widget? privacyToString(BuildContext context, int privacyLevel) {
|
||||
final m = S.of(context).privacy(getPrivacyLevel(context, privacyLevel).toUpperCase());
|
||||
final m = S
|
||||
.of(context)
|
||||
.privacy(getPrivacyLevel(context, privacyLevel).toUpperCase());
|
||||
switch (privacyLevel) {
|
||||
case 0: return getColoredButton(m, Colors.red);
|
||||
case 1: return getColoredButton(m, Colors.orange);
|
||||
case 2: return getColoredButton(m, Colors.yellow);
|
||||
case 3: return getColoredButton(m, Colors.green);
|
||||
case 0:
|
||||
return getColoredButton(m, Colors.red);
|
||||
case 1:
|
||||
return getColoredButton(m, Colors.orange);
|
||||
case 2:
|
||||
return getColoredButton(m, Colors.yellow);
|
||||
case 3:
|
||||
return getColoredButton(m, Colors.green);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
ElevatedButton getColoredButton(String text, Color color) {
|
||||
var foregroundColor = color.computeLuminance() > 0.5 ? Colors.black : Colors.white;
|
||||
var foregroundColor =
|
||||
color.computeLuminance() > 0.5 ? Colors.black : Colors.white;
|
||||
|
||||
return ElevatedButton(onPressed: null, child: Text(text), style:
|
||||
ElevatedButton.styleFrom(
|
||||
disabledBackgroundColor: color, disabledForegroundColor: foregroundColor));
|
||||
return ElevatedButton(
|
||||
onPressed: null,
|
||||
child: Text(text),
|
||||
style: ElevatedButton.styleFrom(
|
||||
disabledBackgroundColor: color,
|
||||
disabledForegroundColor: foregroundColor));
|
||||
}
|
||||
|
|
|
@ -25,6 +25,14 @@ packages:
|
|||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "4.2.2"
|
||||
another_flushbar:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
name: another_flushbar
|
||||
sha256: fa09f8a4ca582c417669b7b1d0e85ce65bd074d80bb0dcbb1302ad1b22bdc3ef
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "1.12.29"
|
||||
app_links:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
|
|
|
@ -15,7 +15,7 @@ publish_to: 'none' # Remove this line if you wish to publish to pub.dev
|
|||
# In iOS, build-name is used as CFBundleShortVersionString while build-number used as CFBundleVersion.
|
||||
# Read more about iOS versioning at
|
||||
# https://developer.apple.com/library/archive/documentation/General/Reference/InfoPlistKeyReference/Articles/CoreFoundationKeys.html
|
||||
version: 1.3.6+419
|
||||
version: 1.3.6+420
|
||||
|
||||
environment:
|
||||
sdk: ">=2.12.0 <3.0.0"
|
||||
|
@ -52,6 +52,7 @@ dependencies:
|
|||
flex_color_scheme: ^4.2.0
|
||||
flutter_colorpicker: ^1.0.3
|
||||
fl_chart: ^0.50.1
|
||||
another_flushbar: ^1.12.29
|
||||
k_chart:
|
||||
git:
|
||||
url: https://github.com/hhanh00/k_chart.git
|
||||
|
|
Loading…
Reference in New Issue