Merge pull request #506 from keerifox/tx-list-auto-refresh
Automatically refresh transaction list upon receive or new receivable tx
This commit is contained in:
commit
9b00d0d26f
4 changed files with 233 additions and 19 deletions
|
|
@ -258,12 +258,12 @@
|
||||||
<ng-template #notUpdatingTxList>
|
<ng-template #notUpdatingTxList>
|
||||||
<a
|
<a
|
||||||
class="icon-transactions-refresh"
|
class="icon-transactions-refresh"
|
||||||
[class.disabled]="!statsRefreshEnabled"
|
[class.disabled]="!manualRefreshAllowed"
|
||||||
[class.uk-text-muted]="!statsRefreshEnabled"
|
[class.uk-text-muted]="!manualRefreshAllowed"
|
||||||
uk-icon="icon: refresh;"
|
uk-icon="icon: refresh;"
|
||||||
[title]="'general.reload' | transloco"
|
[title]="'general.reload' | transloco"
|
||||||
uk-tooltip
|
uk-tooltip
|
||||||
(click)="loadAccountDetails(true)"
|
(click)="onRefreshButtonClick()"
|
||||||
></a>
|
></a>
|
||||||
</ng-template>
|
</ng-template>
|
||||||
</li>
|
</li>
|
||||||
|
|
|
||||||
|
|
@ -42,7 +42,9 @@ export class AccountDetailsComponent implements OnInit, OnDestroy {
|
||||||
|
|
||||||
walletAccount = null;
|
walletAccount = null;
|
||||||
|
|
||||||
timeoutIdAllowingRefresh: any = null;
|
timeoutIdAllowingManualRefresh: any = null;
|
||||||
|
timeoutIdAllowingInstantAutoRefresh: any = null;
|
||||||
|
timeoutIdQueuedAutoRefresh: any = null;
|
||||||
qrModal: any = null;
|
qrModal: any = null;
|
||||||
mobileAccountMenuModal: any = null;
|
mobileAccountMenuModal: any = null;
|
||||||
mobileTransactionMenuModal: any = null;
|
mobileTransactionMenuModal: any = null;
|
||||||
|
|
@ -66,7 +68,11 @@ export class AccountDetailsComponent implements OnInit, OnDestroy {
|
||||||
routerSub = null;
|
routerSub = null;
|
||||||
priceSub = null;
|
priceSub = null;
|
||||||
|
|
||||||
statsRefreshEnabled = true;
|
initialLoadDone = false;
|
||||||
|
manualRefreshAllowed = true;
|
||||||
|
instantAutoRefreshAllowed = true;
|
||||||
|
shouldQueueAutoRefresh = false;
|
||||||
|
autoRefreshReasonBlockUpdate = null;
|
||||||
dateStringToday = '';
|
dateStringToday = '';
|
||||||
dateStringYesterday = '';
|
dateStringYesterday = '';
|
||||||
|
|
||||||
|
|
@ -148,6 +154,10 @@ export class AccountDetailsComponent implements OnInit, OnDestroy {
|
||||||
this.account.pendingFiat = this.util.nano.rawToMnano(this.account.pending || 0).times(this.price.price.lastPrice).toNumber();
|
this.account.pendingFiat = this.util.nano.rawToMnano(this.account.pending || 0).times(this.price.price.lastPrice).toNumber();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
this.wallet.wallet.pendingBlocksUpdate$.subscribe(async receivableBlockUpdate => {
|
||||||
|
this.onReceivableBlockUpdate(receivableBlockUpdate);
|
||||||
|
});
|
||||||
|
|
||||||
const UIkit = window['UIkit'];
|
const UIkit = window['UIkit'];
|
||||||
const qrModal = UIkit.modal('#qr-code-modal');
|
const qrModal = UIkit.modal('#qr-code-modal');
|
||||||
this.qrModal = qrModal;
|
this.qrModal = qrModal;
|
||||||
|
|
@ -159,6 +169,7 @@ export class AccountDetailsComponent implements OnInit, OnDestroy {
|
||||||
this.mobileTransactionMenuModal = mobileTransactionMenuModal;
|
this.mobileTransactionMenuModal = mobileTransactionMenuModal;
|
||||||
|
|
||||||
await this.loadAccountDetails();
|
await this.loadAccountDetails();
|
||||||
|
this.initialLoadDone = true;
|
||||||
this.addressBook.loadAddressBook();
|
this.addressBook.loadAddressBook();
|
||||||
|
|
||||||
this.populateRepresentativeList();
|
this.populateRepresentativeList();
|
||||||
|
|
@ -262,14 +273,180 @@ export class AccountDetailsComponent implements OnInit, OnDestroy {
|
||||||
this.repLabel = null;
|
this.repLabel = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
async loadAccountDetails(refresh= false) {
|
onRefreshButtonClick() {
|
||||||
if (refresh && !this.statsRefreshEnabled) return;
|
if (!this.manualRefreshAllowed) return;
|
||||||
this.statsRefreshEnabled = false;
|
|
||||||
|
|
||||||
if (this.timeoutIdAllowingRefresh != null) {
|
this.loadAccountDetails();
|
||||||
clearTimeout(this.timeoutIdAllowingRefresh);
|
}
|
||||||
|
|
||||||
|
isReceivableBlockUpdateRelevant(receivableBlockUpdate) {
|
||||||
|
let isRelevant = true;
|
||||||
|
|
||||||
|
if (receivableBlockUpdate.account !== this.accountID) {
|
||||||
|
isRelevant = false;
|
||||||
|
return isRelevant;
|
||||||
}
|
}
|
||||||
this.timeoutIdAllowingRefresh = setTimeout(() => this.statsRefreshEnabled = true, 5000);
|
|
||||||
|
const sourceHashToFind = receivableBlockUpdate.sourceHash;
|
||||||
|
|
||||||
|
const alreadyInReceivableBlocks =
|
||||||
|
this.pendingBlocks.some(
|
||||||
|
(knownReceivableBlock) =>
|
||||||
|
(knownReceivableBlock.hash === sourceHashToFind)
|
||||||
|
);
|
||||||
|
|
||||||
|
if (receivableBlockUpdate.hasBeenReceived === true) {
|
||||||
|
const destinationHashToFind = receivableBlockUpdate.destinationHash;
|
||||||
|
|
||||||
|
const alreadyInAccountHistory =
|
||||||
|
this.accountHistory.some(
|
||||||
|
(knownAccountHistoryBlock) =>
|
||||||
|
(knownAccountHistoryBlock.hash === destinationHashToFind)
|
||||||
|
);
|
||||||
|
|
||||||
|
if (
|
||||||
|
(alreadyInAccountHistory === true)
|
||||||
|
&& (alreadyInReceivableBlocks === false)
|
||||||
|
) {
|
||||||
|
isRelevant = false;
|
||||||
|
return isRelevant;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if (alreadyInReceivableBlocks === true) {
|
||||||
|
isRelevant = false;
|
||||||
|
return isRelevant;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return isRelevant;
|
||||||
|
}
|
||||||
|
|
||||||
|
onReceivableBlockUpdate(receivableBlockUpdate) {
|
||||||
|
if (receivableBlockUpdate === null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const isRelevantUpdate =
|
||||||
|
this.isReceivableBlockUpdateRelevant(receivableBlockUpdate);
|
||||||
|
|
||||||
|
if (isRelevantUpdate === false) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.loadAccountDetailsThrottled({ receivableBlockUpdate });
|
||||||
|
}
|
||||||
|
|
||||||
|
loadAccountDetailsThrottled(params) {
|
||||||
|
this.autoRefreshReasonBlockUpdate = (
|
||||||
|
(params.receivableBlockUpdate != null)
|
||||||
|
? params.receivableBlockUpdate
|
||||||
|
: null
|
||||||
|
);
|
||||||
|
|
||||||
|
if (this.initialLoadDone === false) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (this.instantAutoRefreshAllowed === true) {
|
||||||
|
this.loadAccountDetails();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (this.loadingAccountDetails === true) {
|
||||||
|
// Queue refresh once the loading is done
|
||||||
|
this.shouldQueueAutoRefresh = true;
|
||||||
|
} else {
|
||||||
|
// Queue refresh now
|
||||||
|
this.loadAccountDetailsDelayed(3000);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
enableManualRefreshDelayed(delayMS) {
|
||||||
|
if (this.timeoutIdAllowingManualRefresh != null) {
|
||||||
|
clearTimeout(this.timeoutIdAllowingManualRefresh);
|
||||||
|
}
|
||||||
|
|
||||||
|
this.timeoutIdAllowingManualRefresh =
|
||||||
|
setTimeout(
|
||||||
|
() => {
|
||||||
|
this.manualRefreshAllowed = true;
|
||||||
|
},
|
||||||
|
delayMS
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
enableInstantAutoRefreshDelayed(delayMS) {
|
||||||
|
if (this.timeoutIdAllowingInstantAutoRefresh != null) {
|
||||||
|
clearTimeout(this.timeoutIdAllowingInstantAutoRefresh);
|
||||||
|
}
|
||||||
|
|
||||||
|
this.timeoutIdAllowingInstantAutoRefresh =
|
||||||
|
setTimeout(
|
||||||
|
() => {
|
||||||
|
this.instantAutoRefreshAllowed = true;
|
||||||
|
},
|
||||||
|
delayMS
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
loadAccountDetailsDelayed(delayMS) {
|
||||||
|
if (this.timeoutIdQueuedAutoRefresh != null) {
|
||||||
|
clearTimeout(this.timeoutIdQueuedAutoRefresh);
|
||||||
|
}
|
||||||
|
|
||||||
|
this.timeoutIdQueuedAutoRefresh =
|
||||||
|
setTimeout(
|
||||||
|
() => {
|
||||||
|
if (this.autoRefreshReasonBlockUpdate !== null) {
|
||||||
|
const isUpdateStillRelevant =
|
||||||
|
this.isReceivableBlockUpdateRelevant(this.autoRefreshReasonBlockUpdate);
|
||||||
|
|
||||||
|
if (isUpdateStillRelevant === false) {
|
||||||
|
this.enableRefreshesEventually();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
this.loadAccountDetails();
|
||||||
|
},
|
||||||
|
delayMS
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
onAccountDetailsLoadStart() {
|
||||||
|
this.instantAutoRefreshAllowed = false;
|
||||||
|
this.manualRefreshAllowed = false;
|
||||||
|
|
||||||
|
if (this.timeoutIdAllowingManualRefresh != null) {
|
||||||
|
clearTimeout(this.timeoutIdAllowingManualRefresh);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (this.timeoutIdAllowingInstantAutoRefresh != null) {
|
||||||
|
clearTimeout(this.timeoutIdAllowingInstantAutoRefresh);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (this.timeoutIdQueuedAutoRefresh != null) {
|
||||||
|
clearTimeout(this.timeoutIdQueuedAutoRefresh);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
enableRefreshesEventually() {
|
||||||
|
this.enableInstantAutoRefreshDelayed(3000);
|
||||||
|
this.enableManualRefreshDelayed(5000);
|
||||||
|
}
|
||||||
|
|
||||||
|
onAccountDetailsLoadDone() {
|
||||||
|
if (this.shouldQueueAutoRefresh === true) {
|
||||||
|
this.shouldQueueAutoRefresh = false;
|
||||||
|
this.loadAccountDetailsDelayed(3000);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.enableRefreshesEventually();
|
||||||
|
}
|
||||||
|
|
||||||
|
async loadAccountDetails() {
|
||||||
|
this.onAccountDetailsLoadStart();
|
||||||
|
|
||||||
this.pendingBlocks = [];
|
this.pendingBlocks = [];
|
||||||
|
|
||||||
|
|
@ -288,11 +465,13 @@ export class AccountDetailsComponent implements OnInit, OnDestroy {
|
||||||
|
|
||||||
if (accountID !== this.accountID) {
|
if (accountID !== this.accountID) {
|
||||||
// Navigated to a different account while account info was loading
|
// Navigated to a different account while account info was loading
|
||||||
|
this.onAccountDetailsLoadDone();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!this.account) {
|
if (!this.account) {
|
||||||
this.loadingAccountDetails = false;
|
this.loadingAccountDetails = false;
|
||||||
|
this.onAccountDetailsLoadDone();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -316,6 +495,7 @@ export class AccountDetailsComponent implements OnInit, OnDestroy {
|
||||||
|
|
||||||
if (accountID !== this.accountID) {
|
if (accountID !== this.accountID) {
|
||||||
// Navigated to a different account while incoming tx were loading
|
// Navigated to a different account while incoming tx were loading
|
||||||
|
this.onAccountDetailsLoadDone();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -380,10 +560,12 @@ export class AccountDetailsComponent implements OnInit, OnDestroy {
|
||||||
|
|
||||||
if (accountID !== this.accountID) {
|
if (accountID !== this.accountID) {
|
||||||
// Navigated to a different account while account history was loading
|
// Navigated to a different account while account history was loading
|
||||||
|
this.onAccountDetailsLoadDone();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
this.loadingAccountDetails = false;
|
this.loadingAccountDetails = false;
|
||||||
|
this.onAccountDetailsLoadDone();
|
||||||
}
|
}
|
||||||
|
|
||||||
getAccountLabel(accountID, defaultLabel) {
|
getAccountLabel(accountID, defaultLabel) {
|
||||||
|
|
@ -458,9 +640,18 @@ export class AccountDetailsComponent implements OnInit, OnDestroy {
|
||||||
);
|
);
|
||||||
|
|
||||||
if (h.type === 'state') {
|
if (h.type === 'state') {
|
||||||
// For Open and receive blocks, we need to look up block info to get originating account
|
|
||||||
if (h.subtype === 'open' || h.subtype === 'receive') {
|
if (h.subtype === 'open' || h.subtype === 'receive') {
|
||||||
|
// Look up block info to get sender account
|
||||||
additionalBlocksInfo.push({ hash: h.hash, link: h.link });
|
additionalBlocksInfo.push({ hash: h.hash, link: h.link });
|
||||||
|
|
||||||
|
// Remove a receivable block if this is a receive for it
|
||||||
|
const sourceHashToFind = h.link;
|
||||||
|
|
||||||
|
this.pendingBlocks =
|
||||||
|
this.pendingBlocks.filter(
|
||||||
|
(knownReceivableBlock) =>
|
||||||
|
(knownReceivableBlock.hash !== sourceHashToFind)
|
||||||
|
);
|
||||||
} else if (h.subtype === 'change') {
|
} else if (h.subtype === 'change') {
|
||||||
h.link_as_account = h.representative;
|
h.link_as_account = h.representative;
|
||||||
h.addressBookName = (
|
h.addressBookName = (
|
||||||
|
|
@ -790,6 +981,8 @@ export class AccountDetailsComponent implements OnInit, OnDestroy {
|
||||||
receivableBlock.loading = false;
|
receivableBlock.loading = false;
|
||||||
|
|
||||||
await this.wallet.reloadBalances();
|
await this.wallet.reloadBalances();
|
||||||
|
|
||||||
|
this.loadAccountDetailsThrottled({});
|
||||||
}
|
}
|
||||||
|
|
||||||
async generateSend() {
|
async generateSend() {
|
||||||
|
|
|
||||||
|
|
@ -85,7 +85,11 @@ export class ReceiveComponent implements OnInit, OnDestroy {
|
||||||
this.selAccountInit = true;
|
this.selAccountInit = true;
|
||||||
});
|
});
|
||||||
|
|
||||||
this.walletService.wallet.pendingBlocksUpdate$.subscribe(async acc => {
|
this.walletService.wallet.pendingBlocksUpdate$.subscribe(async receivableBlockUpdate => {
|
||||||
|
if (receivableBlockUpdate === null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
this.updatePendingBlocks();
|
this.updatePendingBlocks();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -38,6 +38,13 @@ export interface Block {
|
||||||
source: string;
|
source: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export interface ReceivableBlockUpdate {
|
||||||
|
account: string;
|
||||||
|
sourceHash: string;
|
||||||
|
destinationHash: string|null;
|
||||||
|
hasBeenReceived: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
export interface FullWallet {
|
export interface FullWallet {
|
||||||
type: WalletType;
|
type: WalletType;
|
||||||
seedBytes: any;
|
seedBytes: any;
|
||||||
|
|
@ -58,7 +65,7 @@ export interface FullWallet {
|
||||||
locked: boolean;
|
locked: boolean;
|
||||||
password: string;
|
password: string;
|
||||||
pendingBlocks: Block[];
|
pendingBlocks: Block[];
|
||||||
pendingBlocksUpdate$: BehaviorSubject<boolean|false>;
|
pendingBlocksUpdate$: BehaviorSubject<ReceivableBlockUpdate|null>;
|
||||||
newWallet$: BehaviorSubject<boolean|false>;
|
newWallet$: BehaviorSubject<boolean|false>;
|
||||||
refresh$: BehaviorSubject<boolean|false>;
|
refresh$: BehaviorSubject<boolean|false>;
|
||||||
}
|
}
|
||||||
|
|
@ -106,7 +113,7 @@ export class WalletService {
|
||||||
locked: false,
|
locked: false,
|
||||||
password: '',
|
password: '',
|
||||||
pendingBlocks: [],
|
pendingBlocks: [],
|
||||||
pendingBlocksUpdate$: new BehaviorSubject(false),
|
pendingBlocksUpdate$: new BehaviorSubject(null),
|
||||||
newWallet$: new BehaviorSubject(false),
|
newWallet$: new BehaviorSubject(false),
|
||||||
refresh$: new BehaviorSubject(false),
|
refresh$: new BehaviorSubject(false),
|
||||||
};
|
};
|
||||||
|
|
@ -953,8 +960,13 @@ export class WalletService {
|
||||||
if (existingHash) return false; // Already added
|
if (existingHash) return false; // Already added
|
||||||
|
|
||||||
this.wallet.pendingBlocks.push({ account: accountID, hash: blockHash, amount: amount, source: source });
|
this.wallet.pendingBlocks.push({ account: accountID, hash: blockHash, amount: amount, source: source });
|
||||||
this.wallet.pendingBlocksUpdate$.next(true);
|
this.wallet.pendingBlocksUpdate$.next({
|
||||||
this.wallet.pendingBlocksUpdate$.next(false);
|
account: accountID,
|
||||||
|
sourceHash: blockHash,
|
||||||
|
destinationHash: null,
|
||||||
|
hasBeenReceived: false,
|
||||||
|
});
|
||||||
|
this.wallet.pendingBlocksUpdate$.next(null);
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -1008,8 +1020,13 @@ export class WalletService {
|
||||||
// list also updated with reloadBalances but not if called too fast
|
// list also updated with reloadBalances but not if called too fast
|
||||||
this.removePendingBlock(nextBlock.hash);
|
this.removePendingBlock(nextBlock.hash);
|
||||||
await this.reloadBalances();
|
await this.reloadBalances();
|
||||||
this.wallet.pendingBlocksUpdate$.next(true);
|
this.wallet.pendingBlocksUpdate$.next({
|
||||||
this.wallet.pendingBlocksUpdate$.next(false);
|
account: nextBlock.account,
|
||||||
|
sourceHash: nextBlock.hash,
|
||||||
|
destinationHash: newHash,
|
||||||
|
hasBeenReceived: true,
|
||||||
|
});
|
||||||
|
this.wallet.pendingBlocksUpdate$.next(null);
|
||||||
} else {
|
} else {
|
||||||
if (this.isLedgerWallet()) {
|
if (this.isLedgerWallet()) {
|
||||||
this.processingPending = false;
|
this.processingPending = false;
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue