diff --git a/interface/resources/icons/tablet-icons/goto-a-msg.svg b/interface/resources/icons/tablet-icons/goto-a-msg.svg
new file mode 100644
index 0000000000..f1f611adb9
--- /dev/null
+++ b/interface/resources/icons/tablet-icons/goto-a-msg.svg
@@ -0,0 +1,57 @@
+
+
+
+
\ No newline at end of file
diff --git a/interface/resources/icons/tablet-icons/goto-msg.svg b/interface/resources/icons/tablet-icons/goto-i-msg.svg
similarity index 100%
rename from interface/resources/icons/tablet-icons/goto-msg.svg
rename to interface/resources/icons/tablet-icons/goto-i-msg.svg
diff --git a/interface/resources/icons/tablet-icons/wallet-a-msg.svg b/interface/resources/icons/tablet-icons/wallet-a-msg.svg
new file mode 100644
index 0000000000..d51c3e99a2
--- /dev/null
+++ b/interface/resources/icons/tablet-icons/wallet-a-msg.svg
@@ -0,0 +1,6 @@
+
\ No newline at end of file
diff --git a/interface/resources/icons/tablet-icons/wallet-i-msg.svg b/interface/resources/icons/tablet-icons/wallet-i-msg.svg
new file mode 100644
index 0000000000..676f97a966
--- /dev/null
+++ b/interface/resources/icons/tablet-icons/wallet-i-msg.svg
@@ -0,0 +1,16 @@
+
+
+
diff --git a/interface/resources/qml/hifi/commerce/purchases/Purchases.qml b/interface/resources/qml/hifi/commerce/purchases/Purchases.qml
index 3b8e2c0f4d..2435678e77 100644
--- a/interface/resources/qml/hifi/commerce/purchases/Purchases.qml
+++ b/interface/resources/qml/hifi/commerce/purchases/Purchases.qml
@@ -93,7 +93,7 @@ Rectangle {
console.log("Failed to get Available Updates", result.data.message);
} else {
sendToScript({method: 'purchases_availableUpdatesReceived', numUpdates: result.data.updates.length });
- root.numUpdatesAvailable = result.data.updates.length;
+ root.numUpdatesAvailable = result.total_entries;
}
}
diff --git a/interface/resources/qml/hifi/commerce/wallet/WalletHome.qml b/interface/resources/qml/hifi/commerce/wallet/WalletHome.qml
index 50208793fe..627da1d43f 100644
--- a/interface/resources/qml/hifi/commerce/wallet/WalletHome.qml
+++ b/interface/resources/qml/hifi/commerce/wallet/WalletHome.qml
@@ -45,14 +45,6 @@ Item {
onHistoryResult : {
transactionHistoryModel.handlePage(null, result);
}
-
- onAvailableUpdatesResult: {
- if (result.status !== 'success') {
- console.log("Failed to get Available Updates", result.data.message);
- } else {
- sendToScript({method: 'wallet_availableUpdatesReceived', numUpdates: result.data.updates.length });
- }
- }
}
Connections {
diff --git a/interface/src/avatar/MyAvatar.cpp b/interface/src/avatar/MyAvatar.cpp
index df7ec93b6a..f2e6b68a0f 100755
--- a/interface/src/avatar/MyAvatar.cpp
+++ b/interface/src/avatar/MyAvatar.cpp
@@ -116,6 +116,7 @@ MyAvatar::MyAvatar(QThread* thread) :
_bodySensorMatrix(),
_goToPending(false),
_goToSafe(true),
+ _goToFeetAjustment(false),
_goToPosition(),
_goToOrientation(),
_prevShouldDrawHead(true),
@@ -498,7 +499,7 @@ void MyAvatar::update(float deltaTime) {
setCurrentStandingHeight(computeStandingHeightMode(getControllerPoseInAvatarFrame(controller::Action::HEAD)));
setAverageHeadRotation(computeAverageHeadRotation(getControllerPoseInAvatarFrame(controller::Action::HEAD)));
- if (_drawAverageFacingEnabled) {
+ if (_drawAverageFacingEnabled) {
auto sensorHeadPose = getControllerPoseInSensorFrame(controller::Action::HEAD);
glm::vec3 worldHeadPos = transformPoint(getSensorToWorldMatrix(), sensorHeadPose.getTranslation());
glm::vec3 worldFacingAverage = transformVectorFast(getSensorToWorldMatrix(), glm::vec3(_headControllerFacingMovingAverage.x, 0.0f, _headControllerFacingMovingAverage.y));
@@ -526,6 +527,11 @@ void MyAvatar::update(float deltaTime) {
_physicsSafetyPending = getCollisionsEnabled();
_characterController.recomputeFlying(); // In case we've gone to into the sky.
}
+ if (_goToFeetAjustment && _skeletonModelLoaded) {
+ auto feetAjustment = getWorldPosition() - getWorldFeetPosition();
+ goToLocation(getWorldPosition() + feetAjustment);
+ _goToFeetAjustment = false;
+ }
if (_physicsSafetyPending && qApp->isPhysicsEnabled() && _characterController.isEnabledAndReady()) {
// When needed and ready, arrange to check and fix.
_physicsSafetyPending = false;
@@ -1728,6 +1734,7 @@ void MyAvatar::setSkeletonModelURL(const QUrl& skeletonModelURL) {
_headBoneSet.clear();
_cauterizationNeedsUpdate = true;
+ _skeletonModelLoaded = false;
std::shared_ptr skeletonConnection = std::make_shared();
*skeletonConnection = QObject::connect(_skeletonModel.get(), &SkeletonModel::skeletonLoaded, [this, skeletonModelChangeCount, skeletonConnection]() {
@@ -1745,6 +1752,7 @@ void MyAvatar::setSkeletonModelURL(const QUrl& skeletonModelURL) {
_skeletonModel->setCauterizeBoneSet(_headBoneSet);
_fstAnimGraphOverrideUrl = _skeletonModel->getGeometry()->getAnimGraphOverrideUrl();
initAnimGraph();
+ _skeletonModelLoaded = true;
}
QObject::disconnect(*skeletonConnection);
});
@@ -2945,46 +2953,10 @@ void MyAvatar::goToLocation(const QVariant& propertiesVar) {
}
void MyAvatar::goToFeetLocation(const glm::vec3& newPosition,
- bool hasOrientation, const glm::quat& newOrientation,
- bool shouldFaceLocation) {
-
- qCDebug(interfaceapp).nospace() << "MyAvatar goToFeetLocation - moving to " << newPosition.x << ", "
- << newPosition.y << ", " << newPosition.z;
-
- ShapeInfo shapeInfo;
- computeShapeInfo(shapeInfo);
- glm::vec3 halfExtents = shapeInfo.getHalfExtents();
- glm::vec3 localFeetPos = shapeInfo.getOffset() - glm::vec3(0.0f, halfExtents.y + halfExtents.x, 0.0f);
- glm::mat4 localFeet = createMatFromQuatAndPos(Quaternions::IDENTITY, localFeetPos);
-
- glm::mat4 worldFeet = createMatFromQuatAndPos(Quaternions::IDENTITY, newPosition);
-
- glm::mat4 avatarMat = worldFeet * glm::inverse(localFeet);
-
- glm::vec3 adjustedPosition = extractTranslation(avatarMat);
-
- _goToPending = true;
- _goToPosition = adjustedPosition;
- _goToOrientation = getWorldOrientation();
- if (hasOrientation) {
- qCDebug(interfaceapp).nospace() << "MyAvatar goToFeetLocation - new orientation is "
- << newOrientation.x << ", " << newOrientation.y << ", " << newOrientation.z << ", " << newOrientation.w;
-
- // orient the user to face the target
- glm::quat quatOrientation = cancelOutRollAndPitch(newOrientation);
-
- if (shouldFaceLocation) {
- quatOrientation = newOrientation * glm::angleAxis(PI, Vectors::UP);
-
- // move the user a couple units away
- const float DISTANCE_TO_USER = 2.0f;
- _goToPosition = adjustedPosition - quatOrientation * IDENTITY_FORWARD * DISTANCE_TO_USER;
- }
-
- _goToOrientation = quatOrientation;
- }
-
- emit transformChanged();
+ bool hasOrientation, const glm::quat& newOrientation,
+ bool shouldFaceLocation) {
+ _goToFeetAjustment = true;
+ goToLocation(newPosition, hasOrientation, newOrientation, shouldFaceLocation);
}
void MyAvatar::goToLocation(const glm::vec3& newPosition,
diff --git a/interface/src/avatar/MyAvatar.h b/interface/src/avatar/MyAvatar.h
index 1dc0b3cd40..d7379a18c4 100644
--- a/interface/src/avatar/MyAvatar.h
+++ b/interface/src/avatar/MyAvatar.h
@@ -1732,6 +1732,7 @@ private:
bool _goToPending { false };
bool _physicsSafetyPending { false };
bool _goToSafe { true };
+ bool _goToFeetAjustment { false };
glm::vec3 _goToPosition;
glm::quat _goToOrientation;
@@ -1807,6 +1808,7 @@ private:
bool _haveReceivedHeightLimitsFromDomain { false };
int _disableHandTouchCount { 0 };
+ bool _skeletonModelLoaded { false };
Setting::Handle _dominantHandSetting;
Setting::Handle _headPitchSetting;
diff --git a/scripts/modules/appUi.js b/scripts/modules/appUi.js
index d5ab3f32ba..d3d76bceec 100644
--- a/scripts/modules/appUi.js
+++ b/scripts/modules/appUi.js
@@ -106,7 +106,9 @@ function AppUi(properties) {
that.notificationPollCaresAboutSince = false;
that.notificationInitialCallbackMade = false;
that.notificationDisplayBanner = function (message) {
- Window.displayAnnouncement(message);
+ if (!that.isOpen) {
+ Window.displayAnnouncement(message);
+ }
};
//
// END Notification Handling Defaults
@@ -117,6 +119,7 @@ function AppUi(properties) {
// Set isOpen, wireEventBridge, set buttonActive as appropriate,
// and finally call onOpened() or onClosed() IFF defined.
that.setCurrentVisibleScreenMetadata(type, url);
+
if (that.checkIsOpen(type, url)) {
that.wireEventBridge(true);
if (!that.isOpen) {
@@ -154,17 +157,21 @@ function AppUi(properties) {
return;
}
- // User is "appearing offline"
- if (GlobalServices.findableBy === "none") {
+ // User is "appearing offline" or is offline
+ if (GlobalServices.findableBy === "none" || Account.username === "") {
that.notificationPollTimeout = Script.setTimeout(that.notificationPoll, that.notificationPollTimeoutMs);
return;
}
var url = METAVERSE_BASE + that.notificationPollEndpoint;
+ var settingsKey = "notifications/" + that.buttonName + "/lastPoll";
+ var currentTimestamp = new Date().getTime();
+ var lastPollTimestamp = Settings.getValue(settingsKey, currentTimestamp);
if (that.notificationPollCaresAboutSince) {
- url = url + "&since=" + (new Date().getTime());
+ url = url + "&since=" + lastPollTimestamp/1000;
}
+ Settings.setValue(settingsKey, currentTimestamp);
console.debug(that.buttonName, 'polling for notifications at endpoint', url);
@@ -192,17 +199,18 @@ function AppUi(properties) {
} else {
concatenatedServerResponse = concatenatedServerResponse.concat(that.notificationDataProcessPage(response));
currentDataPageToRetrieve++;
- request({ uri: (url + "&page=" + currentDataPageToRetrieve) }, requestCallback);
+ request({ json: true, uri: (url + "&page=" + currentDataPageToRetrieve) }, requestCallback);
}
}
- request({ uri: url }, requestCallback);
+ request({ json: true, uri: url }, requestCallback);
};
// This won't do anything if there isn't a notification endpoint set
that.notificationPoll();
- function availabilityChanged() {
+ function restartNotificationPoll() {
+ that.notificationInitialCallbackMade = false;
if (that.notificationPollTimeout) {
Script.clearTimeout(that.notificationPollTimeout);
that.notificationPollTimeout = false;
@@ -302,7 +310,8 @@ function AppUi(properties) {
} : that.ignore;
that.onScriptEnding = function onScriptEnding() {
// Close if necessary, clean up any remaining handlers, and remove the button.
- GlobalServices.findableByChanged.disconnect(availabilityChanged);
+ GlobalServices.myUsernameChanged.disconnect(restartNotificationPoll);
+ GlobalServices.findableByChanged.disconnect(restartNotificationPoll);
if (that.isOpen) {
that.close();
}
@@ -322,6 +331,7 @@ function AppUi(properties) {
that.tablet.screenChanged.connect(that.onScreenChanged);
that.button.clicked.connect(that.onClicked);
Script.scriptEnding.connect(that.onScriptEnding);
- GlobalServices.findableByChanged.connect(availabilityChanged);
+ GlobalServices.findableByChanged.connect(restartNotificationPoll);
+ GlobalServices.myUsernameChanged.connect(restartNotificationPoll);
}
module.exports = AppUi;
diff --git a/scripts/modules/request.js b/scripts/modules/request.js
index 3516554567..d0037f9b43 100644
--- a/scripts/modules/request.js
+++ b/scripts/modules/request.js
@@ -19,7 +19,7 @@ module.exports = {
// ------------------------------------------------------------------
request: function (options, callback) { // cb(error, responseOfCorrectContentType) of url. A subset of npm request.
- var httpRequest = new XMLHttpRequest(), key;
+ var httpRequest = new XMLHttpRequest(), key;
// QT bug: apparently doesn't handle onload. Workaround using readyState.
httpRequest.onreadystatechange = function () {
var READY_STATE_DONE = 4;
@@ -72,7 +72,7 @@ module.exports = {
}
httpRequest.open(options.method, options.uri, true);
httpRequest.send(options.body || null);
- }
+ }
};
// ===========================================================================================
diff --git a/scripts/system/commerce/wallet.js b/scripts/system/commerce/wallet.js
index b12191b00c..5b91afea33 100644
--- a/scripts/system/commerce/wallet.js
+++ b/scripts/system/commerce/wallet.js
@@ -474,9 +474,6 @@ function fromQml(message) {
Window.location = "hifi://BankOfHighFidelity";
}
break;
- case 'wallet_availableUpdatesReceived':
- // NOP
- break;
case 'http.request':
// Handled elsewhere, don't log.
break;
@@ -491,32 +488,65 @@ function walletOpened() {
Controller.mouseMoveEvent.connect(handleMouseMoveEvent);
triggerMapping.enable();
triggerPressMapping.enable();
+ shouldShowDot = false;
+ ui.messagesWaiting(shouldShowDot);
}
function walletClosed() {
off();
}
-//
-// Manage the connection between the button and the window.
-//
+function notificationDataProcessPage(data) {
+ return data.data.history;
+}
+
+var shouldShowDot = false;
+function notificationPollCallback(historyArray) {
+ if (!ui.isOpen) {
+ var notificationCount = historyArray.length;
+ shouldShowDot = shouldShowDot || notificationCount > 0;
+ ui.messagesWaiting(shouldShowDot);
+
+ if (notificationCount > 0) {
+ var message;
+ if (!ui.notificationInitialCallbackMade) {
+ message = "You have " + notificationCount + " unread wallet " +
+ "transaction" + (notificationCount === 1 ? "" : "s") + ". Open WALLET to see all activity.";
+ ui.notificationDisplayBanner(message);
+ } else {
+ for (var i = 0; i < notificationCount; i++) {
+ message = '"' + (historyArray[i].message) + '" ' +
+ "Open WALLET to see all activity.";
+ ui.notificationDisplayBanner(message);
+ }
+ }
+ }
+ }
+}
+
+function isReturnedDataEmpty(data) {
+ var historyArray = data.data.history;
+ return historyArray.length === 0;
+}
+
var DEVELOPER_MENU = "Developer";
var MARKETPLACE_ITEM_TESTER_LABEL = "Marketplace Item Tester";
var MARKETPLACE_ITEM_TESTER_QML_SOURCE = "hifi/commerce/marketplaceItemTester/MarketplaceItemTester.qml";
function installMarketplaceItemTester() {
if (!Menu.menuExists(DEVELOPER_MENU)) {
- Menu.addMenu(DEVELOPER_MENU);
+ Menu.addMenu(DEVELOPER_MENU);
}
if (!Menu.menuItemExists(DEVELOPER_MENU, MARKETPLACE_ITEM_TESTER_LABEL)) {
- Menu.addMenuItem({ menuName: DEVELOPER_MENU,
- menuItemName: MARKETPLACE_ITEM_TESTER_LABEL,
- isCheckable: false })
+ Menu.addMenuItem({
+ menuName: DEVELOPER_MENU,
+ menuItemName: MARKETPLACE_ITEM_TESTER_LABEL,
+ isCheckable: false
+ });
}
Menu.menuItemEvent.connect(function (menuItem) {
if (menuItem === MARKETPLACE_ITEM_TESTER_LABEL) {
- var tablet = Tablet.getTablet("com.highfidelity.interface.tablet.system");
- tablet.loadQMLSource(MARKETPLACE_ITEM_TESTER_QML_SOURCE);
+ ui.open(MARKETPLACE_ITEM_TESTER_QML_SOURCE);
}
});
}
@@ -539,7 +569,13 @@ function startup() {
home: WALLET_QML_SOURCE,
onOpened: walletOpened,
onClosed: walletClosed,
- onMessage: fromQml
+ onMessage: fromQml,
+ notificationPollEndpoint: "/api/v1/commerce/history?per_page=10",
+ notificationPollTimeoutMs: 300000,
+ notificationDataProcessPage: notificationDataProcessPage,
+ notificationPollCallback: notificationPollCallback,
+ notificationPollStopPaginatingConditionMet: isReturnedDataEmpty,
+ notificationPollCaresAboutSince: true
});
GlobalServices.myUsernameChanged.connect(onUsernameChanged);
installMarketplaceItemTester();
diff --git a/scripts/system/marketplaces/marketplaces.js b/scripts/system/marketplaces/marketplaces.js
index f36679a4b6..b92a427c1f 100644
--- a/scripts/system/marketplaces/marketplaces.js
+++ b/scripts/system/marketplaces/marketplaces.js
@@ -178,10 +178,10 @@ function onUsernameChanged() {
}
}
-var userHasUpdates = false;
function walletNeedsSetup() {
return Wallet.walletStatus === 1;
}
+
function sendCommerceSettings() {
ui.sendToHtml({
type: "marketplaces",
@@ -191,7 +191,7 @@ function sendCommerceSettings() {
userIsLoggedIn: Account.loggedIn,
walletNeedsSetup: walletNeedsSetup(),
metaverseServerURL: Account.metaverseServerURL,
- messagesWaiting: userHasUpdates
+ messagesWaiting: shouldShowDot
}
});
}
@@ -972,10 +972,9 @@ var onQmlMessageReceived = function onQmlMessageReceived(message) {
removeOverlays();
}
break;
- case 'wallet_availableUpdatesReceived':
case 'purchases_availableUpdatesReceived':
- userHasUpdates = message.numUpdates > 0;
- ui.messagesWaiting(userHasUpdates);
+ shouldShowDot = message.numUpdates > 0;
+ ui.messagesWaiting(shouldShowDot && !ui.isOpen);
break;
case 'purchases_updateWearables':
var currentlyWornWearables = [];
@@ -1131,9 +1130,41 @@ var onTabletScreenChanged = function onTabletScreenChanged(type, url) {
"\nNew screen URL: " + url + "\nCurrent app open status: " + ui.isOpen + "\n");
};
-//
-// Manage the connection between the button and the window.
-//
+function notificationDataProcessPage(data) {
+ return data.data.updates;
+}
+
+var shouldShowDot = false;
+function notificationPollCallback(updatesArray) {
+ shouldShowDot = shouldShowDot || updatesArray.length > 0;
+ ui.messagesWaiting(shouldShowDot && !ui.isOpen);
+
+ if (updatesArray.length > 0) {
+ var message;
+ if (!ui.notificationInitialCallbackMade) {
+ message = updatesArray.length + " of your purchased items " +
+ (updatesArray.length === 1 ? "has an update " : "have updates ") +
+ "available. Open MARKET to update.";
+ ui.notificationDisplayBanner(message);
+
+ ui.notificationPollCaresAboutSince = true;
+ } else {
+ for (var i = 0; i < updatesArray.length; i++) {
+ message = "Update available for \"" +
+ updatesArray[i].base_item_title + "\"." +
+ "Open MARKET to update.";
+ ui.notificationDisplayBanner(message);
+ }
+ }
+ }
+}
+
+function isReturnedDataEmpty(data) {
+ var historyArray = data.data.updates;
+ return historyArray.length === 0;
+}
+
+
var BUTTON_NAME = "MARKET";
var MARKETPLACE_URL = METAVERSE_SERVER_URL + "/marketplace";
var MARKETPLACE_URL_INITIAL = MARKETPLACE_URL + "?"; // Append "?" to signal injected script that it's the initial page.
@@ -1145,7 +1176,13 @@ function startup() {
inject: MARKETPLACES_INJECT_SCRIPT_URL,
home: MARKETPLACE_URL_INITIAL,
onScreenChanged: onTabletScreenChanged,
- onMessage: onQmlMessageReceived
+ onMessage: onQmlMessageReceived,
+ notificationPollEndpoint: "/api/v1/commerce/available_updates?per_page=10",
+ notificationPollTimeoutMs: 300000,
+ notificationDataProcessPage: notificationDataProcessPage,
+ notificationPollCallback: notificationPollCallback,
+ notificationPollStopPaginatingConditionMet: isReturnedDataEmpty,
+ notificationPollCaresAboutSince: false // Changes to true after first poll
});
ContextOverlay.contextOverlayClicked.connect(openInspectionCertificateQML);
Entities.canWriteAssetsChanged.connect(onCanWriteAssetsChanged);
diff --git a/scripts/system/pal.js b/scripts/system/pal.js
index 85898c28fb..a2ebae1a33 100644
--- a/scripts/system/pal.js
+++ b/scripts/system/pal.js
@@ -823,46 +823,40 @@ function notificationDataProcessPage(data) {
}
var shouldShowDot = false;
-var storedOnlineUsersArray = [];
+var pingPong = false;
+var storedOnlineUsers = {};
function notificationPollCallback(connectionsArray) {
//
// START logic for handling online/offline user changes
//
- var i, j;
- var newlyOnlineConnectionsArray = [];
- for (i = 0; i < connectionsArray.length; i++) {
- var currentUser = connectionsArray[i];
+ pingPong = !pingPong;
+ var newOnlineUsers = 0;
+ var message;
- if (connectionsArray[i].online) {
- var indexOfStoredOnlineUser = -1;
- for (j = 0; j < storedOnlineUsersArray.length; j++) {
- if (currentUser.username === storedOnlineUsersArray[j].username) {
- indexOfStoredOnlineUser = j;
- break;
- }
- }
- // If the user record isn't already presesnt inside `storedOnlineUsersArray`...
- if (indexOfStoredOnlineUser < 0) {
- storedOnlineUsersArray.push(currentUser);
- newlyOnlineConnectionsArray.push(currentUser);
- }
- } else {
- var indexOfOfflineUser = -1;
- for (j = 0; j < storedOnlineUsersArray.length; j++) {
- if (currentUser.username === storedOnlineUsersArray[j].username) {
- indexOfOfflineUser = j;
- break;
- }
- }
- if (indexOfOfflineUser >= 0) {
- storedOnlineUsersArray.splice(indexOfOfflineUser);
- }
+ connectionsArray.forEach(function (user) {
+ var stored = storedOnlineUsers[user.username];
+ var storedOrNew = stored || user;
+ storedOrNew.pingPong = pingPong;
+ if (stored) {
+ return;
+ }
+
+ newOnlineUsers++;
+ storedOnlineUsers[user.username] = user;
+
+ if (!ui.isOpen && ui.notificationInitialCallbackMade) {
+ message = user.username + " is available in " +
+ user.location.root.name + ". Open PEOPLE to join them.";
+ ui.notificationDisplayBanner(message);
+ }
+ });
+ var key;
+ for (key in storedOnlineUsers) {
+ if (storedOnlineUsers[key].pingPong !== pingPong) {
+ delete storedOnlineUsers[key];
}
}
- // If there's new data, the light should turn on.
- // If the light is already on and you have connections online, the light should stay on.
- // In all other cases, the light should turn off or stay off.
- shouldShowDot = newlyOnlineConnectionsArray.length > 0 || (storedOnlineUsersArray.length > 0 && shouldShowDot);
+ shouldShowDot = newOnlineUsers > 0 || (Object.keys(storedOnlineUsers).length > 0 && shouldShowDot);
//
// END logic for handling online/offline user changes
//
@@ -874,19 +868,10 @@ function notificationPollCallback(connectionsArray) {
shouldShowDot: shouldShowDot
});
- if (newlyOnlineConnectionsArray.length > 0) {
- var message;
- if (!ui.notificationInitialCallbackMade) {
- message = newlyOnlineConnectionsArray.length + " of your connections " +
- (newlyOnlineConnectionsArray.length === 1 ? "is" : "are") + " online. Open PEOPLE to join them!";
- ui.notificationDisplayBanner(message);
- } else {
- for (i = 0; i < newlyOnlineConnectionsArray.length; i++) {
- message = newlyOnlineConnectionsArray[i].username + " is available in " +
- newlyOnlineConnectionsArray[i].location.root.name + ". Open PEOPLE to join them!";
- ui.notificationDisplayBanner(message);
- }
- }
+ if (newOnlineUsers > 0 && !ui.notificationInitialCallbackMade) {
+ message = newOnlineUsers + " of your connections " +
+ (newOnlineUsers === 1 ? "is" : "are") + " available online. Open PEOPLE to join them.";
+ ui.notificationDisplayBanner(message);
}
}
}
@@ -904,7 +889,7 @@ function startup() {
onOpened: palOpened,
onClosed: off,
onMessage: fromQml,
- notificationPollEndpoint: "/api/v1/users?filter=connections&per_page=10",
+ notificationPollEndpoint: "/api/v1/users?filter=connections&status=online&per_page=10",
notificationPollTimeoutMs: 60000,
notificationDataProcessPage: notificationDataProcessPage,
notificationPollCallback: notificationPollCallback,
diff --git a/scripts/system/tablet-goto.js b/scripts/system/tablet-goto.js
index 804f838d04..6d8ba3a927 100644
--- a/scripts/system/tablet-goto.js
+++ b/scripts/system/tablet-goto.js
@@ -15,118 +15,121 @@
//
(function () { // BEGIN LOCAL_SCOPE
-var request = Script.require('request').request;
var AppUi = Script.require('appUi');
-var DEBUG = false;
-function debug() {
- if (!DEBUG) {
- return;
- }
- print('tablet-goto.js:', [].map.call(arguments, JSON.stringify));
-}
-
-var stories = {}, pingPong = false;
-function expire(id) {
- var options = {
- uri: Account.metaverseServerURL + '/api/v1/user_stories/' + id,
- method: 'PUT',
- json: true,
- body: {expire: "true"}
- };
- request(options, function (error, response) {
- debug('expired story', options, 'error:', error, 'response:', response);
- if (error || (response.status !== 'success')) {
- print("ERROR expiring story: ", error || response.status);
- }
- });
-}
-var PER_PAGE_DEBUG = 10;
-var PER_PAGE_NORMAL = 100;
-function pollForAnnouncements() {
- // We could bail now if !Account.isLoggedIn(), but what if we someday have system-wide announcments?
- var actions = 'announcement';
- var count = DEBUG ? PER_PAGE_DEBUG : PER_PAGE_NORMAL;
- var options = [
- 'now=' + new Date().toISOString(),
- 'include_actions=' + actions,
- 'restriction=' + (Account.isLoggedIn() ? 'open,hifi' : 'open'),
- 'require_online=true',
- 'protocol=' + encodeURIComponent(Window.protocolSignature()),
- 'per_page=' + count
- ];
- var url = Account.metaverseServerURL + '/api/v1/user_stories?' + options.join('&');
- request({
- uri: url
- }, function (error, data) {
- debug(url, error, data);
- if (error || (data.status !== 'success')) {
- print("Error: unable to get", url, error || data.status);
- return;
- }
- var didNotify = false, key;
- pingPong = !pingPong;
- data.user_stories.forEach(function (story) {
- var stored = stories[story.id], storedOrNew = stored || story;
- debug('story exists:', !!stored, storedOrNew);
- if ((storedOrNew.username === Account.username) && (storedOrNew.place_name !== location.placename)) {
- if (storedOrNew.audience === 'for_connections') { // Only expire if we haven't already done so.
- expire(story.id);
- }
- return; // before marking
- }
- storedOrNew.pingPong = pingPong;
- if (stored) { // already seen
- return;
- }
- stories[story.id] = story;
- var message = story.username + " " + story.action_string + " in " +
- story.place_name + ". Open GOTO to join them.";
- Window.displayAnnouncement(message);
- didNotify = true;
- });
- for (key in stories) { // Any story we were tracking that was not marked, has expired.
- if (stories[key].pingPong !== pingPong) {
- debug('removing story', key);
- delete stories[key];
- }
- }
- if (didNotify) {
- ui.messagesWaiting(true);
- if (HMD.isHandControllerAvailable()) {
- var STRENGTH = 1.0, DURATION_MS = 60, HAND = 2; // both hands
- Controller.triggerHapticPulse(STRENGTH, DURATION_MS, HAND);
- }
- } else if (!Object.keys(stories).length) { // If there's nothing being tracked, then any messageWaiting has expired.
- ui.messagesWaiting(false);
- }
- });
-}
-var MS_PER_SEC = 1000;
-var DEBUG_POLL_TIME_SEC = 10;
-var NORMAL_POLL_TIME_SEC = 60;
-var ANNOUNCEMENTS_POLL_TIME_MS = (DEBUG ? DEBUG_POLL_TIME_SEC : NORMAL_POLL_TIME_SEC) * MS_PER_SEC;
-var pollTimer = Script.setInterval(pollForAnnouncements, ANNOUNCEMENTS_POLL_TIME_MS);
function gotoOpened() {
- ui.messagesWaiting(false);
+ shouldShowDot = false;
+ ui.messagesWaiting(shouldShowDot);
+}
+
+function notificationDataProcessPage(data) {
+ return data.user_stories;
+}
+
+var shouldShowDot = false;
+var pingPong = false;
+var storedAnnouncements = {};
+var storedFeaturedStories = {};
+var message;
+function notificationPollCallback(userStoriesArray) {
+ //
+ // START logic for keeping track of new info
+ //
+ pingPong = !pingPong;
+ var totalNewStories = 0;
+ var shouldNotifyIndividually = !ui.isOpen && ui.notificationInitialCallbackMade;
+ userStoriesArray.forEach(function (story) {
+ if (story.audience !== "for_connections" &&
+ story.audience !== "for_feed") {
+ return;
+ }
+
+ var stored = storedAnnouncements[story.id] || storedFeaturedStories[story.id];
+ var storedOrNew = stored || story;
+ storedOrNew.pingPong = pingPong;
+ if (stored) {
+ return;
+ }
+
+ totalNewStories++;
+
+ if (story.audience === "for_connections") {
+ storedAnnouncements[story.id] = story;
+
+ if (shouldNotifyIndividually) {
+ message = story.username + " says something is happening in " +
+ story.place_name + ". Open GOTO to join them.";
+ ui.notificationDisplayBanner(message);
+ }
+ } else if (story.audience === "for_feed") {
+ storedFeaturedStories[story.id] = story;
+
+ if (shouldNotifyIndividually) {
+ message = story.username + " invites you to an event in " +
+ story.place_name + ". Open GOTO to join them.";
+ ui.notificationDisplayBanner(message);
+ }
+ }
+ });
+ var key;
+ for (key in storedAnnouncements) {
+ if (storedAnnouncements[key].pingPong !== pingPong) {
+ delete storedAnnouncements[key];
+ }
+ }
+ for (key in storedFeaturedStories) {
+ if (storedFeaturedStories[key].pingPong !== pingPong) {
+ delete storedFeaturedStories[key];
+ }
+ }
+ //
+ // END logic for keeping track of new info
+ //
+
+ var totalStories = Object.keys(storedAnnouncements).length +
+ Object.keys(storedFeaturedStories).length;
+ shouldShowDot = totalNewStories > 0 || (totalStories > 0 && shouldShowDot);
+ ui.messagesWaiting(shouldShowDot && !ui.isOpen);
+
+ if (totalStories > 0 && !ui.isOpen && !ui.notificationInitialCallbackMade) {
+ message = "There " + (totalStories === 1 ? "is " : "are ") + totalStories + " event" +
+ (totalStories === 1 ? "" : "s") + " to know about. " +
+ "Open GOTO to see " + (totalStories === 1 ? "it" : "them") + ".";
+ ui.notificationDisplayBanner(message);
+ }
+}
+
+function isReturnedDataEmpty(data) {
+ var storiesArray = data.user_stories;
+ return storiesArray.length === 0;
}
var ui;
var GOTO_QML_SOURCE = "hifi/tablet/TabletAddressDialog.qml";
var BUTTON_NAME = "GOTO";
function startup() {
+ var options = [
+ 'include_actions=announcement',
+ 'restriction=open,hifi',
+ 'require_online=true',
+ 'protocol=' + encodeURIComponent(Window.protocolSignature()),
+ 'per_page=10'
+ ];
+ var endpoint = '/api/v1/user_stories?' + options.join('&');
+
ui = new AppUi({
buttonName: BUTTON_NAME,
sortOrder: 8,
onOpened: gotoOpened,
- home: GOTO_QML_SOURCE
+ home: GOTO_QML_SOURCE,
+ notificationPollEndpoint: endpoint,
+ notificationPollTimeoutMs: 60000,
+ notificationDataProcessPage: notificationDataProcessPage,
+ notificationPollCallback: notificationPollCallback,
+ notificationPollStopPaginatingConditionMet: isReturnedDataEmpty,
+ notificationPollCaresAboutSince: false
});
}
-function shutdown() {
- Script.clearInterval(pollTimer);
-}
-
startup();
-Script.scriptEnding.connect(shutdown);
}()); // END LOCAL_SCOPE