Account Manager

- New account from seed does not trigger rescan until manager closed
- No account icon
- Message Box when duplicate account
This commit is contained in:
Hanh 2021-09-06 12:41:32 +08:00
parent f68830e2c7
commit f977338c7a
13 changed files with 98 additions and 66 deletions

View File

@ -37,7 +37,6 @@ class _AccountPageState extends State<AccountPage>
String _snapAddress = ""; String _snapAddress = "";
TabController _tabController; TabController _tabController;
bool _accountTab = true; bool _accountTab = true;
bool _syncing = false;
StreamSubscription _progressDispose; StreamSubscription _progressDispose;
StreamSubscription _syncDispose; StreamSubscription _syncDispose;
@ -70,7 +69,6 @@ class _AccountPageState extends State<AccountPage>
syncStatus.setSyncHeight(height); syncStatus.setSyncHeight(height);
eta.checkpoint(height, DateTime.now()); eta.checkpoint(height, DateTime.now());
} else { } else {
_syncing = false;
WarpApi.mempoolReset(syncStatus.latestHeight); WarpApi.mempoolReset(syncStatus.latestHeight);
_trySync(); _trySync();
} }
@ -104,10 +102,21 @@ class _AccountPageState extends State<AccountPage>
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
super.build(context); super.build(context);
if (!syncStatus.isSynced() && !_syncing) _trySync(); if (!syncStatus.isSynced() && !syncStatus.syncing) _trySync();
if (accountManager.active == null) return CircularProgressIndicator(); if (accountManager.active == null) return CircularProgressIndicator();
final theme = Theme.of(this.context); final theme = Theme.of(this.context);
final hasTAddr = accountManager.taddress.isNotEmpty; final hasTAddr = accountManager.taddress.isNotEmpty;
if (syncStatus.accountRestored) {
syncStatus.setAccountRestored(false);
Future.microtask(() {
rescanDialog(context, () {
syncStatus.sync(context);
Navigator.of(context).pop();
});
});
}
return Scaffold( return Scaffold(
appBar: AppBar( appBar: AppBar(
title: Observer( title: Observer(
@ -158,8 +167,8 @@ class _AccountPageState extends State<AccountPage>
final _1 = eta.eta; final _1 = eta.eta;
final _2 = syncStatus.syncedHeight; final _2 = syncStatus.syncedHeight;
final _3 = syncStatus.latestHeight; final _3 = syncStatus.latestHeight;
return syncStatus.syncedHeight <= 0 return syncStatus.syncedHeight < 0
? Text(S.of(context).synching) ? Text("")
: syncStatus.isSynced() : syncStatus.isSynced()
? Text('${syncStatus.syncedHeight}', ? Text('${syncStatus.syncedHeight}',
style: theme.textTheme.caption) style: theme.textTheme.caption)
@ -368,9 +377,6 @@ class _AccountPageState extends State<AccountPage>
} }
_sync() async { _sync() async {
_syncing = true;
await syncStatus.update();
await sync(settings.getTx, settings.anchorOffset, syncPort.sendPort);
} }
_trySync() async { _trySync() async {
@ -463,15 +469,8 @@ class _AccountPageState extends State<AccountPage>
_rescan() { _rescan() {
rescanDialog(context, () { rescanDialog(context, () {
Navigator.of(context).pop(); Navigator.of(context).pop();
final snackBar = syncStatus.sync(context);
SnackBar(content: Text(S });
.of(context)
.rescanRequested));
rootScaffoldMessengerKey.currentState.showSnackBar(snackBar);
syncStatus.setSyncHeight(0);
WarpApi.rewindToHeight(0);
_sync();
});
} }
_cold() { _cold() {
@ -992,7 +991,3 @@ class ContactsState extends State<ContactsWidget>
} }
} }
Future<void> sync(bool getTx, int anchorOffset, SendPort port) async {
final params = SyncParams(getTx, anchorOffset, port);
await compute(WarpApi.warpSync, params);
}

View File

@ -25,7 +25,7 @@ class AccountManagerState extends State<AccountManagerPage> {
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
return Scaffold( return Scaffold(
appBar: AppBar(title: Text(S.of(context).accounts), actions: [ appBar: AppBar(title: Text(S.of(context).selectAccount), actions: [
PopupMenuButton<String>( PopupMenuButton<String>(
itemBuilder: (context) => [ itemBuilder: (context) => [
PopupMenuItem( PopupMenuItem(
@ -36,36 +36,33 @@ class AccountManagerState extends State<AccountManagerPage> {
onSelected: _onMenu) onSelected: _onMenu)
]), ]),
body: Observer( body: Observer(
builder: (context) => Stack(children: [ builder: (context) =>
accountManager.accounts.isEmpty accountManager.accounts.isEmpty
? Center(child: NoAccount()) ? Center(child: NoAccount())
: ListView( : ListView.builder(
children: accountManager.accounts itemCount: accountManager.accounts.length,
.asMap() itemBuilder: (BuildContext context, int index) {
.entries final a = accountManager.accounts[index];
.map((a) => Card( return Card(
child: Dismissible( child: Dismissible(
key: Key(a.value.name), key: Key(a.name),
child: ListTile( child: ListTile(
title: Text(a.value.name, title: Text(a.name,
style: Theme.of(context) style: Theme.of(context).textTheme.headline5),
.textTheme trailing: Text("${a.balance / ZECUNIT}"),
.headline5), onTap: () {
trailing: _selectAccount(a);
Text("${a.value.balance / ZECUNIT}"), },
onTap: () { onLongPress: () {
_selectAccount(a.value); _editAccount(a);
}, },
onLongPress: () { ),
_editAccount(a.value); confirmDismiss: _onAccountDelete,
}, onDismissed: (direction) =>
), _onDismissed(index, a.id),
confirmDismiss: _onAccountDelete, ));
onDismissed: (direction) => }),
_onDismissed(a.key, a.value.id), ),
)))
.toList()),
])),
floatingActionButton: FloatingActionButton( floatingActionButton: FloatingActionButton(
onPressed: _onRestore, child: Icon(Icons.add))); onPressed: _onRestore, child: Icon(Icons.add)));
} }
@ -135,14 +132,10 @@ class AccountManagerState extends State<AccountManagerPage> {
} }
class NoAccount extends StatelessWidget { class NoAccount extends StatelessWidget {
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
final Widget wallet = SvgPicture.asset( final Widget wallet = SvgPicture.asset('assets/wallet.svg',
'assets/wallet.svg', color: Theme.of(context).primaryColor, semanticsLabel: 'Wallet');
color: Theme.of(context).primaryColor,
semanticsLabel: 'Wallet'
);
return Column(mainAxisAlignment: MainAxisAlignment.center, children: [ return Column(mainAxisAlignment: MainAxisAlignment.center, children: [
SizedBox(child: wallet, height: 150, width: 150), SizedBox(child: wallet, height: 150, width: 150),

View File

@ -119,6 +119,7 @@ class MessageLookup extends MessageLookupByLibrary {
"scanStartingMomentarily" : MessageLookupByLibrary.simpleMessage("Scan starting momentarily"), "scanStartingMomentarily" : MessageLookupByLibrary.simpleMessage("Scan starting momentarily"),
"secretKey" : MessageLookupByLibrary.simpleMessage("Secret Key"), "secretKey" : MessageLookupByLibrary.simpleMessage("Secret Key"),
"seed" : MessageLookupByLibrary.simpleMessage("Seed"), "seed" : MessageLookupByLibrary.simpleMessage("Seed"),
"selectAccount" : MessageLookupByLibrary.simpleMessage("Select Account"),
"selectNotesToExcludeFromPayments" : MessageLookupByLibrary.simpleMessage("Select notes to EXCLUDE from payments"), "selectNotesToExcludeFromPayments" : MessageLookupByLibrary.simpleMessage("Select notes to EXCLUDE from payments"),
"send" : MessageLookupByLibrary.simpleMessage("Send"), "send" : MessageLookupByLibrary.simpleMessage("Send"),
"sendCointicker" : m2, "sendCointicker" : m2,

View File

@ -119,6 +119,7 @@ class MessageLookup extends MessageLookupByLibrary {
"scanStartingMomentarily" : MessageLookupByLibrary.simpleMessage("Escaneo comenzando momentáneamente "), "scanStartingMomentarily" : MessageLookupByLibrary.simpleMessage("Escaneo comenzando momentáneamente "),
"secretKey" : MessageLookupByLibrary.simpleMessage("Llave Secreta"), "secretKey" : MessageLookupByLibrary.simpleMessage("Llave Secreta"),
"seed" : MessageLookupByLibrary.simpleMessage("Semilla"), "seed" : MessageLookupByLibrary.simpleMessage("Semilla"),
"selectAccount" : MessageLookupByLibrary.simpleMessage("Select Account"),
"selectNotesToExcludeFromPayments" : MessageLookupByLibrary.simpleMessage("Seleccionar Notas a EXCLUIR de los pagos"), "selectNotesToExcludeFromPayments" : MessageLookupByLibrary.simpleMessage("Seleccionar Notas a EXCLUIR de los pagos"),
"send" : MessageLookupByLibrary.simpleMessage("Enviar"), "send" : MessageLookupByLibrary.simpleMessage("Enviar"),
"sendCointicker" : m2, "sendCointicker" : m2,

View File

@ -119,6 +119,7 @@ class MessageLookup extends MessageLookupByLibrary {
"scanStartingMomentarily" : MessageLookupByLibrary.simpleMessage("Le scan démarre momentanément"), "scanStartingMomentarily" : MessageLookupByLibrary.simpleMessage("Le scan démarre momentanément"),
"secretKey" : MessageLookupByLibrary.simpleMessage("Clé secrète"), "secretKey" : MessageLookupByLibrary.simpleMessage("Clé secrète"),
"seed" : MessageLookupByLibrary.simpleMessage("Graine"), "seed" : MessageLookupByLibrary.simpleMessage("Graine"),
"selectAccount" : MessageLookupByLibrary.simpleMessage("Select Account"),
"selectNotesToExcludeFromPayments" : MessageLookupByLibrary.simpleMessage("Sélectionnez les billets à EXCLURE des paiements"), "selectNotesToExcludeFromPayments" : MessageLookupByLibrary.simpleMessage("Sélectionnez les billets à EXCLURE des paiements"),
"send" : MessageLookupByLibrary.simpleMessage("Envoyer"), "send" : MessageLookupByLibrary.simpleMessage("Envoyer"),
"sendCointicker" : m2, "sendCointicker" : m2,

View File

@ -115,6 +115,7 @@ class MessageLookup extends MessageLookupByLibrary {
"scanStartingMomentarily" : MessageLookupByLibrary.simpleMessage("掃描即將開始"), "scanStartingMomentarily" : MessageLookupByLibrary.simpleMessage("掃描即將開始"),
"secretKey" : MessageLookupByLibrary.simpleMessage("秘密鎖匙"), "secretKey" : MessageLookupByLibrary.simpleMessage("秘密鎖匙"),
"seed" : MessageLookupByLibrary.simpleMessage("種子"), "seed" : MessageLookupByLibrary.simpleMessage("種子"),
"selectAccount" : MessageLookupByLibrary.simpleMessage("Select Account"),
"selectNotesToExcludeFromPayments" : MessageLookupByLibrary.simpleMessage("選取不需要的貨幣"), "selectNotesToExcludeFromPayments" : MessageLookupByLibrary.simpleMessage("選取不需要的貨幣"),
"send" : MessageLookupByLibrary.simpleMessage("發送"), "send" : MessageLookupByLibrary.simpleMessage("發送"),
"sendCointicker" : m2, "sendCointicker" : m2,

View File

@ -1204,6 +1204,16 @@ class S {
args: [], args: [],
); );
} }
/// `Select Account`
String get selectAccount {
return Intl.message(
'Select Account',
name: 'selectAccount',
desc: '',
args: [],
);
}
} }
class AppLocalizationDelegate extends LocalizationsDelegate<S> { class AppLocalizationDelegate extends LocalizationsDelegate<S> {

View File

@ -115,5 +115,6 @@
"useUa": "Use UA", "useUa": "Use UA",
"createANewAccount": "Create a new account and it will show up here", "createANewAccount": "Create a new account and it will show up here",
"duplicateAccount": "Duplicate Account", "duplicateAccount": "Duplicate Account",
"thisAccountAlreadyExists": "Another account has the same address" "thisAccountAlreadyExists": "Another account has the same address",
"selectAccount": "Select Account"
} }

View File

@ -116,5 +116,6 @@
"useUa": "Use UA", "useUa": "Use UA",
"createANewAccount": "Create a new account and it will show up here", "createANewAccount": "Create a new account and it will show up here",
"duplicateAccount": "Duplicate Account", "duplicateAccount": "Duplicate Account",
"thisAccountAlreadyExists": "This account already exists." "thisAccountAlreadyExists": "This account already exists.",
"selectAccount": "Select Account"
} }

View File

@ -116,5 +116,6 @@
"useUa": "Use UA", "useUa": "Use UA",
"createANewAccount": "Create a new account and it will show up here", "createANewAccount": "Create a new account and it will show up here",
"duplicateAccount": "Duplicate Account", "duplicateAccount": "Duplicate Account",
"thisAccountAlreadyExists": "This account already exists." "thisAccountAlreadyExists": "This account already exists.",
"selectAccount": "Select Account"
} }

View File

@ -112,5 +112,6 @@
"useUa": "Use UA", "useUa": "Use UA",
"createANewAccount": "Create a new account and it will show up here", "createANewAccount": "Create a new account and it will show up here",
"duplicateAccount": "Duplicate Account", "duplicateAccount": "Duplicate Account",
"thisAccountAlreadyExists": "This account already exists." "thisAccountAlreadyExists": "This account already exists.",
"selectAccount": "Select Account"
} }

View File

@ -64,7 +64,6 @@ class _RestorePageState extends State<RestorePage> {
if (_formKey.currentState.validate()) { if (_formKey.currentState.validate()) {
final account = final account =
WarpApi.newAccount(_nameController.text, _keyController.text); WarpApi.newAccount(_nameController.text, _keyController.text);
print("$account");
if (account < 0) { if (account < 0) {
showDialog( showDialog(
context: context, context: context,
@ -85,15 +84,11 @@ class _RestorePageState extends State<RestorePage> {
await accountManager.refresh(); await accountManager.refresh();
await accountManager.setActiveAccountId(account); await accountManager.setActiveAccountId(account);
if (_keyController.text != "") { if (_keyController.text != "") {
await rescanDialog(context, () { syncStatus.setAccountRestored(true);
WarpApi.rewindToHeight(0);
Navigator.of(context).pop();
});
} }
else if (accountManager.accounts.length == 1) else if (accountManager.accounts.length == 1)
WarpApi.skipToLastHeight(); // single new account -> quick sync WarpApi.skipToLastHeight(); // single new account -> quick sync
Navigator.of(context).pop(); Navigator.of(context).pop();
Navigator.of(context).pushReplacementNamed('/account');
} }
} }
} }

View File

@ -1,6 +1,7 @@
import 'dart:isolate'; import 'dart:isolate';
import 'dart:typed_data'; import 'dart:typed_data';
import 'dart:math' as math; import 'dart:math' as math;
import 'package:flutter/foundation.dart';
import 'package:json_annotation/json_annotation.dart'; import 'package:json_annotation/json_annotation.dart';
import 'package:charts_flutter/flutter.dart' as charts show MaterialPalette; import 'package:charts_flutter/flutter.dart' as charts show MaterialPalette;
@ -16,6 +17,7 @@ import 'dart:convert' as convert;
import 'package:convert/convert.dart'; import 'package:convert/convert.dart';
import 'package:flex_color_scheme/flex_color_scheme.dart'; import 'package:flex_color_scheme/flex_color_scheme.dart';
import 'generated/l10n.dart';
import 'main.dart'; import 'main.dart';
part 'store.g.dart'; part 'store.g.dart';
@ -423,6 +425,8 @@ abstract class _AccountManager with Store {
Future<void> delete(int account) async { Future<void> delete(int account) async {
await db.rawDelete("DELETE FROM accounts WHERE id_account = ?1", [account]); await db.rawDelete("DELETE FROM accounts WHERE id_account = ?1", [account]);
await db.rawDelete("DELETE FROM taddrs WHERE account = ?1", [account]); await db.rawDelete("DELETE FROM taddrs WHERE account = ?1", [account]);
if (account == active?.id)
resetToDefaultAccount();
} }
@action @action
@ -786,6 +790,12 @@ abstract class _SyncStatus with Store {
await update(); await update();
} }
@observable
bool accountRestored = false;
@observable
bool syncing = false;
@observable @observable
int syncedHeight = -1; int syncedHeight = -1;
@ -810,6 +820,27 @@ abstract class _SyncStatus with Store {
if (_syncedHeight > 0) syncedHeight = _syncedHeight; if (_syncedHeight > 0) syncedHeight = _syncedHeight;
return syncedHeight == latestHeight; return syncedHeight == latestHeight;
} }
@action
Future<void> sync(BuildContext context) async {
syncing = true;
final snackBar =
SnackBar(content: Text(S
.of(context)
.rescanRequested));
rootScaffoldMessengerKey.currentState.showSnackBar(snackBar);
syncStatus.setSyncHeight(0);
WarpApi.rewindToHeight(0);
await syncStatus.update();
final params = SyncParams(settings.getTx, settings.anchorOffset, syncPort.sendPort);
await compute(WarpApi.warpSync, params);
syncing = false;
}
@action
void setAccountRestored(bool v) {
accountRestored = v;
}
} }
class MultiPayStore = _MultiPayStore with _$MultiPayStore; class MultiPayStore = _MultiPayStore with _$MultiPayStore;