From 448be8847ff82b62232b2521a3ce035949d8f32b Mon Sep 17 00:00:00 2001
From: Zach Fox <fox@highfidelity.io>
Date: Wed, 31 Oct 2018 13:23:58 -0700
Subject: [PATCH] Notifications are working!

---
 .../qml/hifi/commerce/purchases/Purchases.qml |   1 -
 .../qml/hifi/commerce/wallet/Wallet.qml       |  26 +++-
 scripts/modules/appUi.js                      | 144 +++++++++++-------
 scripts/modules/request.js                    |   5 +-
 scripts/system/commerce/wallet.js             |  46 +++---
 scripts/system/pal.js                         |  16 +-
 scripts/system/tablet-goto.js                 |  16 +-
 7 files changed, 155 insertions(+), 99 deletions(-)

diff --git a/interface/resources/qml/hifi/commerce/purchases/Purchases.qml b/interface/resources/qml/hifi/commerce/purchases/Purchases.qml
index eeb98eeb8c..3f77a17ac0 100644
--- a/interface/resources/qml/hifi/commerce/purchases/Purchases.qml
+++ b/interface/resources/qml/hifi/commerce/purchases/Purchases.qml
@@ -91,7 +91,6 @@ Rectangle {
             if (result.status !== 'success') {
                 console.log("Failed to get Available Updates", result.data.message);
             } else {
-                sendToScript({method: 'purchases_availableUpdatesReceived', numUpdates: result.data.updates.length });
                 root.numUpdatesAvailable = result.total_entries;
             }
         }
diff --git a/interface/resources/qml/hifi/commerce/wallet/Wallet.qml b/interface/resources/qml/hifi/commerce/wallet/Wallet.qml
index a958e62aad..e1e58bf7c7 100644
--- a/interface/resources/qml/hifi/commerce/wallet/Wallet.qml
+++ b/interface/resources/qml/hifi/commerce/wallet/Wallet.qml
@@ -99,6 +99,13 @@ Rectangle {
         }
     }
 
