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>
|
||||
<a
|
||||
class="icon-transactions-refresh"
|
||||
[class.disabled]="!statsRefreshEnabled"
|
||||
[class.uk-text-muted]="!statsRefreshEnabled"
|
||||
[class.disabled]="!manualRefreshAllowed"
|
||||
[class.uk-text-muted]="!manualRefreshAllowed"
|
||||
uk-icon="icon: refresh;"
|
||||
[title]="'general.reload' | transloco"
|
||||
uk-tooltip
|
||||
(click)="loadAccountDetails(true)"
|
||||
(click)="onRefreshButtonClick()"
|
||||
></a>
|
||||
</ng-template>
|
||||
</li>
|
||||
|
|
|
|||
|
|
@ -42,7 +42,9 @@ export class AccountDetailsComponent implements OnInit, OnDestroy {
|
|||
|
||||
walletAccount = null;
|
||||
|
||||
timeoutIdAllowingRefresh: any = null;
|
||||
timeoutIdAllowingManualRefresh: any = null;
|
||||
timeoutIdAllowingInstantAutoRefresh: any = null;
|
||||
timeoutIdQueuedAutoRefresh: any = null;
|
||||
qrModal: any = null;
|
||||
mobileAccountMenuModal: any = null;
|
||||
mobileTransactionMenuModal: any = null;
|
||||
|
|
@ -66,7 +68,11 @@ export class AccountDetailsComponent implements OnInit, OnDestroy {
|
|||
routerSub = null;
|
||||
priceSub = null;
|
||||
|
||||
statsRefreshEnabled = true;
|
||||
initialLoadDone = false;
|
||||
manualRefreshAllowed = true;
|
||||
instantAutoRefreshAllowed = true;
|
||||
shouldQueueAutoRefresh = false;
|
||||
autoRefreshReasonBlockUpdate = null;
|
||||
dateStringToday = '';
|
||||
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.wallet.wallet.pendingBlocksUpdate$.subscribe(async receivableBlockUpdate => {
|
||||
this.onReceivableBlockUpdate(receivableBlockUpdate);
|
||||
});
|
||||
|
||||
const UIkit = window['UIkit'];
|
||||
const qrModal = UIkit.modal('#qr-code-modal');
|
||||
this.qrModal = qrModal;
|
||||
|
|
@ -159,6 +169,7 @@ export class AccountDetailsComponent implements OnInit, OnDestroy {
|
|||
this.mobileTransactionMenuModal = mobileTransactionMenuModal;
|
||||
|
||||
await this.loadAccountDetails();
|
||||
this.initialLoadDone = true;
|
||||
this.addressBook.loadAddressBook();
|
||||
|
||||
this.populateRepresentativeList();
|
||||
|
|
@ -262,14 +273,180 @@ export class AccountDetailsComponent implements OnInit, OnDestroy {
|
|||
this.repLabel = null;
|
||||
}
|
||||
|
||||
async loadAccountDetails(refresh= false) {
|
||||
if (refresh && !this.statsRefreshEnabled) return;
|
||||
this.statsRefreshEnabled = false;
|
||||
onRefreshButtonClick() {
|
||||
if (!this.manualRefreshAllowed) return;
|
||||
|
||||
if (this.timeoutIdAllowingRefresh != null) {
|
||||
clearTimeout(this.timeoutIdAllowingRefresh);
|
||||
this.loadAccountDetails();
|
||||
}
|
||||
|
||||
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 = [];
|
||||
|
||||
|
|
@ -288,11 +465,13 @@ export class AccountDetailsComponent implements OnInit, OnDestroy {
|
|||
|
||||
if (accountID !== this.accountID) {
|
||||
// Navigated to a different account while account info was loading
|
||||
this.onAccountDetailsLoadDone();
|
||||
return;
|
||||
}
|
||||
|
||||
if (!this.account) {
|
||||
this.loadingAccountDetails = false;
|
||||
this.onAccountDetailsLoadDone();
|
||||
return;
|
||||
}
|
||||
|
||||
|
|
@ -316,6 +495,7 @@ export class AccountDetailsComponent implements OnInit, OnDestroy {
|
|||
|
||||
if (accountID !== this.accountID) {
|
||||
// Navigated to a different account while incoming tx were loading
|
||||
this.onAccountDetailsLoadDone();
|
||||
return;
|
||||
}
|
||||
|
||||
|
|
@ -380,10 +560,12 @@ export class AccountDetailsComponent implements OnInit, OnDestroy {
|
|||
|
||||
if (accountID !== this.accountID) {
|
||||
// Navigated to a different account while account history was loading
|
||||
this.onAccountDetailsLoadDone();
|
||||
return;
|
||||
}
|
||||
|
||||
this.loadingAccountDetails = false;
|
||||
this.onAccountDetailsLoadDone();
|
||||
}
|
||||
|
||||
getAccountLabel(accountID, defaultLabel) {
|
||||
|
|
@ -458,9 +640,18 @@ export class AccountDetailsComponent implements OnInit, OnDestroy {
|
|||
);
|
||||
|
||||
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') {
|
||||
// Look up block info to get sender account
|
||||
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') {
|
||||
h.link_as_account = h.representative;
|
||||
h.addressBookName = (
|
||||
|
|
@ -790,6 +981,8 @@ export class AccountDetailsComponent implements OnInit, OnDestroy {
|
|||
receivableBlock.loading = false;
|
||||
|
||||
await this.wallet.reloadBalances();
|
||||
|
||||
this.loadAccountDetailsThrottled({});
|
||||
}
|
||||
|
||||
async generateSend() {
|
||||
|
|
|
|||
|
|
@ -85,7 +85,11 @@ export class ReceiveComponent implements OnInit, OnDestroy {
|
|||
this.selAccountInit = true;
|
||||
});
|
||||
|
||||
this.walletService.wallet.pendingBlocksUpdate$.subscribe(async acc => {
|
||||
this.walletService.wallet.pendingBlocksUpdate$.subscribe(async receivableBlockUpdate => {
|
||||
if (receivableBlockUpdate === null) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.updatePendingBlocks();
|
||||
});
|
||||
|
||||
|
|
|
|||
|
|
@ -38,6 +38,13 @@ export interface Block {
|
|||
source: string;
|
||||
}
|
||||
|
||||
export interface ReceivableBlockUpdate {
|
||||
account: string;
|
||||
sourceHash: string;
|
||||
destinationHash: string|null;
|
||||
hasBeenReceived: boolean;
|
||||
}
|
||||
|
||||
export interface FullWallet {
|
||||
type: WalletType;
|
||||
seedBytes: any;
|
||||
|
|
@ -58,7 +65,7 @@ export interface FullWallet {
|
|||
locked: boolean;
|
||||
password: string;
|
||||
pendingBlocks: Block[];
|
||||
pendingBlocksUpdate$: BehaviorSubject<boolean|false>;
|
||||
pendingBlocksUpdate$: BehaviorSubject<ReceivableBlockUpdate|null>;
|
||||
newWallet$: BehaviorSubject<boolean|false>;
|
||||
refresh$: BehaviorSubject<boolean|false>;
|
||||
}
|
||||
|
|
@ -106,7 +113,7 @@ export class WalletService {
|
|||
locked: false,
|
||||
password: '',
|
||||
pendingBlocks: [],
|
||||
pendingBlocksUpdate$: new BehaviorSubject(false),
|
||||
pendingBlocksUpdate$: new BehaviorSubject(null),
|
||||
newWallet$: new BehaviorSubject(false),
|
||||
refresh$: new BehaviorSubject(false),
|
||||
};
|
||||
|
|
@ -953,8 +960,13 @@ export class WalletService {
|
|||
if (existingHash) return false; // Already added
|
||||
|
||||
this.wallet.pendingBlocks.push({ account: accountID, hash: blockHash, amount: amount, source: source });
|
||||
this.wallet.pendingBlocksUpdate$.next(true);
|
||||
this.wallet.pendingBlocksUpdate$.next(false);
|
||||
this.wallet.pendingBlocksUpdate$.next({
|
||||
account: accountID,
|
||||
sourceHash: blockHash,
|
||||
destinationHash: null,
|
||||
hasBeenReceived: false,
|
||||
});
|
||||
this.wallet.pendingBlocksUpdate$.next(null);
|
||||
return true;
|
||||
}
|
||||
|
||||
|
|
@ -1008,8 +1020,13 @@ export class WalletService {
|
|||
// list also updated with reloadBalances but not if called too fast
|
||||
this.removePendingBlock(nextBlock.hash);
|
||||
await this.reloadBalances();
|
||||
this.wallet.pendingBlocksUpdate$.next(true);
|
||||
this.wallet.pendingBlocksUpdate$.next(false);
|
||||
this.wallet.pendingBlocksUpdate$.next({
|
||||
account: nextBlock.account,
|
||||
sourceHash: nextBlock.hash,
|
||||
destinationHash: newHash,
|
||||
hasBeenReceived: true,
|
||||
});
|
||||
this.wallet.pendingBlocksUpdate$.next(null);
|
||||
} else {
|
||||
if (this.isLedgerWallet()) {
|
||||
this.processingPending = false;
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue