From b5a4bcb247ec94ee99ca83c068d660574a36a0d7 Mon Sep 17 00:00:00 2001 From: Florian Dold Date: Tue, 12 Dec 2017 16:39:55 +0100 Subject: [PATCH] show notification dot when balance changes (#5214) --- src/wallet.ts | 24 +++++++++++++++++++++++- src/webex/chromeBadge.ts | 37 +++++++++++++++++++++++++++++++++++++ src/webex/messages.ts | 4 ++++ src/webex/pages/popup.tsx | 3 +++ src/webex/wxApi.ts | 4 ++++ src/webex/wxBackend.ts | 20 ++++++++++++++++++++ 6 files changed, 91 insertions(+), 1 deletion(-) diff --git a/src/wallet.ts b/src/wallet.ts index 5b658de85..9e8eb3f3e 100644 --- a/src/wallet.ts +++ b/src/wallet.ts @@ -247,6 +247,16 @@ export interface Badge { * Stop indicating background activity. */ stopBusy(): void; + + /** + * Show the notification in the badge. + */ + showNotification(): void; + + /** + * Stop showing the notification. + */ + clearNotification(): void; } @@ -982,7 +992,7 @@ export class Wallet { .put(Stores.purchases, t) .putAll(Stores.coins, payCoinInfo.map((pci) => pci.updatedCoin)) .finish(); - + this.badge.showNotification(); this.notifier.notify(); } @@ -1198,7 +1208,11 @@ export class Wallet { return c; } await this.q().mutate(Stores.coins, coin.coinPub, mutateCoin) + // Show notifications only for accepted tips + this.badge.showNotification(); } + } else { + this.badge.showNotification(); } this.notifier.notify(); @@ -1720,6 +1734,7 @@ export class Wallet { console.log("suspending coin", c); c.suspended = true; q.put(Stores.coins, c); + this.badge.showNotification(); this.notifier.notify(); }); await q.finish(); @@ -2673,6 +2688,7 @@ export class Wallet { .put(Stores.coinsReturns, coinsReturnRecord) .putAll(Stores.coins, payCoinInfo.map((pci) => pci.updatedCoin)) .finish(); + this.badge.showNotification(); this.notifier.notify(); this.depositReturnedCoins(coinsReturnRecord); @@ -2835,6 +2851,7 @@ export class Wallet { this.refresh(perm.coin_pub); } + this.badge.showNotification(); this.notifier.notify(); } @@ -2968,6 +2985,7 @@ export class Wallet { } await q.finish(); + this.badge.showNotification(); this.notifier.notify(); } @@ -3047,4 +3065,8 @@ export class Wallet { // FIXME(#5210) also GC coins } + + clearNotification(): void { + this.badge.clearNotification(); + } } diff --git a/src/webex/chromeBadge.ts b/src/webex/chromeBadge.ts index 13add9b3f..3dfe94518 100644 --- a/src/webex/chromeBadge.ts +++ b/src/webex/chromeBadge.ts @@ -62,6 +62,11 @@ export class ChromeBadge implements Badge { */ private gapWidth: number = 0; + /** + * Should we show the notification dot? + */ + private hasNotification = false; + /** * Maximum value for our rotationAngle, corresponds to 2 Pi. */ @@ -150,6 +155,21 @@ export class ChromeBadge implements Badge { // go back to the origin this.ctx.translate(-this.canvas.width / 2, -this.canvas.height / 2); + if (this.hasNotification) { + // We draw a circle with a soft border in the + // lower right corner. + const r = 8; + const cw = this.canvas.width; + const ch = this.canvas.height; + this.ctx.beginPath(); + this.ctx.arc(cw - r, ch - r, r, 0, 2 * Math.PI, false); + const gradient = this.ctx.createRadialGradient(cw - r, ch - r, r, cw - r, ch - r, 5); + gradient.addColorStop(0, "rgba(255, 255, 255, 1)"); + gradient.addColorStop(1, "blue"); + this.ctx.fillStyle = gradient; + this.ctx.fill(); + } + // Allow running outside the extension for testing // tslint:disable-next-line:no-string-literal if (window["chrome"] && window.chrome["browserAction"]) { @@ -211,6 +231,23 @@ export class ChromeBadge implements Badge { rAF(step); } + /** + * Draw the badge such that it shows the + * user that something happened (balance changed). + */ + showNotification() { + this.hasNotification = true; + this.draw(); + } + + /** + * Draw the badge without the notification mark. + */ + clearNotification() { + this.hasNotification = false; + this.draw(); + } + startBusy() { if (this.isBusy) { return; diff --git a/src/webex/messages.ts b/src/webex/messages.ts index 7cc6c4259..44c9f166c 100644 --- a/src/webex/messages.ts +++ b/src/webex/messages.ts @@ -208,6 +208,10 @@ export interface MessageMap { request: types.TipStatusRequest; response: void; }; + "clear-notification": { + request: { }; + response: void; + }; } /** diff --git a/src/webex/pages/popup.tsx b/src/webex/pages/popup.tsx index 389be3b5c..6d1ff3b2b 100644 --- a/src/webex/pages/popup.tsx +++ b/src/webex/pages/popup.tsx @@ -566,4 +566,7 @@ const el = ( document.addEventListener("DOMContentLoaded", () => { ReactDOM.render(el, document.getElementById("content")!); + // Will be used by the backend to detect when the popup gets closed, + // so we can clear notifications + chrome.runtime.connect({name: "popup"}); }); diff --git a/src/webex/wxApi.ts b/src/webex/wxApi.ts index 61a45c024..2575eec90 100644 --- a/src/webex/wxApi.ts +++ b/src/webex/wxApi.ts @@ -381,3 +381,7 @@ export function acceptTip(merchantDomain: string, tipId: string): Promise { return callBackend("process-tip-response", { merchantDomain, tipId, tipResponse }); } + +export function clearNotification(): Promise { + return callBackend("clear-notification", { }); +} diff --git a/src/webex/wxBackend.ts b/src/webex/wxBackend.ts index a7757c68e..a804c73d1 100644 --- a/src/webex/wxBackend.ts +++ b/src/webex/wxBackend.ts @@ -337,6 +337,9 @@ function handleMessage(sender: MessageSender, const req = GetTipPlanchetsRequest.checked(detail); return needsWallet().getTipPlanchets(req.merchantDomain, req.tipId, req.amount, req.deadline, req.exchangeUrl, req.nextUrl); } + case "clear-notification": { + return needsWallet().clearNotification(); + } default: // Exhaustiveness check. // See https://www.typescriptlang.org/docs/handbook/advanced-types.html @@ -698,6 +701,23 @@ export async function wxMain() { }); + + // Clear notifications both when the popop opens, + // as well when it closes. + chrome.runtime.onConnect.addListener((port) => { + if (port.name == "popup") { + if (currentWallet) { + currentWallet.clearNotification(); + } + port.onDisconnect.addListener(() => { + if (currentWallet) { + currentWallet.clearNotification(); + } + }); + } + }); + + // Handlers for catching HTTP requests chrome.webRequest.onHeadersReceived.addListener((details) => { const wallet = currentWallet;