This commit is contained in:
Hanh 2023-03-16 00:00:47 +10:00
parent 35734222ee
commit 42f5f2c377
12 changed files with 423 additions and 304 deletions

View File

@ -321,7 +321,7 @@ class QRAddressState extends State<QRAddressWidget> {
await active.refreshTAddr();
active.updateTBalance();
} on String catch (message) {
showSnackBar(message);
showSnackBar(message, error: true);
}
}
}

View File

@ -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);

View File

@ -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);
}

View File

@ -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();
}
}

View File

@ -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();
}
}

View File

@ -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);
}
}

View File

@ -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);
}
}
}

View File

@ -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);

View File

@ -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;

View File

@ -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));
}

View File

@ -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:

View File

@ -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