diff --git a/interface/resources/qml/hifi/commerce/purchases/Purchases.qml b/interface/resources/qml/hifi/commerce/purchases/Purchases.qml index de66be4a88..87b784bc4e 100644 --- a/interface/resources/qml/hifi/commerce/purchases/Purchases.qml +++ b/interface/resources/qml/hifi/commerce/purchases/Purchases.qml @@ -36,6 +36,7 @@ Rectangle { property bool pendingInventoryReply: true; property bool isShowingMyItems: false; property bool isDebuggingFirstUseTutorial: false; + property int pendingItemCount: 0; // Style color: hifi.colors.white; Connections { @@ -79,18 +80,22 @@ Rectangle { onInventoryResult: { purchasesReceived = true; - if (root.pendingInventoryReply) { - inventoryTimer.start(); - } - if (result.status !== 'success') { console.log("Failed to get purchases", result.message); - } else { + } else if (!purchasesContentsList.dragging) { // Don't modify the view if the user's scrolling var inventoryResult = processInventoryResult(result.data.assets); + var currentIndex = purchasesContentsList.currentIndex === -1 ? 0 : purchasesContentsList.currentIndex; purchasesModel.clear(); purchasesModel.append(inventoryResult); + root.pendingItemCount = 0; + for (var i = 0; i < purchasesModel.count; i++) { + if (purchasesModel.get(i).status === "pending") { + root.pendingItemCount++; + } + } + if (previousPurchasesModel.count !== 0) { checkIfAnyItemStatusChanged(); } else { @@ -103,6 +108,12 @@ Rectangle { previousPurchasesModel.append(inventoryResult); buildFilteredPurchasesModel(); + + purchasesContentsList.positionViewAtIndex(currentIndex, ListView.Beginning); + } + + if (root.pendingInventoryReply && root.pendingItemCount > 0) { + inventoryTimer.start(); } root.pendingInventoryReply = false; @@ -419,6 +430,8 @@ Rectangle { visible: (root.isShowingMyItems && filteredPurchasesModel.count !== 0) || (!root.isShowingMyItems && filteredPurchasesModel.count !== 0); clip: true; model: filteredPurchasesModel; + snapMode: ListView.SnapToItem; + highlightRangeMode: ListView.StrictlyEnforceRange; // Anchors anchors.top: root.canRezCertifiedItems ? separator.bottom : cantRezCertified.bottom; anchors.topMargin: 12; diff --git a/interface/resources/qml/hifi/commerce/wallet/WalletHome.qml b/interface/resources/qml/hifi/commerce/wallet/WalletHome.qml index 42ee44d584..780e08caf8 100644 --- a/interface/resources/qml/hifi/commerce/wallet/WalletHome.qml +++ b/interface/resources/qml/hifi/commerce/wallet/WalletHome.qml @@ -25,8 +25,12 @@ Item { HifiConstants { id: hifi; } id: root; - property bool historyReceived: false; + property bool initialHistoryReceived: false; + property bool historyRequestPending: true; + property bool noMoreHistoryData: false; property int pendingCount: 0; + property int currentHistoryPage: 1; + property var pagesAlreadyAdded: new Array(); Connections { target: Commerce; @@ -36,32 +40,86 @@ Item { } onHistoryResult : { - historyReceived = true; - if (result.status === 'success') { - var sameItemCount = 0; - tempTransactionHistoryModel.clear(); - - tempTransactionHistoryModel.append(result.data.history); - - for (var i = 0; i < tempTransactionHistoryModel.count; i++) { - if (!transactionHistoryModel.get(i)) { - sameItemCount = -1; - break; - } else if (tempTransactionHistoryModel.get(i).transaction_type === transactionHistoryModel.get(i).transaction_type && - tempTransactionHistoryModel.get(i).text === transactionHistoryModel.get(i).text) { - sameItemCount++; - } - } + root.initialHistoryReceived = true; + root.historyRequestPending = false; - if (sameItemCount !== tempTransactionHistoryModel.count) { - transactionHistoryModel.clear(); + if (result.status === 'success') { + var currentPage = parseInt(result.current_page); + + if (result.data.history.length === 0) { + root.noMoreHistoryData = true; + console.log("No more data to retrieve from Commerce.history() endpoint.") + } else if (root.currentHistoryPage === 1) { + var sameItemCount = 0; + tempTransactionHistoryModel.clear(); + + tempTransactionHistoryModel.append(result.data.history); + for (var i = 0; i < tempTransactionHistoryModel.count; i++) { - transactionHistoryModel.append(tempTransactionHistoryModel.get(i)); + if (!transactionHistoryModel.get(i)) { + sameItemCount = -1; + break; + } else if (tempTransactionHistoryModel.get(i).transaction_type === transactionHistoryModel.get(i).transaction_type && + tempTransactionHistoryModel.get(i).text === transactionHistoryModel.get(i).text) { + sameItemCount++; + } + } + + if (sameItemCount !== tempTransactionHistoryModel.count) { + transactionHistoryModel.clear(); + for (var i = 0; i < tempTransactionHistoryModel.count; i++) { + transactionHistoryModel.append(tempTransactionHistoryModel.get(i)); + } + calculatePendingAndInvalidated(); + } + } else { + if (root.pagesAlreadyAdded.indexOf(currentPage) !== -1) { + console.log("Page " + currentPage + " of history has already been added to the list."); + } else { + // First, add the history result to a temporary model + tempTransactionHistoryModel.clear(); + tempTransactionHistoryModel.append(result.data.history); + + // Make a note that we've already added this page to the model... + root.pagesAlreadyAdded.push(currentPage); + + var insertionIndex = 0; + // If there's nothing in the model right now, we don't need to modify insertionIndex. + if (transactionHistoryModel.count !== 0) { + var currentIteratorPage; + // Search through the whole transactionHistoryModel and look for the insertion point. + // The insertion point is found when the result page from the server is less than + // the page that the current item came from, OR when we've reached the end of the whole model. + for (var i = 0; i < transactionHistoryModel.count; i++) { + currentIteratorPage = transactionHistoryModel.get(i).resultIsFromPage; + + if (currentPage < currentIteratorPage) { + insertionIndex = i; + break; + } else if (i === transactionHistoryModel.count - 1) { + insertionIndex = i + 1; + break; + } + } + } + + // Go through the results we just got back from the server, setting the "resultIsFromPage" + // property of those results and adding them to the main model. + for (var i = 0; i < tempTransactionHistoryModel.count; i++) { + tempTransactionHistoryModel.setProperty(i, "resultIsFromPage", currentPage); + transactionHistoryModel.insert(i + insertionIndex, tempTransactionHistoryModel.get(i)) + } + + calculatePendingAndInvalidated(); } - calculatePendingAndInvalidated(); } } - refreshTimer.start(); + + // Only auto-refresh if the user hasn't scrolled + // and there is more data to grab + if (transactionHistory.atYBeginning && !root.noMoreHistoryData) { + refreshTimer.start(); + } } } @@ -134,9 +192,13 @@ Item { onVisibleChanged: { if (visible) { - historyReceived = false; + transactionHistoryModel.clear(); Commerce.balance(); - Commerce.history(); + initialHistoryReceived = false; + root.currentHistoryPage = 1; + root.noMoreHistoryData = false; + root.historyRequestPending = true; + Commerce.history(root.currentHistoryPage); } else { refreshTimer.stop(); } @@ -164,9 +226,12 @@ Item { id: refreshTimer; interval: 4000; onTriggered: { - console.log("Refreshing Wallet Home..."); - Commerce.balance(); - Commerce.history(); + if (transactionHistory.atYBeginning) { + console.log("Refreshing 1st Page of Recent Activity..."); + root.historyRequestPending = true; + Commerce.balance(); + Commerce.history(1); + } } } @@ -241,7 +306,7 @@ Item { anchors.right: parent.right; Item { - visible: transactionHistoryModel.count === 0 && root.historyReceived; + visible: transactionHistoryModel.count === 0 && root.initialHistoryReceived; anchors.centerIn: parent; width: parent.width - 12; height: parent.height; @@ -364,7 +429,12 @@ Item { onAtYEndChanged: { if (transactionHistory.atYEnd) { console.log("User scrolled to the bottom of 'Recent Activity'."); - // Grab next page of results and append to model + if (!root.historyRequestPending && !root.noMoreHistoryData) { + // Grab next page of results and append to model + root.historyRequestPending = true; + Commerce.history(++root.currentHistoryPage); + console.log("Fetching Page " + root.currentHistoryPage + " of Recent Activity..."); + } } } } diff --git a/interface/src/commerce/Ledger.cpp b/interface/src/commerce/Ledger.cpp index d7d36dabf6..51658ddef8 100644 --- a/interface/src/commerce/Ledger.cpp +++ b/interface/src/commerce/Ledger.cpp @@ -72,11 +72,16 @@ void Ledger::signedSend(const QString& propertyName, const QByteArray& text, con send(endpoint, success, fail, QNetworkAccessManager::PutOperation, AccountManagerAuth::Required, request); } -void Ledger::keysQuery(const QString& endpoint, const QString& success, const QString& fail) { +void Ledger::keysQuery(const QString& endpoint, const QString& success, const QString& fail, QJsonObject& requestParams) { auto wallet = DependencyManager::get(); - QJsonObject request; - request["public_keys"] = QJsonArray::fromStringList(wallet->listPublicKeys()); - send(endpoint, success, fail, QNetworkAccessManager::PostOperation, AccountManagerAuth::Required, request); + requestParams["public_keys"] = QJsonArray::fromStringList(wallet->listPublicKeys()); + + send(endpoint, success, fail, QNetworkAccessManager::PostOperation, AccountManagerAuth::Required, requestParams); +} + +void Ledger::keysQuery(const QString& endpoint, const QString& success, const QString& fail) { + QJsonObject requestParams; + keysQuery(endpoint, success, fail, requestParams); } void Ledger::buy(const QString& hfc_key, int cost, const QString& asset_id, const QString& inventory_key, const bool controlled_failure) { @@ -169,6 +174,7 @@ void Ledger::historySuccess(QNetworkReply& reply) { QJsonObject newDataData; newDataData["history"] = newHistoryArray; newData["data"] = newDataData; + newData["current_page"] = data["current_page"].toInt(); emit historyResult(newData); } @@ -176,8 +182,11 @@ void Ledger::historyFailure(QNetworkReply& reply) { failResponse("history", reply); } -void Ledger::history(const QStringList& keys) { - keysQuery("history", "historySuccess", "historyFailure"); +void Ledger::history(const QStringList& keys, const int& pageNumber) { + QJsonObject params; + params["per_page"] = 100; + params["page"] = pageNumber; + keysQuery("history", "historySuccess", "historyFailure", params); } // The api/failResponse is called just for the side effect of logging. diff --git a/interface/src/commerce/Ledger.h b/interface/src/commerce/Ledger.h index 42eb0ffc49..5d90aa0808 100644 --- a/interface/src/commerce/Ledger.h +++ b/interface/src/commerce/Ledger.h @@ -29,7 +29,7 @@ public: bool receiveAt(const QString& hfc_key, const QString& old_key); void balance(const QStringList& keys); void inventory(const QStringList& keys); - void history(const QStringList& keys); + void history(const QStringList& keys, const int& pageNumber); void account(); void reset(); void updateLocation(const QString& asset_id, const QString location, const bool controlledFailure = false); @@ -79,6 +79,7 @@ private: QJsonObject apiResponse(const QString& label, QNetworkReply& reply); QJsonObject failResponse(const QString& label, QNetworkReply& reply); void send(const QString& endpoint, const QString& success, const QString& fail, QNetworkAccessManager::Operation method, AccountManagerAuth::Type authType, QJsonObject request); + void keysQuery(const QString& endpoint, const QString& success, const QString& fail, QJsonObject& extraRequestParams); void keysQuery(const QString& endpoint, const QString& success, const QString& fail); void signedSend(const QString& propertyName, const QByteArray& text, const QString& key, const QString& endpoint, const QString& success, const QString& fail, const bool controlled_failure = false); }; diff --git a/interface/src/commerce/QmlCommerce.cpp b/interface/src/commerce/QmlCommerce.cpp index 62e87f9c66..320c7e041c 100644 --- a/interface/src/commerce/QmlCommerce.cpp +++ b/interface/src/commerce/QmlCommerce.cpp @@ -96,12 +96,12 @@ void QmlCommerce::inventory() { } } -void QmlCommerce::history() { +void QmlCommerce::history(const int& pageNumber) { auto ledger = DependencyManager::get(); auto wallet = DependencyManager::get(); QStringList cachedPublicKeys = wallet->listPublicKeys(); if (!cachedPublicKeys.isEmpty()) { - ledger->history(cachedPublicKeys); + ledger->history(cachedPublicKeys, pageNumber); } } diff --git a/interface/src/commerce/QmlCommerce.h b/interface/src/commerce/QmlCommerce.h index c53e73d565..f2e6c82021 100644 --- a/interface/src/commerce/QmlCommerce.h +++ b/interface/src/commerce/QmlCommerce.h @@ -60,7 +60,7 @@ protected: Q_INVOKABLE void buy(const QString& assetId, int cost, const bool controlledFailure = false); Q_INVOKABLE void balance(); Q_INVOKABLE void inventory(); - Q_INVOKABLE void history(); + Q_INVOKABLE void history(const int& pageNumber); Q_INVOKABLE void generateKeyPair(); Q_INVOKABLE void reset(); Q_INVOKABLE void resetLocalWalletOnly();