+    onActiveViewChanged: {
+        if (activeView === "walletHome") {
+            walletHomeButtonContainer.messagesWaiting = false;
+            sendToScript({method: 'clearShouldShowDotHistory'});
+        }
+    }
+
     HifiCommerceCommon.CommerceLightbox {
         id: lightboxPopup;
         visible: false;
@@ -494,6 +501,7 @@ Rectangle {
         // "WALLET HOME" tab button
         Rectangle {
             id: walletHomeButtonContainer;
+            property bool messagesWaiting: false;
             visible: !walletSetup.visible;
             color: root.activeView === "walletHome" ? hifi.colors.blueAccent : hifi.colors.black;
             anchors.top: parent.top;
@@ -514,6 +522,19 @@ Rectangle {
                 color: WalletScriptingInterface.limitedCommerce ? hifi.colors.lightGray50 : ((root.activeView === "walletHome" || walletHomeTabMouseArea.containsMouse) ? hifi.colors.white : hifi.colors.blueHighlight);
             }
 
+            Rectangle {
+                id: recentActivityMessagesWaitingLight;
+                visible: parent.messagesWaiting;
+                anchors.right: homeTabIcon.left;
+                anchors.rightMargin: -4;
+                anchors.top: homeTabIcon.top;
+                anchors.topMargin: 16;
+                height: 10;
+                width: height;
+                radius: height/2;
+                color: "red";
+            }
+
             RalewaySemiBold {
                 text: "RECENT ACTIVITY";
                 // Text size
@@ -572,7 +593,7 @@ Rectangle {
             }
 
             Rectangle {
-                id: messagesWaitingLight;
+                id: exchangeMoneyMessagesWaitingLight;
                 visible: parent.messagesWaiting;
                 anchors.right: exchangeMoneyTabIcon.left;
                 anchors.rightMargin: -4;
@@ -889,6 +910,9 @@ Rectangle {
             case 'updateWearables':
                 walletInventory.fromScript(message);
             break;
+            case 'updateRecentActivityMessageLight':
+                walletHomeButtonContainer.messagesWaiting = message.messagesWaiting;
+            break;
             default:
                 console.log('Unrecognized message from wallet.js:', JSON.stringify(message));
         }
diff --git a/scripts/modules/appUi.js b/scripts/modules/appUi.js
index efb842a9bb..c340dfecd2 100644
--- a/scripts/modules/appUi.js
+++ b/scripts/modules/appUi.js
@@ -95,16 +95,16 @@ function AppUi(properties) {
             activeIcon: isWaiting ? that.activeMessagesButton : that.activeButton
         });
     };
-    that.notificationPollTimeout = false;
-    that.notificationPollTimeoutMs = 60000;
-    that.notificationPollEndpoint = false;
-    that.notificationPollStopPaginatingConditionMet = false;
+    that.notificationPollTimeout = [false];
+    that.notificationPollTimeoutMs = [60000];
+    that.notificationPollEndpoint = [false];
+    that.notificationPollStopPaginatingConditionMet = [false];
     that.notificationDataProcessPage = function (data) {
         return data;
     };
-    that.notificationPollCallback = that.ignore;
-    that.notificationPollCaresAboutSince = false;
-    that.notificationInitialCallbackMade = false;
+    that.notificationPollCallback = [that.ignore];
+    that.notificationPollCaresAboutSince = [false];
+    that.notificationInitialCallbackMade = [false];
     that.notificationDisplayBanner = function (message) {
         if (!that.isOpen) {
             Window.displayAnnouncement(message);
@@ -149,73 +149,105 @@ function AppUi(properties) {
     //
     // START Notification Handling
     //
+
+    var currentDataPageToRetrieve = [];
+    var concatenatedServerResponse = [];
+    for (var i = 0; i < that.notificationPollEndpoint.length; i++) {
+        currentDataPageToRetrieve[i] = 1;
+        concatenatedServerResponse[i] = new Array();
+    }
+
+    function requestCallback(error, response, optionalParams) {
+        var indexOfRequest = optionalParams.indexOfRequest;
+        var urlOfRequest = optionalParams.urlOfRequest;
+
+        if (error || (response.status !== 'success')) {
+            print("Error: unable to get", urlOfRequest, error || response.status);
+            that.notificationPollTimeout[indexOfRequest] = Script.setTimeout(
+                that.notificationPoll(indexOfRequest), that.notificationPollTimeoutMs[indexOfRequest]);
+            return;
+        }
+
+        if (!that.notificationPollStopPaginatingConditionMet[indexOfRequest] ||
+            that.notificationPollStopPaginatingConditionMet[indexOfRequest](response)) {
+            that.notificationPollTimeout[indexOfRequest] = Script.setTimeout(function () {
+                that.notificationPoll(indexOfRequest);
+            }, that.notificationPollTimeoutMs[indexOfRequest]);
+
+            var notificationData;
+            if (concatenatedServerResponse[indexOfRequest].length) {
+                notificationData = concatenatedServerResponse[indexOfRequest];
+            } else {
+                notificationData = that.notificationDataProcessPage[indexOfRequest](response);
+            }
+            console.debug(that.buttonName, that.notificationPollEndpoint[indexOfRequest],
+                'notification data for processing:', JSON.stringify(notificationData));
+            that.notificationPollCallback[indexOfRequest](notificationData);
+            that.notificationInitialCallbackMade[indexOfRequest] = true;
+            currentDataPageToRetrieve[indexOfRequest] = 1;
+            concatenatedServerResponse[indexOfRequest] = new Array();
+        } else {
+            concatenatedServerResponse[indexOfRequest] =
+                concatenatedServerResponse[indexOfRequest].concat(that.notificationDataProcessPage[indexOfRequest](response));
+            currentDataPageToRetrieve[indexOfRequest]++;
+            request({
+                json: true,
+                uri: (urlOfRequest + "&page=" + currentDataPageToRetrieve[indexOfRequest])
+            }, requestCallback, optionalParams);
+        }
+    }
+
+
     var METAVERSE_BASE = Account.metaverseServerURL;
-    var currentDataPageToRetrieve = 1;
-    var concatenatedServerResponse = new Array();
-    that.notificationPoll = function () {
-        if (!that.notificationPollEndpoint) {
+    that.notificationPoll = function (i) {
+        if (!that.notificationPollEndpoint[i]) {
             return;
         }
 
         // User is "appearing offline" or is offline
         if (GlobalServices.findableBy === "none" || Account.username === "") {
-            that.notificationPollTimeout = Script.setTimeout(that.notificationPoll, that.notificationPollTimeoutMs);
+            that.notificationPollTimeout[i] = Script.setTimeout(
+                that.notificationPoll(i), that.notificationPollTimeoutMs[i]);
             return;
         }
 
-        var url = METAVERSE_BASE + that.notificationPollEndpoint;
+        var url = METAVERSE_BASE + that.notificationPollEndpoint[i];
 
-        var settingsKey = "notifications/" + that.buttonName + "/lastPoll";
+        var settingsKey = "notifications/" + that.notificationPollEndpoint[i] + "/lastPoll";
         var currentTimestamp = new Date().getTime();
         var lastPollTimestamp = Settings.getValue(settingsKey, currentTimestamp);
-        if (that.notificationPollCaresAboutSince) {
-            url = url + "&since=" + lastPollTimestamp/1000;
+        if (that.notificationPollCaresAboutSince[i]) {
+            url = url + "&since=" + lastPollTimestamp / 1000;
         }
         Settings.setValue(settingsKey, currentTimestamp);
 
         console.debug(that.buttonName, 'polling for notifications at endpoint', url);
 
-        function requestCallback(error, response) {
-            if (error || (response.status !== 'success')) {
-                print("Error: unable to get", url, error || response.status);
-                that.notificationPollTimeout = Script.setTimeout(that.notificationPoll, that.notificationPollTimeoutMs);
-                return;
-            }
-
-            if (!that.notificationPollStopPaginatingConditionMet || that.notificationPollStopPaginatingConditionMet(response)) {
-                that.notificationPollTimeout = Script.setTimeout(that.notificationPoll, that.notificationPollTimeoutMs);
-
-                var notificationData;
-                if (concatenatedServerResponse.length) {
-                    notificationData = concatenatedServerResponse;
-                } else {
-                    notificationData = that.notificationDataProcessPage(response);
-                }
-                console.debug(that.buttonName, 'notification data for processing:', JSON.stringify(notificationData));
-                that.notificationPollCallback(notificationData);
-                that.notificationInitialCallbackMade = true;
-                currentDataPageToRetrieve = 1;
-                concatenatedServerResponse = new Array();
-            } else {
-                concatenatedServerResponse = concatenatedServerResponse.concat(that.notificationDataProcessPage(response));
-                currentDataPageToRetrieve++;
-                request({ json: true, uri: (url + "&page=" + currentDataPageToRetrieve) }, requestCallback);
-            }
-        }
-
-        request({ json: true, uri: url }, requestCallback);
+        request({
+            json: true,
+            uri: url
+        },
+        requestCallback,
+        {
+            indexOfRequest: i,
+            urlOfRequest: url
+        });
     };
 
     // This won't do anything if there isn't a notification endpoint set
-    that.notificationPoll();
+    for (i = 0; i < that.notificationPollEndpoint.length; i++) {
+        that.notificationPoll(i);
+    }
 
     function restartNotificationPoll() {
-        that.notificationInitialCallbackMade = false;
-        if (that.notificationPollTimeout) {
-            Script.clearTimeout(that.notificationPollTimeout);
-            that.notificationPollTimeout = false;
+        for (var j = 0; j < that.notificationPollEndpoint.length; j++) {
+            that.notificationInitialCallbackMade[j] = false;
+            if (that.notificationPollTimeout[j]) {
+                Script.clearTimeout(that.notificationPollTimeout[j]);
+                that.notificationPollTimeout[j] = false;
+            }
+            that.notificationPoll(j);
         }
-        that.notificationPoll();
     }
     //
     // END Notification Handling
@@ -322,9 +354,11 @@ function AppUi(properties) {
             }
             that.tablet.removeButton(that.button);
         }
-        if (that.notificationPollTimeout) {
-            Script.clearInterval(that.notificationPollTimeout);
-            that.notificationPollTimeout = false;
+        for (var i = 0; i < that.notificationPollTimeout.length; i++) {
+            if (that.notificationPollTimeout[i]) {
+                Script.clearInterval(that.notificationPollTimeout[i]);
+                that.notificationPollTimeout[i] = false;
+            }
         }
     };
     // Set up the handlers.
@@ -333,7 +367,7 @@ function AppUi(properties) {
     Script.scriptEnding.connect(that.onScriptEnding);
     GlobalServices.findableByChanged.connect(restartNotificationPoll);
     GlobalServices.myUsernameChanged.connect(restartNotificationPoll);
-    if (that.buttonName == Settings.getValue("startUpApp")) {
+    if (that.buttonName === Settings.getValue("startUpApp")) {
         Settings.setValue("startUpApp", "");
         Script.setTimeout(function () {
             that.open();
diff --git a/scripts/modules/request.js b/scripts/modules/request.js
index d0037f9b43..37f3ac0d7b 100644
--- a/scripts/modules/request.js
+++ b/scripts/modules/request.js
@@ -18,7 +18,8 @@
 module.exports = {
 
     // ------------------------------------------------------------------
-    request: function (options, callback) { // cb(error, responseOfCorrectContentType) of url. A subset of npm request.
+    // cb(error, responseOfCorrectContentType, optionalCallbackParameter) of url. A subset of npm request.
+    request: function (options, callback, optionalCallbackParameter) {
         var httpRequest = new XMLHttpRequest(), key;
         // QT bug: apparently doesn't handle onload. Workaround using readyState.
         httpRequest.onreadystatechange = function () {
@@ -38,7 +39,7 @@ module.exports = {
                 if (error) {
                     response = { statusCode: httpRequest.status };
                 }
-                callback(error, response);
+                callback(error, response, optionalCallbackParameter);
             }
         };
         if (typeof options === 'string') {
diff --git a/scripts/system/commerce/wallet.js b/scripts/system/commerce/wallet.js
index 353145035e..5396014866 100644
--- a/scripts/system/commerce/wallet.js
+++ b/scripts/system/commerce/wallet.js
@@ -506,10 +506,6 @@ function fromQml(message) {
 
         ui.tablet.sendToQml({ method: 'updateWearables', wornWearables: currentlyWornWearables });
         break;
-    case 'purchases_availableUpdatesReceived':
-        shouldShowDot = message.numUpdates > 0;
-        ui.messagesWaiting(shouldShowDot && !ui.isOpen);
-        break;
     case 'purchases_walletNotSetUp':
         ui.tablet.sendToQml({
             method: 'updateWalletReferrer',
@@ -525,6 +521,10 @@ function fromQml(message) {
             openMarketplace(itemId);
         }
         break;
+    case 'clearShouldShowDotHistory':
+        shouldShowDotHistory = false;
+        ui.messagesWaiting(shouldShowDotUpdates || shouldShowDotHistory);
+        break;
     case 'http.request':
         // Handled elsewhere, don't log.
         break;
@@ -541,8 +541,13 @@ function walletOpened() {
     triggerMapping.enable();
     triggerPressMapping.enable();
     isWired = true;
-    shouldShowDot = false;
-    ui.messagesWaiting(shouldShowDot);
+
+    if (shouldShowDotHistory) {
+        ui.sendMessage({
+            method: 'updateRecentActivityMessageLight',
+            messagesWaiting: shouldShowDotHistory
+        });
+    }
 }
 
 function walletClosed() {
@@ -557,20 +562,20 @@ function notificationDataProcessPageHistory(data) {
     return data.data.history;
 }
 
-var shouldShowDot = false;
+var shouldShowDotUpdates = false;
 function notificationPollCallbackUpdates(updatesArray) {
-    shouldShowDot = shouldShowDot || updatesArray.length > 0;
-    ui.messagesWaiting(shouldShowDot && !ui.isOpen);
+    shouldShowDotUpdates = shouldShowDotUpdates || updatesArray.length > 0;
+    ui.messagesWaiting(shouldShowDotUpdates || shouldShowDotHistory);
 
     if (updatesArray.length > 0) {
         var message;
-        if (!ui.notificationInitialCallbackMade) {
+        if (!ui.notificationInitialCallbackMade[0]) {
             message = updatesArray.length + " of your purchased items " +
                 (updatesArray.length === 1 ? "has an update " : "have updates ") +
                 "available. Open WALLET to update.";
             ui.notificationDisplayBanner(message);
 
-            ui.notificationPollCaresAboutSince = true;
+            ui.notificationPollCaresAboutSince[0] = true;
         } else {
             for (var i = 0; i < updatesArray.length; i++) {
                 message = "Update available for \"" +
@@ -581,15 +586,16 @@ function notificationPollCallbackUpdates(updatesArray) {
         }
     }
 }
+var shouldShowDotHistory = false;
 function notificationPollCallbackHistory(historyArray) {
     if (!ui.isOpen) {
         var notificationCount = historyArray.length;
-        shouldShowDot = shouldShowDot || notificationCount > 0;
-        ui.messagesWaiting(shouldShowDot);
+        shouldShowDotHistory = shouldShowDotHistory || notificationCount > 0;
+        ui.messagesWaiting(shouldShowDotUpdates || shouldShowDotHistory);
 
         if (notificationCount > 0) {
             var message;
-            if (!ui.notificationInitialCallbackMade) {
+            if (!ui.notificationInitialCallbackMade[1]) {
                 message = "You have " + notificationCount + " unread wallet " +
                     "transaction" + (notificationCount === 1 ? "" : "s") + ". Open WALLET to see all activity.";
                 ui.notificationDisplayBanner(message);
@@ -605,8 +611,8 @@ function notificationPollCallbackHistory(historyArray) {
 }
 
 function isReturnedDataEmptyUpdates(data) {
-    var historyArray = data.data.history;
-    return historyArray.length === 0;
+    var updatesArray = data.data.updates;
+    return updatesArray.length === 0;
 }
 
 function isReturnedDataEmptyHistory(data) {
@@ -657,20 +663,12 @@ function startup() {
         onOpened: walletOpened,
         onClosed: walletClosed,
         onMessage: fromQml,
-/* Gotta re-add all this stuff once I get it working
         notificationPollEndpoint: ["/api/v1/commerce/available_updates?per_page=10", "/api/v1/commerce/history?per_page=10"],
         notificationPollTimeoutMs: [NOTIFICATION_POLL_TIMEOUT, NOTIFICATION_POLL_TIMEOUT],
         notificationDataProcessPage: [notificationDataProcessPageUpdates, notificationDataProcessPageHistory],
         notificationPollCallback: [notificationPollCallbackUpdates, notificationPollCallbackHistory],
         notificationPollStopPaginatingConditionMet: [isReturnedDataEmptyUpdates, isReturnedDataEmptyHistory],
         notificationPollCaresAboutSince: [false, true]
-*/
-        notificationPollEndpoint: "/api/v1/commerce/available_updates?per_page=10",
-        notificationPollTimeoutMs: 300000,
-        notificationDataProcessPage: notificationDataProcessPageUpdates,
-        notificationPollCallback: notificationPollCallbackUpdates,
-        notificationPollStopPaginatingConditionMet: isReturnedDataEmptyUpdates,
-        notificationPollCaresAboutSince: false
     });
     GlobalServices.myUsernameChanged.connect(onUsernameChanged);
     installMarketplaceItemTester();
diff --git a/scripts/system/pal.js b/scripts/system/pal.js
index a2ebae1a33..341ce9ebc8 100644
--- a/scripts/system/pal.js
+++ b/scripts/system/pal.js
@@ -844,7 +844,7 @@ function notificationPollCallback(connectionsArray) {
         newOnlineUsers++;
         storedOnlineUsers[user.username] = user;
 
-        if (!ui.isOpen && ui.notificationInitialCallbackMade) {
+        if (!ui.isOpen && ui.notificationInitialCallbackMade[0]) {
             message = user.username + " is available in " +
                 user.location.root.name + ". Open PEOPLE to join them.";
             ui.notificationDisplayBanner(message);
@@ -868,7 +868,7 @@ function notificationPollCallback(connectionsArray) {
             shouldShowDot: shouldShowDot
         });
 
-        if (newOnlineUsers > 0 && !ui.notificationInitialCallbackMade) {
+        if (newOnlineUsers > 0 && !ui.notificationInitialCallbackMade[0]) {
             message = newOnlineUsers + " of your connections " +
                 (newOnlineUsers === 1 ? "is" : "are") + " available online. Open PEOPLE to join them.";
             ui.notificationDisplayBanner(message);
@@ -889,12 +889,12 @@ function startup() {
         onOpened: palOpened,
         onClosed: off,
         onMessage: fromQml,
-        notificationPollEndpoint: "/api/v1/users?filter=connections&status=online&per_page=10",
-        notificationPollTimeoutMs: 60000,
-        notificationDataProcessPage: notificationDataProcessPage,
-        notificationPollCallback: notificationPollCallback,
-        notificationPollStopPaginatingConditionMet: isReturnedDataEmpty,
-        notificationPollCaresAboutSince: false
+        notificationPollEndpoint: ["/api/v1/users?filter=connections&status=online&per_page=10"],
+        notificationPollTimeoutMs: [60000],
+        notificationDataProcessPage: [notificationDataProcessPage],
+        notificationPollCallback: [notificationPollCallback],
+        notificationPollStopPaginatingConditionMet: [isReturnedDataEmpty],
+        notificationPollCaresAboutSince: [false]
     });
     Window.domainChanged.connect(clearLocalQMLDataAndClosePAL);
     Window.domainConnectionRefused.connect(clearLocalQMLDataAndClosePAL);
diff --git a/scripts/system/tablet-goto.js b/scripts/system/tablet-goto.js
index 6d8ba3a927..94117fd9ea 100644
--- a/scripts/system/tablet-goto.js
+++ b/scripts/system/tablet-goto.js
@@ -37,7 +37,7 @@ function notificationPollCallback(userStoriesArray) {
     //
     pingPong = !pingPong;
     var totalNewStories = 0;
-    var shouldNotifyIndividually = !ui.isOpen && ui.notificationInitialCallbackMade;
+    var shouldNotifyIndividually = !ui.isOpen && ui.notificationInitialCallbackMade[0];
     userStoriesArray.forEach(function (story) {
         if (story.audience !== "for_connections" &&
             story.audience !== "for_feed") {
@@ -91,7 +91,7 @@ function notificationPollCallback(userStoriesArray) {
     shouldShowDot = totalNewStories > 0 || (totalStories > 0 && shouldShowDot);
     ui.messagesWaiting(shouldShowDot && !ui.isOpen);
 
-    if (totalStories > 0 && !ui.isOpen && !ui.notificationInitialCallbackMade) {
+    if (totalStories > 0 && !ui.isOpen && !ui.notificationInitialCallbackMade[0]) {
         message = "There " + (totalStories === 1 ? "is " : "are ") + totalStories + " event" +
             (totalStories === 1 ? "" : "s") + " to know about. " +
             "Open GOTO to see " + (totalStories === 1 ? "it" : "them") + ".";
@@ -122,12 +122,12 @@ function startup() {
         sortOrder: 8,
         onOpened: gotoOpened,
         home: GOTO_QML_SOURCE,
-        notificationPollEndpoint: endpoint,
-        notificationPollTimeoutMs: 60000,
-        notificationDataProcessPage: notificationDataProcessPage,
-        notificationPollCallback: notificationPollCallback,
-        notificationPollStopPaginatingConditionMet: isReturnedDataEmpty,
-        notificationPollCaresAboutSince: false
+        notificationPollEndpoint: [endpoint],
+        notificationPollTimeoutMs: [60000],
+        notificationDataProcessPage: [notificationDataProcessPage],
+        notificationPollCallback: [notificationPollCallback],
+        notificationPollStopPaginatingConditionMet: [isReturnedDataEmpty],
+        notificationPollCaresAboutSince: [false]
     });
 }