From 8010d86210bef1c2d95c235bba9b112d0e3a2e1a Mon Sep 17 00:00:00 2001 From: Roxanne Skelly <roxanne@highfidelity.io> Date: Mon, 21 Jan 2019 13:39:16 -0800 Subject: [PATCH 01/18] QML Marketplace support Support QML UI for the Marketplace as some devices do not handle web on 3d surfaces. Checkpoint code --- .../hifi/commerce/marketplace/Marketplace.qml | 336 ++++++++++++++++++ interface/src/Application.cpp | 9 + interface/src/commerce/QmlMarketplace.cpp | 131 +++++++ interface/src/commerce/QmlMarketplace.h | 68 ++++ scripts/system/marketplaces/marketplace.js | 117 +----- scripts/system/marketplaces/marketplaces.js | 8 +- 6 files changed, 565 insertions(+), 104 deletions(-) create mode 100644 interface/resources/qml/hifi/commerce/marketplace/Marketplace.qml create mode 100644 interface/src/commerce/QmlMarketplace.cpp create mode 100644 interface/src/commerce/QmlMarketplace.h diff --git a/interface/resources/qml/hifi/commerce/marketplace/Marketplace.qml b/interface/resources/qml/hifi/commerce/marketplace/Marketplace.qml new file mode 100644 index 0000000000..fb8b410e3f --- /dev/null +++ b/interface/resources/qml/hifi/commerce/marketplace/Marketplace.qml @@ -0,0 +1,336 @@ +// +// Marketplace.qml +// qml/hifi/commerce/marketplace +// +// Marketplace +// +// Created by Roxanne Skelly on 2019-01-18 +// Copyright 2019 High Fidelity, Inc. +// +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// + +import Hifi 1.0 as Hifi +import QtQuick 2.9 +import QtQuick.Controls 2.2 +import QtGraphicalEffects 1.0 +import stylesUit 1.0 +import controlsUit 1.0 as HifiControlsUit +import "../../../controls" as HifiControls +import "../common" as HifiCommerceCommon +import "qrc:////qml//hifi//models" as HifiModels // Absolute path so the same code works everywhere. +import "../common/sendAsset" +import "../.." as HifiCommon + +Rectangle { + HifiConstants { id: hifi; } + + id: root; + + property string activeView: "initialize"; + property bool keyboardRaised: false; + property int category_index: -1; + property alias categoryChoices: categoriesModel; + + anchors.fill: (typeof parent === undefined) ? undefined : parent; + + Component.onDestruction: { + KeyboardScriptingInterface.raised = false; + } + + Connections { + target: Marketplace; + + onGetMarketplaceCategoriesResult: { + if (result.status !== 'success') { + console.log("Failed to get Marketplace Categories", result.data.message); + } else { + + } + } + } + + HifiCommerceCommon.CommerceLightbox { + id: lightboxPopup; + visible: false; + anchors.fill: parent; + } + + // + // HEADER BAR START + // + Item { + id: header; + visible: true; + width: parent.width; + anchors.left: parent.left; + anchors.top: parent.top; + anchors.right: parent.right; + + Item { + id: titleBarContainer; + visible: true; + // Size + width: parent.width; + height: 50; + // Anchors + anchors.left: parent.left; + anchors.top: parent.top; + + // Wallet icon + Image { + id: walletIcon; + source: "../../../../images/hifi-logo-blackish.svg"; + height: 20 + width: walletIcon.height; + anchors.left: parent.left; + anchors.leftMargin: 8; + anchors.verticalCenter: parent.verticalCenter; + visible: true; + } + + // Title Bar text + RalewaySemiBold { + id: titleBarText; + text: "Marketplace"; + // Text size + size: hifi.fontSizes.overlayTitle; + // Anchors + anchors.top: parent.top; + anchors.left: walletIcon.right; + anchors.leftMargin: 6; + anchors.bottom: parent.bottom; + width: paintedWidth; + // Style + color: hifi.colors.black; + // Alignment + verticalAlignment: Text.AlignVCenter; + } + } + + Item { + id: searchBarContainer; + visible: true; + // Size + width: parent.width; + anchors.top: titleBarContainer.bottom; + height: 50; + + + Rectangle { + id: categoriesButton; + anchors.left: parent.left; + anchors.leftMargin: 10; + anchors.verticalCenter: parent.verticalCenter; + height: 34; + width: categoriesText.width + 25; + color: "white"; + radius: 4; + border.width: 1; + border.color: hifi.colors.lightGray; + + + // Categories Text + RalewayRegular { + id: categoriesText; + text: "Categories"; + // Text size + size: 18; + // Style + color: hifi.colors.baseGray; + elide: Text.ElideRight; + horizontalAlignment: Text.AlignHCenter; + verticalAlignment: Text.AlignVCenter; + width: Math.min(textMetrics.width + 25, 110); + // Anchors + anchors.centerIn: parent; + rightPadding: 10; + } + HiFiGlyphs { + id: categoriesDropdownIcon; + text: hifi.glyphs.caratDn; + // Size + size: 34; + // Anchors + anchors.right: parent.right; + anchors.rightMargin: -8; + anchors.verticalCenter: parent.verticalCenter; + horizontalAlignment: Text.AlignHCenter; + // Style + color: hifi.colors.baseGray; + } + + MouseArea { + anchors.fill: parent; + hoverEnabled: enabled; + onClicked: { + categoriesDropdown.visible = !categoriesDropdown.visible; + } + onEntered: categoriesText.color = hifi.colors.baseGrayShadow; + onExited: categoriesText.color = hifi.colors.baseGray; + } + + Component.onCompleted: { + console.log("Getting Marketplace Categories"); + console.log(JSON.stringify(Marketplace)); + Marketplace.getMarketplaceItems(); + } + } + + Rectangle { + id: categoriesContainer; + visible: true; + height: 50 * categoriesModel.count; + width: parent.width; + anchors.top: categoriesButton.bottom; + anchors.left: categoriesButton.left; + color: hifi.colors.white; + + ListModel { + id: categoriesModel; + } + + ListView { + id: dropdownListView; + interactive: false; + anchors.fill: parent; + model: categoriesModel; + delegate: Item { + width: parent.width; + height: 50; + Rectangle { + id: dropDownButton; + color: hifi.colors.white; + width: parent.width; + height: 50; + visible: true; + + RalewaySemiBold { + id: dropDownButtonText; + text: model.displayName; + anchors.fill: parent; + anchors.topMargin: 2; + anchors.leftMargin: 12; + color: hifi.colors.baseGray; + horizontalAlignment: Text.AlignLeft; + verticalAlignment: Text.AlignVCenter; + size: 18; + } + + MouseArea { + anchors.fill: parent; + hoverEnabled: true; + propagateComposedEvents: false; + onEntered: { + dropDownButton.color = hifi.colors.blueHighlight; + } + onExited: { + dropDownButton.color = hifi.colors.white; + } + onClicked: { + root.category_index = index; + dropdownContainer.visible = false; + } + } + } + Rectangle { + height: 2; + width: parent.width; + color: hifi.colors.lightGray; + visible: model.separator + } + } + } + } + + // or + RalewayRegular { + id: orText; + text: "or"; + // Text size + size: 18; + // Style + color: hifi.colors.baseGray; + elide: Text.ElideRight; + horizontalAlignment: Text.AlignHCenter; + verticalAlignment: Text.AlignVCenter; + width: Math.min(textMetrics.width + 25, 110); + // Anchors + anchors.left: categoriesButton.right; + rightPadding: 10; + leftPadding: 10; + anchors.verticalCenter: parent.verticalCenter; + } + HifiControlsUit.TextField { + id: searchField; + anchors.verticalCenter: parent.verticalCenter; + anchors.right: parent.right; + anchors.left: orText.right; + anchors.rightMargin: 10; + height: 34; + isSearchField: true; + colorScheme: hifi.colorSchemes.faintGray; + + + font.family: "Fira Sans" + font.pixelSize: hifi.fontSizes.textFieldInput; + + placeholderText: "Search Marketplace"; + + TextMetrics { + id: primaryFilterTextMetrics; + font.family: "FiraSans Regular"; + font.pixelSize: hifi.fontSizes.textFieldInput; + font.capitalization: Font.AllUppercase; + text: root.primaryFilter_displayName; + } + + // workaround for https://bugreports.qt.io/browse/QTBUG-49297 + Keys.onPressed: { + switch (event.key) { + case Qt.Key_Return: + case Qt.Key_Enter: + event.accepted = true; + + // emit accepted signal manually + if (acceptableInput) { + root.accepted(); + root.forceActiveFocus(); + } + break; + case Qt.Key_Backspace: + if (textField.text === "") { + primaryFilter_index = -1; + } + break; + } + } + + onAccepted: { + root.forceActiveFocus(); + } + + onActiveFocusChanged: { + if (!activeFocus) { + dropdownContainer.visible = false; + } + } + + } + } + } + // + // HEADER BAR END + // + DropShadow { + anchors.fill: header; + horizontalOffset: 0; + verticalOffset: 4; + radius: 4.0; + samples: 9 + color: Qt.rgba(0, 0, 0, 0.25); + source: header; + visible: header.visible; + } +} diff --git a/interface/src/Application.cpp b/interface/src/Application.cpp index 7ed05611ee..1ec9c93e12 100644 --- a/interface/src/Application.cpp +++ b/interface/src/Application.cpp @@ -232,6 +232,7 @@ #include "commerce/Ledger.h" #include "commerce/Wallet.h" #include "commerce/QmlCommerce.h" +#include "commerce/QmlMarketplace.h" #include "ResourceRequestObserver.h" #include "webbrowser/WebBrowserSuggestionsEngine.h" @@ -2913,6 +2914,14 @@ void Application::initializeUi() { QUrl{ "hifi/dialogs/security/SecurityImageModel.qml" }, QUrl{ "hifi/dialogs/security/SecurityImageSelection.qml" }, }, commerceCallback); + + QmlContextCallback marketplaceCallback = [](QQmlContext* context) { + context->setContextProperty("Marketplace", new QmlMarketplace()); + }; + OffscreenQmlSurface::addWhitelistContextHandler({ + QUrl{ "hifi/commerce/marketplace/Marketplace.qml" }, + }, marketplaceCallback); + QmlContextCallback ttsCallback = [](QQmlContext* context) { context->setContextProperty("TextToSpeech", DependencyManager::get<TTSScriptingInterface>().data()); }; diff --git a/interface/src/commerce/QmlMarketplace.cpp b/interface/src/commerce/QmlMarketplace.cpp new file mode 100644 index 0000000000..99d3bb1ae6 --- /dev/null +++ b/interface/src/commerce/QmlMarketplace.cpp @@ -0,0 +1,131 @@ +// +// QmlMarketplace.cpp +// interface/src/commerce +// +// Guard for safe use of Marketplace by authorized QML. +// +// Created by Roxanne Skelly on 1/18/19. +// Copyright 2019 High Fidelity, Inc. +// +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// + + +#include "QmlMarketplace.h" +#include "CommerceLogging.h" +#include "Application.h" +#include "DependencyManager.h" +#include <Application.h> +#include <UserActivityLogger.h> +#include <ScriptEngines.h> +#include <ui/TabletScriptingInterface.h> +#include "scripting/HMDScriptingInterface.h" + +#define ApiHandler(NAME) void QmlMarketplace::NAME##Success(QNetworkReply* reply) { emit NAME##Result(apiResponse(#NAME, reply)); } +#define FailHandler(NAME) void QmlMarketplace::NAME##Failure(QNetworkReply* reply) { emit NAME##Result(failResponse(#NAME, reply)); } +#define Handler(NAME) ApiHandler(NAME) FailHandler(NAME) +Handler(getMarketplaceItems) +Handler(getMarketplaceItem) +Handler(marketplaceItemLike) +Handler(getMarketplaceCategories) + +QmlMarketplace::QmlMarketplace() { +} + +void QmlMarketplace::openMarketplace(const QString& marketplaceItemId) { + auto tablet = dynamic_cast<TabletProxy*>( + DependencyManager::get<TabletScriptingInterface>()->getTablet("com.highfidelity.interface.tablet.system")); + tablet->loadQMLSource("hifi/commerce/marketplace/Marketplace.qml"); + DependencyManager::get<HMDScriptingInterface>()->openTablet(); + if (!marketplaceItemId.isEmpty()) { + tablet->sendToQml(QVariantMap({ { "method", "marketplace_openItem" }, { "itemId", marketplaceItemId } })); + } +} + +void QmlMarketplace::getMarketplaceItems( + const QString& q, + const QString& view, + const QString& category, + const QString& adminFilter, + const QString& adminFilterCost, + const QString& sort, + const bool isFree, + const int& page, + const int& perPage) { + + QString endpoint = "items"; + QJsonObject request; + request["q"] = q; + request["view"] = view; + request["category"] = category; + request["adminFilter"] = adminFilter; + request["adminFilterCost"] = adminFilterCost; + request["sort"] = sort; + request["isFree"] = isFree; + request["page"] = page; + request["perPage"] = perPage; + send(endpoint, "getMarketplaceItemsSuccess", "getMarketplaceItemsFailure", QNetworkAccessManager::GetOperation, AccountManagerAuth::Optional, request); +} + +void QmlMarketplace::getMarketplaceItem(const QString& marketplaceItemId) { + QString endpoint = QString("items/") + marketplaceItemId; + QJsonObject request; + send(endpoint, "getMarketplaceItemSuccess", "getMarketplaceItemFailure", QNetworkAccessManager::GetOperation, AccountManagerAuth::Optional, request); +} + +void QmlMarketplace::marketplaceItemLike(const QString& marketplaceItemId, const bool like) { + QString endpoint = QString("items/") + marketplaceItemId + "/like"; + QJsonObject request; + send(endpoint, "marketplaceItemLikeSuccess", "marketplaceItemLikeFailure", like ? QNetworkAccessManager::PutOperation : QNetworkAccessManager::DeleteOperation, AccountManagerAuth::Required, request); +} + +void QmlMarketplace::getMarketplaceCategories() { + QString endpoint = "categories"; + QJsonObject request; + send(endpoint, "getMarketplaceCategoriesSuccess", "getMarketplaceCategoriesFailure", QNetworkAccessManager::GetOperation, AccountManagerAuth::None, request); +} + + +void QmlMarketplace::send(const QString& endpoint, const QString& success, const QString& fail, QNetworkAccessManager::Operation method, AccountManagerAuth::Type authType, QJsonObject request) { + auto accountManager = DependencyManager::get<AccountManager>(); + const QString URL = "/api/v1/marketplace/"; + JSONCallbackParameters callbackParams(this, success, fail); +#if defined(DEV_BUILD) // Don't expose user's personal data in the wild. But during development this can be handy. + qCInfo(commerce) << "Sending" << QJsonDocument(request).toJson(QJsonDocument::Compact); +#endif + accountManager->sendRequest(URL + endpoint, + authType, + method, + callbackParams, + QJsonDocument(request).toJson()); +} + +QJsonObject QmlMarketplace::apiResponse(const QString& label, QNetworkReply* reply) { + QByteArray response = reply->readAll(); + QJsonObject data = QJsonDocument::fromJson(response).object(); +#if defined(DEV_BUILD) // Don't expose user's personal data in the wild. But during development this can be handy. + qInfo(commerce) << label << "response" << QJsonDocument(data).toJson(QJsonDocument::Compact); +#endif + return data; +} + +// Non-200 responses are not json: +QJsonObject QmlMarketplace::failResponse(const QString& label, QNetworkReply* reply) { + QString response = reply->readAll(); + qWarning(commerce) << "FAILED" << label << response; + + // tempResult will be NULL if the response isn't valid JSON. + QJsonDocument tempResult = QJsonDocument::fromJson(response.toLocal8Bit()); + if (tempResult.isNull()) { + QJsonObject result + { + { "status", "fail" }, + { "message", response } + }; + return result; + } + else { + return tempResult.object(); + } +} \ No newline at end of file diff --git a/interface/src/commerce/QmlMarketplace.h b/interface/src/commerce/QmlMarketplace.h new file mode 100644 index 0000000000..95a1aa3911 --- /dev/null +++ b/interface/src/commerce/QmlMarketplace.h @@ -0,0 +1,68 @@ +// +// QmlMarketplace.h +// interface/src/commerce +// +// Guard for safe use of Marketplace by authorized QML. +// +// Created by Roxanne Skelly on 1/18/19. +// Copyright 2019 High Fidelity, Inc. +// +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// + +#pragma once +#ifndef hifi_QmlMarketplace_h +#define hifi_QmlMarketplace_h + +#include <QJsonObject> + +#include <QPixmap> +#include <QtNetwork/QNetworkReply> +#include "AccountManager.h" + +class QmlMarketplace : public QObject { + Q_OBJECT + +public: + QmlMarketplace(); + +public slots: + void getMarketplaceItemsSuccess(QNetworkReply* reply); + void getMarketplaceItemsFailure(QNetworkReply* reply); + void getMarketplaceItemSuccess(QNetworkReply* reply); + void getMarketplaceItemFailure(QNetworkReply* reply); + void getMarketplaceCategoriesSuccess(QNetworkReply* reply); + void getMarketplaceCategoriesFailure(QNetworkReply* reply); + void marketplaceItemLikeSuccess(QNetworkReply* reply); + void marketplaceItemLikeFailure(QNetworkReply* reply); + +protected: + Q_INVOKABLE void openMarketplace(const QString& marketplaceItemId = QString()); + Q_INVOKABLE void getMarketplaceItems( + const QString& q = QString(), + const QString& view = QString(), + const QString& category = QString(), + const QString& adminFilter = QString("published"), + const QString& adminFilterCost = QString(), + const QString& sort = QString(), + const bool isFree = false, + const int& page = 1, + const int& perPage = 20); + Q_INVOKABLE void getMarketplaceItem(const QString& marketplaceItemId); + Q_INVOKABLE void marketplaceItemLike(const QString& marketplaceItemId, const bool like = true); + Q_INVOKABLE void getMarketplaceCategories(); + +signals: + void getMarketplaceItemsResult(QJsonObject result); + void getMarketplaceItemResult(QJsonObject result); + void getMarketplaceCategoriesResult(QJsonObject result); + void marketplaceItemLikeResult(QJsonObject result); + +private: + void send(const QString& endpoint, const QString& success, const QString& fail, QNetworkAccessManager::Operation method, AccountManagerAuth::Type authType, QJsonObject request); + QJsonObject apiResponse(const QString& label, QNetworkReply* reply); + QJsonObject failResponse(const QString& label, QNetworkReply* reply); +}; + +#endif // hifi_QmlMarketplace_h diff --git a/scripts/system/marketplaces/marketplace.js b/scripts/system/marketplaces/marketplace.js index d3e5c96739..70680acd1d 100644 --- a/scripts/system/marketplaces/marketplace.js +++ b/scripts/system/marketplaces/marketplace.js @@ -1,8 +1,8 @@ // // marketplace.js // -// Created by Eric Levin on 8 Jan 2016 -// Copyright 2016 High Fidelity, Inc. +// Created by Roxanne Skelly on 1/18/2019 +// Copyright 2019 High Fidelity, Inc. // // Distributed under the Apache License, Version 2.0. // See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html @@ -10,108 +10,27 @@ (function() { // BEGIN LOCAL_SCOPE -/* global WebTablet */ -Script.include("../libraries/WebTablet.js"); +var AppUi = Script.require('appUi'); -var toolIconUrl = Script.resolvePath("../assets/images/tools/"); +var BUTTON_NAME = "MARKET"; +var MARKETPLACE_QML_SOURCE = "hifi/commerce/marketplace/Marketplace.qml"; +var ui; +function startup() { -var MARKETPLACE_URL = Account.metaverseServerURL + "/marketplace"; -var marketplaceWindow = new OverlayWebWindow({ - title: "Marketplace", - source: "about:blank", - width: 900, - height: 700, - visible: false -}); - -var toolHeight = 50; -var toolWidth = 50; -var TOOLBAR_MARGIN_Y = 0; -var marketplaceVisible = false; -var marketplaceWebTablet; - -// We persist avatarEntity data in the .ini file, and reconsistitute it on restart. -// To keep things consistent, we pickle the tablet data in Settings, and kill any existing such on restart and domain change. -var persistenceKey = "io.highfidelity.lastDomainTablet"; - -function shouldShowWebTablet() { - var rightPose = Controller.getPoseValue(Controller.Standard.RightHand); - var leftPose = Controller.getPoseValue(Controller.Standard.LeftHand); - var hasHydra = !!Controller.Hardware.Hydra; - return HMD.active && (leftPose.valid || rightPose.valid || hasHydra); + ui = new AppUi({ + buttonName: BUTTON_NAME, + sortOrder: 10, + home: MARKETPLACE_QML_SOURCE + }); } -function showMarketplace(marketplaceID) { - var url = MARKETPLACE_URL; - if (marketplaceID) { - url = url + "/items/" + marketplaceID; - } - tablet.gotoWebScreen(url); - marketplaceVisible = true; - UserActivityLogger.openedMarketplace(); +function shutdown() { } -function hideTablet(tablet) { - if (!tablet) { - return; - } - updateButtonState(false); - tablet.unregister(); - tablet.destroy(); - marketplaceWebTablet = null; - Settings.setValue(persistenceKey, ""); -} -function clearOldTablet() { // If there was a tablet from previous domain or session, kill it and let it be recreated - -} -function hideMarketplace() { - if (marketplaceWindow.visible) { - marketplaceWindow.setVisible(false); - marketplaceWindow.setURL("about:blank"); - } else if (marketplaceWebTablet) { - - } - marketplaceVisible = false; -} - -function toggleMarketplace() { - if (marketplaceVisible) { - hideMarketplace(); - } else { - showMarketplace(); - } -} - -var tablet = Tablet.getTablet("com.highfidelity.interface.tablet.system"); - -var browseExamplesButton = tablet.addButton({ - icon: "icons/tablet-icons/market-i.svg", - text: "MARKET" -}); - -function updateButtonState(visible) { - -} -function onMarketplaceWindowVisibilityChanged() { - updateButtonState(marketplaceWindow.visible); - marketplaceVisible = marketplaceWindow.visible; -} - -function onClick() { - toggleMarketplace(); -} - -browseExamplesButton.clicked.connect(onClick); -marketplaceWindow.visibleChanged.connect(onMarketplaceWindowVisibilityChanged); - -clearOldTablet(); // Run once at startup, in case there's anything laying around from a crash. -// We could also optionally do something like Window.domainChanged.connect(function () {Script.setTimeout(clearOldTablet, 2000)}), -// but the HUD version stays around, so lets do the same. - -Script.scriptEnding.connect(function () { - browseExamplesButton.clicked.disconnect(onClick); - tablet.removeButton(browseExamplesButton); - marketplaceWindow.visibleChanged.disconnect(onMarketplaceWindowVisibilityChanged); -}); +// +// Run the functions. +// +startup(); +Script.scriptEnding.connect(shutdown); }()); // END LOCAL_SCOPE diff --git a/scripts/system/marketplaces/marketplaces.js b/scripts/system/marketplaces/marketplaces.js index e4891a9bae..db3b2e2107 100644 --- a/scripts/system/marketplaces/marketplaces.js +++ b/scripts/system/marketplaces/marketplaces.js @@ -769,16 +769,14 @@ var onTabletScreenChanged = function onTabletScreenChanged(type, url) { var BUTTON_NAME = "MARKET"; -var MARKETPLACE_URL = METAVERSE_SERVER_URL + "/marketplace"; -// Append "?" if necessary to signal injected script that it's the initial page. -var MARKETPLACE_URL_INITIAL = MARKETPLACE_URL + (MARKETPLACE_URL.indexOf("?") > -1 ? "" : "?"); +var MARKETPLACE_QML_SOURCE = "hifi/commerce/marketplace/Marketplace.qml"; + var ui; function startup() { ui = new AppUi({ buttonName: BUTTON_NAME, sortOrder: 9, - inject: MARKETPLACES_INJECT_SCRIPT_URL, - home: MARKETPLACE_URL_INITIAL, + home: MARKETPLACE_QML_SOURCE, onScreenChanged: onTabletScreenChanged, onMessage: onQmlMessageReceived }); From c1ff51a02d5816d1094e98dfdb4692e474b53556 Mon Sep 17 00:00:00 2001 From: Roxanne Skelly <roxanne@highfidelity.io> Date: Thu, 24 Jan 2019 17:20:42 -0800 Subject: [PATCH 02/18] QML Marketplace Checkpoint full functionality --- .../hifi/commerce/marketplace/Marketplace.qml | 775 +++++++++++++++--- .../commerce/marketplace/MarketplaceItem.qml | 473 +++++++++++ .../marketplace/MarketplaceListItem.qml | 278 +++++++ .../hifi/commerce/marketplace/SortButton.qml | 87 ++ interface/src/Application.cpp | 2 +- interface/src/commerce/QmlMarketplace.cpp | 42 +- interface/src/commerce/QmlMarketplace.h | 2 +- libraries/networking/src/AccountManager.cpp | 8 +- libraries/networking/src/AccountManager.h | 2 +- scripts/system/commerce/wallet.js | 23 +- scripts/system/html/js/marketplacesInject.js | 13 +- scripts/system/marketplaces/marketplaces.js | 47 +- 12 files changed, 1592 insertions(+), 160 deletions(-) create mode 100644 interface/resources/qml/hifi/commerce/marketplace/MarketplaceItem.qml create mode 100644 interface/resources/qml/hifi/commerce/marketplace/MarketplaceListItem.qml create mode 100644 interface/resources/qml/hifi/commerce/marketplace/SortButton.qml diff --git a/interface/resources/qml/hifi/commerce/marketplace/Marketplace.qml b/interface/resources/qml/hifi/commerce/marketplace/Marketplace.qml index fb8b410e3f..e5ce431de8 100644 --- a/interface/resources/qml/hifi/commerce/marketplace/Marketplace.qml +++ b/interface/resources/qml/hifi/commerce/marketplace/Marketplace.qml @@ -30,24 +30,72 @@ Rectangle { property string activeView: "initialize"; property bool keyboardRaised: false; - property int category_index: -1; - property alias categoryChoices: categoriesModel; + property int currentSortIndex: 0; + property string sortString: ""; + property string categoryString: ""; + property string searchString: ""; anchors.fill: (typeof parent === undefined) ? undefined : parent; + function getMarketplaceItems() { + marketplaceItemView.visible = false; + itemsList.visible = true; + marketBrowseModel.getFirstPage(); + } + Component.onDestruction: { KeyboardScriptingInterface.raised = false; } Connections { - target: Marketplace; + target: MarketplaceScriptingInterface; onGetMarketplaceCategoriesResult: { if (result.status !== 'success') { console.log("Failed to get Marketplace Categories", result.data.message); } else { - - } + categoriesModel.clear(); + categoriesModel.append({ + id: -1, + name: "Everything" + }); + result.data.items.forEach(function(category) { + categoriesModel.append({ + id: category.id, + name: category.name + }); + }); + } + getMarketplaceItems(); + } + onGetMarketplaceItemsResult: { + marketBrowseModel.handlePage(result.status !== "success" && result.message, result); + } + + onGetMarketplaceItemResult: { + if (result.status !== 'success') { + console.log("Failed to get Marketplace Item", result.data.message); + } else { + + console.log(JSON.stringify(result.data)); + marketplaceItem.item_id = result.data.id; + marketplaceItem.image_url = result.data.thumbnail_url; + marketplaceItem.name = result.data.title; + marketplaceItem.likes = result.data.likes; + marketplaceItem.liked = result.data.has_liked; + marketplaceItem.creator = result.data.creator; + marketplaceItem.categories = result.data.categories; + marketplaceItem.price = result.data.cost; + marketplaceItem.description = result.data.description; + marketplaceItem.attributions = result.data.attributions; + marketplaceItem.license = result.data.license; + marketplaceItem.available = result.data.availability == "available"; + marketplaceItem.created_at = result.data.created_at; + console.log("HEIGHT: " + marketplaceItemContent.height); + marketplaceItemScrollView.contentHeight = marketplaceItemContent.height; + itemsList.visible = false; + marketplaceItemView.visible = true; + } } } @@ -60,15 +108,36 @@ Rectangle { // // HEADER BAR START // - Item { + + + Rectangle { + id: headerShadowBase; + anchors.fill: header; + color: "white"; + } + DropShadow { + anchors.fill: headerShadowBase; + source: headerShadowBase; + verticalOffset: 4; + horizontalOffset: 4; + radius: 6; + samples: 9; + color: hifi.colors.baseGrayShadow; + z:5; + } + Rectangle { id: header; visible: true; - width: parent.width; anchors.left: parent.left; anchors.top: parent.top; anchors.right: parent.right; - - Item { + anchors.topMargin: -1; + anchors.leftMargin: -1; + anchors.rightMargin: -1; + height: childrenRect.height+5; + z: 5; + + Rectangle { id: titleBarContainer; visible: true; // Size @@ -77,13 +146,14 @@ Rectangle { // Anchors anchors.left: parent.left; anchors.top: parent.top; - - // Wallet icon + + + // Marketplace icon Image { - id: walletIcon; + id: marketplaceIcon; source: "../../../../images/hifi-logo-blackish.svg"; height: 20 - width: walletIcon.height; + width: marketplaceIcon.height; anchors.left: parent.left; anchors.leftMargin: 8; anchors.verticalCenter: parent.verticalCenter; @@ -98,7 +168,7 @@ Rectangle { size: hifi.fontSizes.overlayTitle; // Anchors anchors.top: parent.top; - anchors.left: walletIcon.right; + anchors.left: marketplaceIcon.right; anchors.leftMargin: 6; anchors.bottom: parent.bottom; width: paintedWidth; @@ -109,9 +179,10 @@ Rectangle { } } - Item { + Rectangle { id: searchBarContainer; visible: true; + clip: false; // Size width: parent.width; anchors.top: titleBarContainer.bottom; @@ -125,10 +196,10 @@ Rectangle { anchors.verticalCenter: parent.verticalCenter; height: 34; width: categoriesText.width + 25; - color: "white"; + color: hifi.colors.white; radius: 4; border.width: 1; - border.color: hifi.colors.lightGray; + border.color: hifi.colors.lightGrayText; // Categories Text @@ -136,9 +207,9 @@ Rectangle { id: categoriesText; text: "Categories"; // Text size - size: 18; + size: 14; // Style - color: hifi.colors.baseGray; + color: hifi.colors.lightGrayText; elide: Text.ElideRight; horizontalAlignment: Text.AlignHCenter; verticalAlignment: Text.AlignVCenter; @@ -166,6 +237,8 @@ Rectangle { hoverEnabled: enabled; onClicked: { categoriesDropdown.visible = !categoriesDropdown.visible; + categoriesButton.color = categoriesDropdown.visible ? hifi.colors.lightGray : hifi.colors.white; + categoriesDropdown.forceActiveFocus(); } onEntered: categoriesText.color = hifi.colors.baseGrayShadow; onExited: categoriesText.color = hifi.colors.baseGray; @@ -173,77 +246,10 @@ Rectangle { Component.onCompleted: { console.log("Getting Marketplace Categories"); - console.log(JSON.stringify(Marketplace)); - Marketplace.getMarketplaceItems(); + MarketplaceScriptingInterface.getMarketplaceCategories(); } } - - Rectangle { - id: categoriesContainer; - visible: true; - height: 50 * categoriesModel.count; - width: parent.width; - anchors.top: categoriesButton.bottom; - anchors.left: categoriesButton.left; - color: hifi.colors.white; - - ListModel { - id: categoriesModel; - } - - ListView { - id: dropdownListView; - interactive: false; - anchors.fill: parent; - model: categoriesModel; - delegate: Item { - width: parent.width; - height: 50; - Rectangle { - id: dropDownButton; - color: hifi.colors.white; - width: parent.width; - height: 50; - visible: true; - - RalewaySemiBold { - id: dropDownButtonText; - text: model.displayName; - anchors.fill: parent; - anchors.topMargin: 2; - anchors.leftMargin: 12; - color: hifi.colors.baseGray; - horizontalAlignment: Text.AlignLeft; - verticalAlignment: Text.AlignVCenter; - size: 18; - } - - MouseArea { - anchors.fill: parent; - hoverEnabled: true; - propagateComposedEvents: false; - onEntered: { - dropDownButton.color = hifi.colors.blueHighlight; - } - onExited: { - dropDownButton.color = hifi.colors.white; - } - onClicked: { - root.category_index = index; - dropdownContainer.visible = false; - } - } - } - Rectangle { - height: 2; - width: parent.width; - color: hifi.colors.lightGray; - visible: model.separator - } - } - } - } - + // or RalewayRegular { id: orText; @@ -283,7 +289,6 @@ Rectangle { font.family: "FiraSans Regular"; font.pixelSize: hifi.fontSizes.textFieldInput; font.capitalization: Font.AllUppercase; - text: root.primaryFilter_displayName; } // workaround for https://bugreports.qt.io/browse/QTBUG-49297 @@ -295,20 +300,22 @@ Rectangle { // emit accepted signal manually if (acceptableInput) { - root.accepted(); - root.forceActiveFocus(); + searchField.accepted(); + searchField.forceActiveFocus(); } break; case Qt.Key_Backspace: - if (textField.text === "") { + if (searchField.text === "") { primaryFilter_index = -1; } break; } } - + onTextChanged: root.searchString = text; onAccepted: { - root.forceActiveFocus(); + root.searchString = searchField.text; + getMarketplaceItems(); + searchField.forceActiveFocus(); } onActiveFocusChanged: { @@ -320,17 +327,579 @@ Rectangle { } } } + // // HEADER BAR END // - DropShadow { - anchors.fill: header; - horizontalOffset: 0; - verticalOffset: 4; - radius: 4.0; - samples: 9 - color: Qt.rgba(0, 0, 0, 0.25); - source: header; - visible: header.visible; + + // + // CATEGORIES LIST START + // + Item { + id: categoriesDropdown; + anchors.fill: parent; + visible: false; + z: 10; + + MouseArea { + anchors.fill: parent; + propagateComposedEvents: true; + onClicked: { + categoriesDropdown.visible = false; + categoriesButton.color = hifi.colors.white; + } + } + + Rectangle { + anchors.left: parent.left; + anchors.bottom: parent.bottom; + anchors.top: parent.top; + anchors.topMargin: 100; + width: parent.width/3; + color: hifi.colors.white; + + ListModel { + id: categoriesModel; + } + + ListView { + id: categoriesListView; + anchors.fill: parent; + anchors.rightMargin: 10; + width: parent.width; + clip: true; + + model: categoriesModel; + delegate: ItemDelegate { + height: 34; + width: parent.width; + clip: true; + contentItem: Rectangle { + id: categoriesItem; + anchors.fill: parent; + color: hifi.colors.white; + visible: true; + + RalewayRegular { + id: categoriesItemText; + text: model.name; + anchors.leftMargin: 15; + anchors.fill:parent; + color: ListView.isCurrentItem ? hifi.colors.lightBlueHighlight : hifi.colors.baseGray; + horizontalAlignment: Text.AlignLeft; + verticalAlignment: Text.AlignVCenter; + size: 14; + } + } + MouseArea { + anchors.fill: parent; + hoverEnabled: true; + propagateComposedEvents: false; + z: 10; + onEntered: { + categoriesItem.color = ListView.isCurrentItem ? hifi.colors.white : hifi.colors.lightBlueHighlight; + } + onExited: { + categoriesItem.color = ListView.isCurrentItem ? hifi.colors.lightBlueHighlight : hifi.colors.white; + } + onClicked: { + categoriesListView.currentIndex = index; + categoriesText.text = categoriesItemText.text; + categoriesDropdown.visible = false; + categoriesButton.color = hifi.colors.white; + root.categoryString = categoriesItemText.text; + getMarketplaceItems(); + } + } + + } + ScrollBar.vertical: ScrollBar { + parent: categoriesListView.parent; + anchors.top: categoriesListView.top; + anchors.bottom: categoriesListView.bottom; + anchors.left: categoriesListView.right; + contentItem.opacity: 1; + } + } + } + } + // + // CATEGORIES LIST END + // + + // + // ITEMS LIST START + // + Item { + id: itemsList; + anchors.fill: parent; + anchors.topMargin: 100; + anchors.bottomMargin: 50; + visible: true; + + HifiModels.PSFListModel { + id: marketBrowseModel; + itemsPerPage: 7; + listModelName: 'marketBrowse'; + listView: marketBrowseList; + getPage: function () { + + MarketplaceScriptingInterface.getMarketplaceItems( + root.searchString == "" ? undefined : root.searchString, + "", + root.categoryString.toLowerCase(), + "", + "", + root.sortString, + false, + marketBrowseModel.currentPageToRetrieve, + marketBrowseModel.itemsPerPage + ); + } + processPage: function(data) { + console.log(JSON.stringify(data)); + data.items.forEach(function (item) { + console.log(JSON.stringify(item)); + }); + return data.items; + } + } + ListView { + id: marketBrowseList; + model: marketBrowseModel; + // Anchors + anchors.fill: parent; + anchors.rightMargin: 10; + orientation: ListView.Vertical; + focus: true; + clip: true; + + delegate: MarketplaceListItem { + item_id: model.id; + image_url:model.thumbnail_url; + name: model.title; + likes: model.likes; + liked: model.has_liked; + creator: model.creator; + category: model.primary_category; + price: model.cost; + available: model.availability == "available"; + anchors.topMargin: 10; + + + Component.onCompleted: { + console.log("Rendering marketplace list item " + model.id); + console.log(marketBrowseModel.count); + } + + onShowItem: { + MarketplaceScriptingInterface.getMarketplaceItem(item_id); + } + + onBuy: { + sendToScript({method: 'marketplace_checkout', itemId: item_id}); + } + + onCategoryClicked: { + root.categoryString = category; + categoriesText.text = category; + getMarketplaceItems(); + } + + onRequestReload: getMarketplaceItems(); + } + ScrollBar.vertical: ScrollBar { + parent: marketBrowseList.parent; + anchors.top: marketBrowseList.top; + anchors.bottom: marketBrowseList.bottom; + anchors.left: marketBrowseList.right; + contentItem.opacity: 1; + } + headerPositioning: ListView.InlineHeader; + header: Item { + id: itemsHeading; + + height: childrenRect.height; + width: parent.width; + + Item { + id: breadcrumbs; + anchors.left: parent.left; + anchors.right: parent.right; + height: 34; + visible: categoriesListView.currentIndex >= 0; + RalewayRegular { + id: categoriesItemText; + text: "Home /"; + anchors.leftMargin: 15; + anchors.fill:parent; + color: hifi.colors.blueHighlight; + horizontalAlignment: Text.AlignLeft; + verticalAlignment: Text.AlignVCenter; + size: 18; + } + MouseArea { + anchors.fill: parent; + onClicked: { + categoriesListView.currentIndex = -1; + categoriesText.text = "Categories"; + root.categoryString = ""; + getMarketplaceItems(); + } + } + } + + Item { + id: searchScope; + anchors.top: breadcrumbs.bottom; + anchors.left: parent.left; + anchors.right: parent.right; + height: 50; + + RalewaySemiBold { + id: searchScopeText; + text: "Featured"; + anchors.leftMargin: 15; + anchors.fill:parent; + anchors.topMargin: 10; + color: hifi.colors.baseGray; + horizontalAlignment: Text.AlignLeft; + verticalAlignment: Text.AlignVCenter; + size: 22; + } + } + Item { + id: sort; + anchors.top: searchScope.bottom; + anchors.left: parent.left; + anchors.right: parent.right; + anchors.topMargin: 10; + anchors.leftMargin: 15; + height: childrenRect.height; + RalewayRegular { + id: sortText; + text: "Sort:"; + anchors.leftMargin: 15; + anchors.rightMargin: 20; + height: 34; + color: hifi.colors.lightGrayText; + horizontalAlignment: Text.AlignLeft; + verticalAlignment: Text.AlignVCenter; + size: 14; + } + + Rectangle { + radius: 4; + border.width: 1; + border.color: hifi.colors.faintGray; + anchors.left: sortText.right; + anchors.top: parent.top; + width: 322; + height: 36; + anchors.leftMargin: 20; + + + ListModel { + id: sortModel; + ListElement { + name: "Name"; + glyph: ";"; + sortString: "alpha"; + } + ListElement { + name: "Date"; + glyph: ";"; + sortString: "recent"; + } + ListElement { + name: "Popular"; + glyph: ";"; + sortString: "likes"; + } + ListElement { + name: "My Likes"; + glyph: ";"; + sortString: "my_likes"; + } + } + + ListView { + id: sortListView; + anchors.top: parent.top; + anchors.bottom: parent.bottom; + anchors.left: parent.left; + anchors.topMargin:1; + anchors.bottomMargin:1; + anchors.leftMargin:1; + anchors.rightMargin:1; + width: childrenRect.width; + height: 34; + orientation: ListView.Horizontal; + focus: true; + clip: true; + highlightFollowsCurrentItem: false; + + delegate: SortButton { + width: 80; + height: parent.height; + glyph: model.glyph; + text: model.name; + + checked: { + ListView.isCurrentItem; + } + onClicked: { + root.currentSortIndex = index; + sortListView.positionViewAtIndex(index, ListView.Beginning); + sortListView.currentIndex = index; + root.sortString = model.sortString; + getMarketplaceItems(); + } + } + highlight: Rectangle { + width: 80; + height: parent.height; + color: hifi.colors.faintGray; + x: sortListView.currentItem.x; + } + model: sortModel; + } + } + } + } + } + } + + // + // ITEMS LIST END + // + + // + // ITEM START + // + Item { + id: marketplaceItemView; + anchors.fill: parent; + width: parent.width; + anchors.topMargin: 120; + visible: false; + + ScrollView { + id: marketplaceItemScrollView; + anchors.fill: parent; + + clip: true; + ScrollBar.vertical.policy: ScrollBar.AlwaysOn; + contentWidth: parent.width; + + Rectangle { + id: marketplaceItemContent; + width: parent.width; + height: childrenRect.height + 100; + + // Title Bar text + RalewaySemiBold { + id: backText; + text: "Back"; + // Text size + size: hifi.fontSizes.overlayTitle; + // Anchors + anchors.top: parent.top; + anchors.left: parent.left + anchors.leftMargin: 15; + anchors.bottomMargin: 10; + width: paintedWidth; + height: paintedHeight; + // Style + color: hifi.colors.blueHighlight; + // Alignment + verticalAlignment: Text.AlignVCenter; + } + MouseArea { + anchors.fill: backText; + + onClicked: { + getMarketplaceItems(); + } + } + + MarketplaceItem { + id: marketplaceItem; + anchors.top: backText.bottom; + width: parent.width; + height: childrenRect.height; + anchors.topMargin: 15; + + onBuy: { + sendToScript({method: 'marketplace_checkout', itemId: item_id}); + } + + onShowLicense: { + licenseInfoWebView.url = url; + licenseInfo.visible = true; + } + onCategoryClicked: { + root.categoryString = category; + categoriesText.text = category; + getMarketplaceItems(); + } + } + } + } + } + // + // ITEM END + // + + // + // FOOTER START + // + + Rectangle { + id: footer; + anchors.bottom: parent.bottom; + anchors.left: parent.left; + anchors.right: parent.right; + height: 50; + + color: hifi.colors.blueHighlight; + + Item { + anchors.fill: parent; + anchors.rightMargin: 15; + anchors.leftMargin: 15; + + HiFiGlyphs { + id: footerGlyph; + text: hifi.glyphs.info; + // Size + size: 34; + // Anchors + anchors.left: parent.left; + anchors.top: parent.top; + anchors.bottom: parent.bottom; + + anchors.rightMargin: 10; + // Style + color: hifi.colors.white; + // Alignment + horizontalAlignment: Text.AlignHCenter; + verticalAlignment: Text.AlignVCenter; + } + + RalewaySemiBold { + id: footerInfo; + text: "Get items from Clara.io!"; + anchors.left: footerGlyph.right; + anchors.top: parent.top; + anchors.bottom: parent.bottom; + color: hifi.colors.white; + horizontalAlignment: Text.AlignLeft; + verticalAlignment: Text.AlignVCenter; + size: 18; + } + + HifiControlsUit.Button { + text: "SEE ALL MARKETS"; + anchors.right: parent.right; + anchors.top: parent.top; + anchors.bottom: parent.bottom; + anchors.topMargin: 10; + anchors.bottomMargin: 10; + anchors.leftMargin: 10; + anchors.rightMargin: 10; + width: 180; + + onClicked: { + sendToScript({method: 'marketplace_marketplaces'}); + } + } + } + } + + + // + // FOOTER END + // + + + // + // LICENSE START + // + Rectangle { + id: licenseInfo; + anchors.fill: root; + anchors.topMargin: 100; + anchors.bottomMargin: 0; + visible: false; + + + HifiControlsUit.WebView { + id: licenseInfoWebView; + anchors.bottomMargin: 1; + anchors.topMargin: 50; + anchors.leftMargin: 1; + anchors.rightMargin: 1; + anchors.fill: parent; + } + Item { + id: licenseClose; + anchors.top: parent.top; + anchors.right: parent.right; + anchors.topMargin: 10; + anchors.rightMargin: 10; + + width: 30; + height: 30; + HiFiGlyphs { + anchors.fill: parent; + height: 30; + text: hifi.glyphs.close; + // Size + size: 34; + // Anchors + anchors.rightMargin: 0; + anchors.verticalCenter: parent.verticalCenter; + horizontalAlignment: Text.AlignHCenter; + // Style + color: hifi.colors.baseGray; + } + + MouseArea { + anchors.fill: licenseClose; + onClicked: licenseInfo.visible = false; + } + } + } + // + // LICENSE END + // + + // + // Function Name: fromScript() + // + // Relevant Variables: + // None + // + // Arguments: + // message: The message sent from the JavaScript, in this case the Marketplaces JavaScript. + // Messages are in format "{method, params}", like json-rpc. + // + // Description: + // Called when a message is received from a script. + // + + function fromScript(message) { + console.log("FROM SCRIPT " + JSON.stringify(message)); + switch (message.method) { + case 'updateMarketplaceQMLItem': + if (!message.params.itemId) { + console.log("A message with method 'updateMarketplaceQMLItem' was sent without an itemId!"); + return; + } + + MarketplaceScriptingInterface.getMarketplaceItem(message.params.itemId); + break; + } } } diff --git a/interface/resources/qml/hifi/commerce/marketplace/MarketplaceItem.qml b/interface/resources/qml/hifi/commerce/marketplace/MarketplaceItem.qml new file mode 100644 index 0000000000..a7f2991920 --- /dev/null +++ b/interface/resources/qml/hifi/commerce/marketplace/MarketplaceItem.qml @@ -0,0 +1,473 @@ +// +// MarketplaceListItem.qml +// qml/hifi/commerce/marketplace +// +// MarketplaceListItem +// +// Created by Roxanne Skelly on 2019-01-22 +// Copyright 2019 High Fidelity, Inc. +// +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// + +import Hifi 1.0 as Hifi +import QtQuick 2.9 +import QtQuick.Controls 2.2 +import QtGraphicalEffects 1.0 +import stylesUit 1.0 +import controlsUit 1.0 as HifiControlsUit +import "../../../controls" as HifiControls +import "../common" as HifiCommerceCommon +import "qrc:////qml//hifi//models" as HifiModels // Absolute path so the same code works everywhere. +import "../common/sendAsset" +import "../.." as HifiCommon + +Rectangle { + id: root; + property string item_id: ""; + property string image_url: ""; + property string name: ""; + property int likes: 0; + property bool liked: false; + property string creator: ""; + property var categories: []; + property int price: 0; + property var attributions: []; + property string description: ""; + property string license: ""; + property string posted: ""; + property bool available: false; + property string created_at: ""; + + onCategoriesChanged: { + categoriesListModel.clear(); + categories.forEach(function(category) { + console.log("category is " + category); + categoriesListModel.append({"category":category}); + }); + } + + signal buy(); + signal categoryClicked(string category); + signal showLicense(string url); + + HifiConstants { id: hifi; } + + Connections { + target: MarketplaceScriptingInterface; + + onMarketplaceItemLikeResult: { + if (result.status !== 'success') { + console.log("Failed to get Marketplace Categories", result.data.message); + } else { + root.liked = !root.liked; + root.likes = root.liked ? root.likes + 1 : root.likes - 1; + } + } + } + function getFormattedDate(timestamp) { + function addLeadingZero(n) { + return n < 10 ? '0' + n : '' + n; + } + + var a = new Date(timestamp); + + var year = a.getFullYear(); + var month = addLeadingZero(a.getMonth() + 1); + var day = addLeadingZero(a.getDate()); + var hour = a.getHours(); + var drawnHour = hour; + if (hour === 0) { + drawnHour = 12; + } else if (hour > 12) { + drawnHour -= 12; + } + drawnHour = addLeadingZero(drawnHour); + + var amOrPm = "AM"; + if (hour >= 12) { + amOrPm = "PM"; + } + + var min = addLeadingZero(a.getMinutes()); + var sec = addLeadingZero(a.getSeconds()); + return a.toDateString() + " " + drawnHour + ':' + min + amOrPm; + } + + anchors.left: parent.left; + anchors.right: parent.right; + anchors.leftMargin: 15; + anchors.rightMargin: 15; + height: childrenRect.height; + + + Rectangle { + id: header; + anchors.left: parent.left; + anchors.right: parent.right; + anchors.top: parent.top; + + height: 50; + + RalewaySemiBold { + id: nameText; + text: root.name; + // Text size + size: 24; + // Anchors + anchors.top: parent.top; + anchors.left: parent.left; + anchors.bottom: parent.bottom; + width: paintedWidth; + // Style + color: hifi.colors.baseGray; + // Alignment + verticalAlignment: Text.AlignVCenter; + } + Item { + id: likes; + anchors.top: parent.top; + anchors.right: parent.right; + anchors.bottom: parent.bottom; + anchors.rightMargin: 5; + + RalewaySemiBold { + id: heart; + text: "\u2665"; + // Size + size: 20; + // Anchors + anchors.top: parent.top; + anchors.right: parent.right; + anchors.rightMargin: 0; + anchors.verticalCenter: parent.verticalCenter; + horizontalAlignment: Text.AlignHCenter; + // Style + color: root.liked ? hifi.colors.redAccent : hifi.colors.lightGrayText; + } + + RalewaySemiBold { + id: likesText; + text: root.likes; + // Text size + size: hifi.fontSizes.overlayTitle; + // Anchors + anchors.top: parent.top; + anchors.right: heart.left; + anchors.rightMargin: 5; + anchors.leftMargin: 15; + anchors.bottom: parent.bottom; + width: paintedWidth; + // Style + color: hifi.colors.baseGray; + // Alignment + verticalAlignment: Text.AlignVCenter; + } + MouseArea { + anchors.left: likesText.left; + anchors.right: heart.right; + anchors.top: likesText.top; + anchors.bottom: likesText.bottom; + + onClicked: { + MarketplaceScriptingInterface.marketplaceItemLike(root.item_id, !root.liked); + } + } + } + } + Image { + id: itemImage; + source: root.image_url; + anchors.top: header.bottom; + anchors.left: parent.left; + anchors.right: parent.right; + height: width*0.5625 + fillMode: Image.PreserveAspectCrop; + } + Item { + id: footer; + anchors.left: parent.left; + anchors.right: parent.right; + anchors.top: itemImage.bottom; + height: childrenRect.height; + + HifiControlsUit.Button { + id: buyButton; + text: root.available ? (root.price ? root.price : "FREE") : "UNAVAILABLE (not for sale)"; + enabled: root.available; + buttonGlyph: root.available ? (root.price ? hifi.glyphs.hfc : "") : ""; + anchors.right: parent.right; + anchors.top: parent.top; + anchors.left: parent.left; + anchors.topMargin: 15; + height: 50; + color: hifi.buttons.blue; + + onClicked: root.buy(); + } + + Item { + id: creatorItem; + anchors.top: buyButton.bottom; + anchors.leftMargin: 15; + anchors.topMargin: 15; + width: parent.width; + height: childrenRect.height; + + RalewaySemiBold { + id: creatorLabel; + text: "CREATOR:"; + // Text size + size: 14; + // Anchors + anchors.top: parent.top; + anchors.left: parent.left; + width: paintedWidth; + // Style + color: hifi.colors.lightGrayText; + // Alignment + verticalAlignment: Text.AlignVCenter; + } + + RalewaySemiBold { + id: creatorText; + text: root.creator; + // Text size + size: 18; + // Anchors + anchors.top: creatorLabel.bottom; + anchors.left: parent.left; + anchors.topMargin: 10; + width: paintedWidth; + // Style + color: hifi.colors.blueHighlight; + // Alignment + verticalAlignment: Text.AlignVCenter; + } + } + + Item { + id: posted; + anchors.top: creatorItem.bottom; + anchors.leftMargin: 15; + anchors.topMargin: 15; + width: parent.width; + height: childrenRect.height; + RalewaySemiBold { + id: postedLabel; + text: "POSTED:"; + // Text size + size: 14; + // Anchors + anchors.top: parent.top; + anchors.left: parent.left; + + width: paintedWidth; + // Style + color: hifi.colors.lightGrayText; + // Alignment + verticalAlignment: Text.AlignVCenter; + } + + RalewaySemiBold { + id: created_at; + text: { getFormattedDate(root.created_at); } + // Text size + size: 14; + // Anchors + anchors.top: postedLabel.bottom; + anchors.left: parent.left; + anchors.right: parent.right; + anchors.topMargin: 10; + // Style + color: hifi.colors.lightGray; + // Alignment + verticalAlignment: Text.AlignVCenter; + } + } + + Rectangle { + anchors.top: posted.bottom; + anchors.leftMargin: 15; + anchors.topMargin: 15; + width: parent.width; + height: 1; + color: hifi.colors.lightGray; + } + + Item { + id: licenseItem; + anchors.top: posted.bottom; + anchors.left: parent.left; + anchors.topMargin: 30; + width: parent.width; + height: childrenRect.height; + RalewaySemiBold { + id: licenseLabel; + text: "SHARED UNDER:"; + // Text size + size: 14; + // Anchors + anchors.top: parent.top; + anchors.left: parent.left; + width: paintedWidth; + // Style + color: hifi.colors.lightGrayText; + // Alignment + verticalAlignment: Text.AlignVCenter; + } + + RalewaySemiBold { + id: licenseText; + text: root.license; + // Text size + size: 14; + // Anchors + anchors.top: licenseLabel.bottom; + anchors.left: parent.left; + width: paintedWidth; + // Style + color: hifi.colors.lightGray; + // Alignment + verticalAlignment: Text.AlignVCenter; + } + RalewaySemiBold { + id: licenseHelp; + text: "More about this license"; + // Text size + size: 14; + // Anchors + anchors.top: licenseText.bottom; + anchors.left: parent.left; + width: paintedWidth; + // Style + color: hifi.colors.blueHighlight; + // Alignment + verticalAlignment: Text.AlignVCenter; + + MouseArea { + anchors.fill: parent; + + onClicked: { + licenseInfo.visible = true; + var url; + if (root.license == "No Rights Reserved (CC0)") { + url = "https://creativecommons.org/publicdomain/zero/1.0/" + } else if (root.license == "Attribution (CC BY)") { + url = "https://creativecommons.org/licenses/by/4.0/" + } else if (root.license == "Attribution-ShareAlike (CC BY-SA)") { + url = "https://creativecommons.org/licenses/by-sa/4.0/" + } else if (root.license == "Attribution-NoDerivs (CC BY-ND)") { + url = "https://creativecommons.org/licenses/by-nd/4.0/" + } else if (root.license == "Attribution-NonCommercial (CC BY-NC)") { + url = "https://creativecommons.org/licenses/by-nc/4.0/" + } else if (root.license == "Attribution-NonCommercial-ShareAlike (CC BY-NC-SA)") { + url = "https://creativecommons.org/licenses/by-nc-sa/4.0/" + } else if (root.license == "Attribution-NonCommercial-NoDerivs (CC BY-NC-ND)") { + url = "https://creativecommons.org/licenses/by-nc-nd/4.0/" + } else if (root.license == "Proof of Provenance License (PoP License)") { + url = "https://digitalassetregistry.com/PoP-License/v1/" + } + if(url) { + licenseInfoWebView.url = url; + + } + } + } + } + } + + Item { + id: descriptionItem; + anchors.top: licenseItem.bottom; + anchors.topMargin: 15; + anchors.left: parent.left; + anchors.right: parent.right; + height: childrenRect.height; + RalewaySemiBold { + id: descriptionLabel; + text: "DESCRIPTION:"; + // Text size + size: 14; + // Anchors + anchors.top: parent.top; + anchors.left: parent.left; + width: paintedWidth; + // Style + color: hifi.colors.lightGrayText; + // Alignment + verticalAlignment: Text.AlignVCenter; + } + RalewaySemiBold { + id: descriptionText; + text: root.description; + // Text size + size: 14; + // Anchors + anchors.top: descriptionLabel.bottom; + anchors.left: parent.left; + width: parent.width; + // Style + color: hifi.colors.lightGray; + // Alignment + verticalAlignment: Text.AlignVCenter; + wrapMode: Text.Wrap; + } + } + + Item { + id: categoriesList; + anchors.top: descriptionItem.bottom; + anchors.topMargin: 15; + anchors.left: parent.left; + anchors.right: parent.right; + width: parent.width; + height: childrenRect.height; + RalewaySemiBold { + id: categoryLabel; + text: "IN:"; + // Text size + size: 14; + // Anchors + anchors.top: parent.top; + anchors.left: parent.left; + width: paintedWidth; + // Style + color: hifi.colors.lightGrayText; + // Alignment + verticalAlignment: Text.AlignVCenter; + } + ListModel { + id: categoriesListModel; + } + + ListView { + anchors.left: parent.left; + anchors.right: parent.right; + anchors.top: categoryLabel.bottom; + model: categoriesListModel; + height: 20*model.count; + delegate: RalewaySemiBold { + id: categoryText; + text: model.category; + // Text size + size: 14; + // Anchors + anchors.leftMargin: 15; + width: paintedWidth; + height: 20; + // Style + color: hifi.colors.blueHighlight; + // Alignment + verticalAlignment: Text.AlignVCenter; + + MouseArea { + anchors.fill: categoryText; + onClicked: root.categoryClicked(model.category); + } + } + } + } + } +} diff --git a/interface/resources/qml/hifi/commerce/marketplace/MarketplaceListItem.qml b/interface/resources/qml/hifi/commerce/marketplace/MarketplaceListItem.qml new file mode 100644 index 0000000000..4b8471e8b9 --- /dev/null +++ b/interface/resources/qml/hifi/commerce/marketplace/MarketplaceListItem.qml @@ -0,0 +1,278 @@ +// +// MarketplaceListItem.qml +// qml/hifi/commerce/marketplace +// +// MarketplaceListItem +// +// Created by Roxanne Skelly on 2019-01-22 +// Copyright 2019 High Fidelity, Inc. +// +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// + +import Hifi 1.0 as Hifi +import QtQuick 2.9 +import QtQuick.Controls 2.2 +import QtGraphicalEffects 1.0 +import stylesUit 1.0 +import controlsUit 1.0 as HifiControlsUit +import "../../../controls" as HifiControls +import "../common" as HifiCommerceCommon +import "qrc:////qml//hifi//models" as HifiModels // Absolute path so the same code works everywhere. +import "../common/sendAsset" +import "../.." as HifiCommon + +Rectangle { + id: root; + property string item_id: ""; + property string image_url: ""; + property string name: ""; + property int likes: 0; + property bool liked: false; + property string creator: ""; + property string category: ""; + property int price: 0; + property bool available: false; + + signal buy(); + signal showItem(); + signal categoryClicked(string category); + signal requestReload(); + + HifiConstants { id: hifi; } + + width: parent.width; + height: childrenRect.height+50; + + DropShadow { + anchors.fill: shadowBase; + source: shadowBase; + verticalOffset: 4; + horizontalOffset: 4; + radius: 6; + samples: 9; + color: hifi.colors.baseGrayShadow; + } + + Rectangle { + id: shadowBase; + anchors.fill: itemRect; + color: "white"; + } + + Rectangle { + id: itemRect; + height: childrenRect.height; + anchors.left: parent.left; + anchors.right: parent.right; + anchors.top: parent.top; + anchors.topMargin: 20; + anchors.bottomMargin: 10; + anchors.leftMargin: 15; + anchors.rightMargin: 15; + + MouseArea { + anchors.fill: parent; + onClicked: root.showItem(); + onEntered: { hoverRect.visible = true; console.log("entered"); } + onExited: { hoverRect.visible = false; console.log("exited"); } + hoverEnabled: true + } + + Rectangle { + id: header; + anchors.left: parent.left; + anchors.right: parent.right; + anchors.top: parent.top; + + height: 50; + + RalewaySemiBold { + id: nameText; + text: root.name; + // Text size + size: hifi.fontSizes.overlayTitle; + // Anchors + anchors.top: parent.top; + anchors.left: parent.left; + anchors.right: parent.right; + anchors.rightMargin: 50; + anchors.leftMargin: 15; + anchors.bottom: parent.bottom; + elide: Text.ElideRight; + width: paintedWidth; + // Style + color: hifi.colors.blueHighlight; + // Alignment + verticalAlignment: Text.AlignVCenter; + } + Item { + id: likes; + anchors.top: parent.top; + anchors.right: parent.right; + anchors.rightMargin: 15; + anchors.bottom: parent.bottom; + width: childrenRect.width; + Connections { + target: MarketplaceScriptingInterface; + + onMarketplaceItemLikeResult: { + if (result.status !== 'success') { + console.log("Failed to get Marketplace Categories", result.data.message); + root.requestReload(); + } + } + } + + RalewaySemiBold { + id: heart; + text: "\u2665"; + // Size + size: 20; + // Anchors + anchors.top: parent.top; + anchors.right: parent.right; + anchors.rightMargin: 0; + anchors.verticalCenter: parent.verticalCenter; + horizontalAlignment: Text.AlignHCenter; + // Style + color: root.liked ? hifi.colors.redAccent : hifi.colors.lightGrayText; + } + + RalewaySemiBold { + id: likesText; + text: root.likes; + // Text size + size: hifi.fontSizes.overlayTitle; + // Anchors + anchors.top: parent.top; + anchors.right: heart.left; + anchors.rightMargin: 5; + anchors.leftMargin: 15; + anchors.bottom: parent.bottom; + width: paintedWidth; + // Style + color: hifi.colors.baseGray; + // Alignment + verticalAlignment: Text.AlignVCenter; + } + MouseArea { + anchors.fill: parent; + onClicked: { + console.log("like " + root.item_id); + root.liked = !root.liked; + root.likes = root.liked ? root.likes + 1 : root.likes - 1; + MarketplaceScriptingInterface.marketplaceItemLike(root.item_id, root.liked); + } + } + } + } + Image { + id: itemImage; + source: root.image_url; + anchors.top: header.bottom; + anchors.left: parent.left; + anchors.right: parent.right; + height: width*0.5625 + fillMode: Image.PreserveAspectCrop; + } + Item { + id: footer; + anchors.left: parent.left; + anchors.right: parent.right; + anchors.top: itemImage.bottom; + height: 60; + + RalewaySemiBold { + id: creatorLabel; + text: "CREATOR:"; + // Text size + size: 14; + // Anchors + anchors.top: parent.top; + anchors.left: parent.left; + anchors.leftMargin: 15; + width: paintedWidth; + // Style + color: hifi.colors.lightGrayText; + // Alignment + verticalAlignment: Text.AlignVCenter; + } + + RalewaySemiBold { + id: creatorText; + text: root.creator; + // Text size + size: 14; + // Anchors + anchors.top: creatorLabel.top; + anchors.left: creatorLabel.right; + anchors.leftMargin: 15; + width: paintedWidth; + // Style + color: hifi.colors.lightGray; + // Alignment + verticalAlignment: Text.AlignVCenter; + } + RalewaySemiBold { + id: categoryLabel; + text: "IN:"; + // Text size + size: 14; + // Anchors + anchors.top: creatorLabel.bottom; + anchors.left: parent.left; + anchors.leftMargin: 15; + width: paintedWidth; + // Style + color: hifi.colors.lightGrayText; + // Alignment + verticalAlignment: Text.AlignVCenter; + } + + RalewaySemiBold { + id: categoryText; + text: root.category; + // Text size + size: 14; + // Anchors + anchors.top: categoryLabel.top; + anchors.left: categoryLabel.right; + anchors.leftMargin: 15; + width: paintedWidth; + // Style + color: hifi.colors.blueHighlight; + // Alignment + verticalAlignment: Text.AlignVCenter; + + MouseArea { + anchors.fill: parent; + onClicked: root.categoryClicked(root.category); + } + } + + HifiControlsUit.Button { + text: root.price ? root.price : "FREE"; + buttonGlyph: root.price ? hifi.glyphs.hfc : ""; + anchors.right: parent.right; + anchors.top: parent.top; + anchors.bottom: parent.bottom; + anchors.rightMargin: 15; + anchors.topMargin:10; + anchors.bottomMargin: 10; + color: hifi.buttons.blue; + + onClicked: root.buy(); + } + } + Rectangle { + id: hoverRect; + anchors.fill: parent; + border.color: hifi.colors.blueHighlight; + border.width: 2; + color: "#00000000"; + visible: false; + } + } +} diff --git a/interface/resources/qml/hifi/commerce/marketplace/SortButton.qml b/interface/resources/qml/hifi/commerce/marketplace/SortButton.qml new file mode 100644 index 0000000000..37ad2735ce --- /dev/null +++ b/interface/resources/qml/hifi/commerce/marketplace/SortButton.qml @@ -0,0 +1,87 @@ +// +// SortButton.qml +// qml/hifi/commerce/marketplace +// +// SortButton +// +// Created by Roxanne Skelly on 2019-01-18 +// Copyright 2019 High Fidelity, Inc. +// +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// + +import Hifi 1.0 as Hifi +import QtQuick 2.9 +import QtQuick.Controls 2.2 +import QtGraphicalEffects 1.0 +import stylesUit 1.0 +import controlsUit 1.0 as HifiControlsUit +import "../../../controls" as HifiControls +import "../common" as HifiCommerceCommon +import "qrc:////qml//hifi//models" as HifiModels // Absolute path so the same code works everywhere. +import "../common/sendAsset" +import "../.." as HifiCommon + +Item { + HifiConstants { id: hifi; } + + id: root; + + + property string glyph: ""; + property string text: ""; + property bool checked: false; + signal clicked(); + + width: childrenRect.width; + height: parent.height; + + Rectangle { + anchors.top: parent.top; + anchors.left: parent.left; + height: parent.height; + width: 2; + color: hifi.colors.faintGray; + visible: index > 0; + } + + HiFiGlyphs { + id: buttonGlyph; + text: root.glyph; + // Size + size: 14; + // Anchors + anchors.left: parent.left; + anchors.leftMargin: 0; + anchors.top: parent.top; + anchors.verticalCenter: parent.verticalCenter; + height: parent.height; + horizontalAlignment: Text.AlignHCenter; + // Style + color: hifi.colors.lightGray; + } + RalewayRegular { + id: buttonText; + text: root.text; + // Text size + size: 14; + // Style + color: hifi.colors.lightGray; + elide: Text.ElideRight; + horizontalAlignment: Text.AlignHCenter; + verticalAlignment: Text.AlignVCenter; + // Anchors + anchors.left: parent.left; + anchors.leftMargin: 20; + anchors.top: parent.top; + height: parent.height; + } + MouseArea { + anchors.fill: parent; + hoverEnabled: enabled; + onClicked: { + root.clicked(); + } + } +} \ No newline at end of file diff --git a/interface/src/Application.cpp b/interface/src/Application.cpp index 1ec9c93e12..88fda37623 100644 --- a/interface/src/Application.cpp +++ b/interface/src/Application.cpp @@ -2916,7 +2916,7 @@ void Application::initializeUi() { }, commerceCallback); QmlContextCallback marketplaceCallback = [](QQmlContext* context) { - context->setContextProperty("Marketplace", new QmlMarketplace()); + context->setContextProperty("MarketplaceScriptingInterface", new QmlMarketplace()); }; OffscreenQmlSurface::addWhitelistContextHandler({ QUrl{ "hifi/commerce/marketplace/Marketplace.qml" }, diff --git a/interface/src/commerce/QmlMarketplace.cpp b/interface/src/commerce/QmlMarketplace.cpp index 99d3bb1ae6..07a9e570bd 100644 --- a/interface/src/commerce/QmlMarketplace.cpp +++ b/interface/src/commerce/QmlMarketplace.cpp @@ -55,50 +55,54 @@ void QmlMarketplace::getMarketplaceItems( const int& perPage) { QString endpoint = "items"; - QJsonObject request; - request["q"] = q; - request["view"] = view; - request["category"] = category; - request["adminFilter"] = adminFilter; - request["adminFilterCost"] = adminFilterCost; - request["sort"] = sort; - request["isFree"] = isFree; - request["page"] = page; - request["perPage"] = perPage; + QUrlQuery request; + request.addQueryItem("q", q); + request.addQueryItem("view", view); + request.addQueryItem("category", category); + request.addQueryItem("adminFilter", adminFilter); + request.addQueryItem("adminFilterCost", adminFilterCost); + request.addQueryItem("sort", sort); + if (isFree) { + request.addQueryItem("isFree", "true"); + } + request.addQueryItem("page", QString::number(page)); + request.addQueryItem("perPage", QString::number(perPage)); send(endpoint, "getMarketplaceItemsSuccess", "getMarketplaceItemsFailure", QNetworkAccessManager::GetOperation, AccountManagerAuth::Optional, request); } void QmlMarketplace::getMarketplaceItem(const QString& marketplaceItemId) { QString endpoint = QString("items/") + marketplaceItemId; - QJsonObject request; + QUrlQuery request; send(endpoint, "getMarketplaceItemSuccess", "getMarketplaceItemFailure", QNetworkAccessManager::GetOperation, AccountManagerAuth::Optional, request); } void QmlMarketplace::marketplaceItemLike(const QString& marketplaceItemId, const bool like) { QString endpoint = QString("items/") + marketplaceItemId + "/like"; - QJsonObject request; - send(endpoint, "marketplaceItemLikeSuccess", "marketplaceItemLikeFailure", like ? QNetworkAccessManager::PutOperation : QNetworkAccessManager::DeleteOperation, AccountManagerAuth::Required, request); + QUrlQuery request; + send(endpoint, "marketplaceItemLikeSuccess", "marketplaceItemLikeFailure", like ? QNetworkAccessManager::PostOperation : QNetworkAccessManager::DeleteOperation, AccountManagerAuth::Required, request); } void QmlMarketplace::getMarketplaceCategories() { QString endpoint = "categories"; - QJsonObject request; + QUrlQuery request; send(endpoint, "getMarketplaceCategoriesSuccess", "getMarketplaceCategoriesFailure", QNetworkAccessManager::GetOperation, AccountManagerAuth::None, request); } -void QmlMarketplace::send(const QString& endpoint, const QString& success, const QString& fail, QNetworkAccessManager::Operation method, AccountManagerAuth::Type authType, QJsonObject request) { +void QmlMarketplace::send(const QString& endpoint, const QString& success, const QString& fail, QNetworkAccessManager::Operation method, AccountManagerAuth::Type authType, const QUrlQuery & request) { auto accountManager = DependencyManager::get<AccountManager>(); const QString URL = "/api/v1/marketplace/"; JSONCallbackParameters callbackParams(this, success, fail); -#if defined(DEV_BUILD) // Don't expose user's personal data in the wild. But during development this can be handy. - qCInfo(commerce) << "Sending" << QJsonDocument(request).toJson(QJsonDocument::Compact); -#endif + accountManager->sendRequest(URL + endpoint, authType, method, callbackParams, - QJsonDocument(request).toJson()); + QByteArray(), + NULL, + QVariantMap(), + request); + } QJsonObject QmlMarketplace::apiResponse(const QString& label, QNetworkReply* reply) { diff --git a/interface/src/commerce/QmlMarketplace.h b/interface/src/commerce/QmlMarketplace.h index 95a1aa3911..f954198371 100644 --- a/interface/src/commerce/QmlMarketplace.h +++ b/interface/src/commerce/QmlMarketplace.h @@ -60,7 +60,7 @@ signals: void marketplaceItemLikeResult(QJsonObject result); private: - void send(const QString& endpoint, const QString& success, const QString& fail, QNetworkAccessManager::Operation method, AccountManagerAuth::Type authType, QJsonObject request); + void send(const QString& endpoint, const QString& success, const QString& fail, QNetworkAccessManager::Operation method, AccountManagerAuth::Type authType, const QUrlQuery & request); QJsonObject apiResponse(const QString& label, QNetworkReply* reply); QJsonObject failResponse(const QString& label, QNetworkReply* reply); }; diff --git a/libraries/networking/src/AccountManager.cpp b/libraries/networking/src/AccountManager.cpp index 989661cb81..5be2c6d02e 100644 --- a/libraries/networking/src/AccountManager.cpp +++ b/libraries/networking/src/AccountManager.cpp @@ -208,7 +208,7 @@ void AccountManager::setSessionID(const QUuid& sessionID) { } } -QNetworkRequest AccountManager::createRequest(QString path, AccountManagerAuth::Type authType) { +QNetworkRequest AccountManager::createRequest(QString path, AccountManagerAuth::Type authType, const QUrlQuery & query) { QNetworkRequest networkRequest; networkRequest.setAttribute(QNetworkRequest::FollowRedirectsAttribute, true); networkRequest.setHeader(QNetworkRequest::UserAgentHeader, _userAgentGetter()); @@ -227,6 +227,7 @@ QNetworkRequest AccountManager::createRequest(QString path, AccountManagerAuth:: } else { requestURL.setPath("/" + path); } + requestURL.setQuery(query); if (authType != AccountManagerAuth::None ) { if (hasValidAccessToken()) { @@ -263,13 +264,14 @@ void AccountManager::sendRequest(const QString& path, Q_ARG(const JSONCallbackParameters&, callbackParams), Q_ARG(const QByteArray&, dataByteArray), Q_ARG(QHttpMultiPart*, dataMultiPart), - Q_ARG(QVariantMap, propertyMap)); + Q_ARG(QVariantMap, propertyMap), + Q_ARG(QUrlQuery, query)); return; } QNetworkAccessManager& networkAccessManager = NetworkAccessManager::getInstance(); - QNetworkRequest networkRequest = createRequest(path, authType); + QNetworkRequest networkRequest = createRequest(path, authType, query); if (VERBOSE_HTTP_REQUEST_DEBUGGING) { qCDebug(networking) << "Making a request to" << qPrintable(networkRequest.url().toString()); diff --git a/libraries/networking/src/AccountManager.h b/libraries/networking/src/AccountManager.h index ca2b826c98..36563a6ae0 100644 --- a/libraries/networking/src/AccountManager.h +++ b/libraries/networking/src/AccountManager.h @@ -61,7 +61,7 @@ class AccountManager : public QObject, public Dependency { public: AccountManager(UserAgentGetter userAgentGetter = DEFAULT_USER_AGENT_GETTER); - QNetworkRequest createRequest(QString path, AccountManagerAuth::Type authType); + QNetworkRequest createRequest(QString path, AccountManagerAuth::Type authType, const QUrlQuery & query = QUrlQuery()); Q_INVOKABLE void sendRequest(const QString& path, AccountManagerAuth::Type authType, QNetworkAccessManager::Operation operation = QNetworkAccessManager::GetOperation, diff --git a/scripts/system/commerce/wallet.js b/scripts/system/commerce/wallet.js index 19efdc042c..7cacfd7935 100644 --- a/scripts/system/commerce/wallet.js +++ b/scripts/system/commerce/wallet.js @@ -379,21 +379,16 @@ function deleteSendMoneyParticleEffect() { function onUsernameChanged() { } -var MARKETPLACES_INJECT_SCRIPT_URL = Script.resolvePath("../html/js/marketplacesInject.js"); -var METAVERSE_SERVER_URL = Account.metaverseServerURL; -var MARKETPLACE_URL_INITIAL = MARKETPLACE_URL + "?"; // Append "?" to signal injected script that it's the initial page. -function openMarketplace(optionalItemOrUrl) { - // This is a bit of a kluge, but so is the whole file. - // If given a whole path, use it with no cta. - // If given an id, build the appropriate url and use the id as the cta. - // Otherwise, use home and 'marketplace cta'. - // AND... if call onMarketplaceOpen to setupWallet if we need to. - var url = optionalItemOrUrl || MARKETPLACE_URL_INITIAL; - // If optionalItemOrUrl contains the metaverse base, then it's a url, not an item id. - if (optionalItemOrUrl && optionalItemOrUrl.indexOf(METAVERSE_SERVER_URL) === -1) { - url = MARKETPLACE_URL + '/items/' + optionalItemOrUrl; +var MARKETPLACE_QML_PATH = "hifi/commerce/marketplace/Marketplace.qml"; +function openMarketplace(optionalItem) { + ui.open(MARKETPLACE_QML_PATH); + + if (optionalItem) { + ui.tablet.sendToQml({ + method: 'updateMarketplaceQMLItem', + params: { itemId: optionalItem } + }); } - ui.open(url, MARKETPLACES_INJECT_SCRIPT_URL); } function setCertificateInfo(itemCertificateId) { diff --git a/scripts/system/html/js/marketplacesInject.js b/scripts/system/html/js/marketplacesInject.js index f1931192e4..74bf8d3fec 100644 --- a/scripts/system/html/js/marketplacesInject.js +++ b/scripts/system/html/js/marketplacesInject.js @@ -19,6 +19,7 @@ var CLARA_IO_CANCEL_DOWNLOAD = "CLARA.IO CANCEL DOWNLOAD"; var CLARA_IO_CANCELLED_DOWNLOAD = "CLARA.IO CANCELLED DOWNLOAD"; var GOTO_DIRECTORY = "GOTO_DIRECTORY"; + var GOTO_MARKETPLACE = "GOTO_MARKETPLACE"; var QUERY_CAN_WRITE_ASSETS = "QUERY_CAN_WRITE_ASSETS"; var CAN_WRITE_ASSETS = "CAN_WRITE_ASSETS"; var WARN_USER_NO_PERMISSIONS = "WARN_USER_NO_PERMISSIONS"; @@ -72,7 +73,13 @@ // Footer actions. $("#back-button").on("click", function () { - (document.referrer !== "") ? window.history.back() : window.location = (marketplaceBaseURL + "/marketplace?"); + if (document.referrer !== "") { + window.history.back(); + } else { + EventBridge.emitWebEvent(JSON.stringify({ + type: GOTO_MARKETPLACE + })); + } }); $("#all-markets").on("click", function () { EventBridge.emitWebEvent(JSON.stringify({ @@ -93,7 +100,9 @@ window.location = "https://clara.io/library?gameCheck=true&public=true"; }); $('#exploreHifiMarketplace').on('click', function () { - window.location = marketplaceBaseURL + "/marketplace?"; + EventBridge.emitWebEvent(JSON.stringify({ + type: GOTO_MARKETPLACE + })); }); } diff --git a/scripts/system/marketplaces/marketplaces.js b/scripts/system/marketplaces/marketplaces.js index db3b2e2107..2ca79b49f0 100644 --- a/scripts/system/marketplaces/marketplaces.js +++ b/scripts/system/marketplaces/marketplaces.js @@ -25,6 +25,7 @@ var MARKETPLACE_INSPECTIONCERTIFICATE_QML_PATH = "hifi/commerce/inspectionCertif var MARKETPLACE_ITEM_TESTER_QML_PATH = "hifi/commerce/marketplaceItemTester/MarketplaceItemTester.qml"; var MARKETPLACE_PURCHASES_QML_PATH = "hifi/commerce/wallet/Wallet.qml"; // HRS FIXME "hifi/commerce/purchases/Purchases.qml"; var MARKETPLACE_WALLET_QML_PATH = "hifi/commerce/wallet/Wallet.qml"; +var MARKETPLACE_QML_PATH = "hifi/commerce/marketplace/Marketplace.qml"; var MARKETPLACES_INJECT_SCRIPT_URL = Script.resolvePath("../html/js/marketplacesInject.js"); var MARKETPLACES_URL = Script.resolvePath("../html/marketplaces.html"); var METAVERSE_SERVER_URL = Account.metaverseServerURL; @@ -36,6 +37,7 @@ var CLARA_IO_STATUS = "CLARA.IO STATUS"; var CLARA_IO_CANCEL_DOWNLOAD = "CLARA.IO CANCEL DOWNLOAD"; var CLARA_IO_CANCELLED_DOWNLOAD = "CLARA.IO CANCELLED DOWNLOAD"; var GOTO_DIRECTORY = "GOTO_DIRECTORY"; +var GOTO_MARKETPLACE = "GOTO_MARKETPLACE"; var QUERY_CAN_WRITE_ASSETS = "QUERY_CAN_WRITE_ASSETS"; var CAN_WRITE_ASSETS = "CAN_WRITE_ASSETS"; var WARN_USER_NO_PERMISSIONS = "WARN_USER_NO_PERMISSIONS"; @@ -154,18 +156,15 @@ function onMarketplaceOpen(referrer) { } } -function openMarketplace(optionalItemOrUrl) { - // This is a bit of a kluge, but so is the whole file. - // If given a whole path, use it with no cta. - // If given an id, build the appropriate url and use the id as the cta. - // Otherwise, use home and 'marketplace cta'. - // AND... if call onMarketplaceOpen to setupWallet if we need to. - var url = optionalItemOrUrl || MARKETPLACE_URL_INITIAL; - // If optionalItemOrUrl contains the metaverse base, then it's a url, not an item id. - if (optionalItemOrUrl && optionalItemOrUrl.indexOf(METAVERSE_SERVER_URL) === -1) { - url = MARKETPLACE_URL + '/items/' + optionalItemOrUrl; +function openMarketplace(optionalItem) { + ui.open(MARKETPLACE_QML_PATH); + + if (optionalItem) { + ui.tablet.sendToQml({ + method: 'updateMarketplaceQMLItem', + params: { itemId: optionalItem } + }); } - ui.open(url, MARKETPLACES_INJECT_SCRIPT_URL); } // Function Name: wireQmlEventBridge() @@ -439,7 +438,9 @@ var referrerURL; // Used for updating Purchases QML var filterText; // Used for updating Purchases QML function onWebEventReceived(message) { message = JSON.parse(message); - if (message.type === GOTO_DIRECTORY) { + if (message.type === GOTO_MARKETPLACE) { + openMarketplace(); + } else if (message.type === GOTO_DIRECTORY) { // This is the chooser between marketplaces. Only OUR markteplace // requires/makes-use-of wallet, so doesn't go through openMarketplace bottleneck. ui.open(MARKETPLACES_URL, MARKETPLACES_INJECT_SCRIPT_URL); @@ -518,6 +519,7 @@ var onQmlMessageReceived = function onQmlMessageReceived(message) { if (message.messageSrc === "HTML") { return; } + console.log(JSON.stringify(message)); switch (message.method) { case 'gotoBank': ui.close(); @@ -560,6 +562,18 @@ var onQmlMessageReceived = function onQmlMessageReceived(message) { case 'checkout_continue': openMarketplace(); break; + case 'marketplace_checkout': + ui.open(MARKETPLACE_CHECKOUT_QML_PATH); + ui.tablet.sendToQml({ + method: 'updateCheckoutQMLItemID', + params: message + }); + break; + case 'marketplace_marketplaces': + // This is the chooser between marketplaces. Only OUR markteplace + // requires/makes-use-of wallet, so doesn't go through openMarketplace bottleneck. + ui.open(MARKETPLACES_URL, MARKETPLACES_INJECT_SCRIPT_URL); + break; case 'checkout_rezClicked': case 'purchases_rezClicked': case 'tester_rezClicked': @@ -699,7 +713,9 @@ var onTabletScreenChanged = function onTabletScreenChanged(type, url) { var onMarketplaceItemTesterScreenNow = ( url.indexOf(MARKETPLACE_ITEM_TESTER_QML_PATH) !== -1 || url === MARKETPLACE_ITEM_TESTER_QML_PATH); - + var onMarketplaceScreenNow = ( + url.indexOf(MARKETPLACE_QML_PATH) !== -1 || + url === MARKETPLACE_QML_PATH); if ((!onWalletScreenNow && onWalletScreen) || (!onCommerceScreenNow && onCommerceScreen) || (!onMarketplaceItemTesterScreenNow && onMarketplaceScreen) @@ -711,7 +727,7 @@ var onTabletScreenChanged = function onTabletScreenChanged(type, url) { onCommerceScreen = onCommerceScreenNow; onWalletScreen = onWalletScreenNow; onMarketplaceItemTesterScreen = onMarketplaceItemTesterScreenNow; - wireQmlEventBridge(onCommerceScreen || onWalletScreen || onMarketplaceItemTesterScreen); + wireQmlEventBridge(onCommerceScreen || onWalletScreen || onMarketplaceItemTesterScreen || onMarketplaceScreenNow); if (url === MARKETPLACE_PURCHASES_QML_PATH) { // FIXME? Is there a race condition here in which the event @@ -769,14 +785,13 @@ var onTabletScreenChanged = function onTabletScreenChanged(type, url) { var BUTTON_NAME = "MARKET"; -var MARKETPLACE_QML_SOURCE = "hifi/commerce/marketplace/Marketplace.qml"; var ui; function startup() { ui = new AppUi({ buttonName: BUTTON_NAME, sortOrder: 9, - home: MARKETPLACE_QML_SOURCE, + home: MARKETPLACE_QML_PATH, onScreenChanged: onTabletScreenChanged, onMessage: onQmlMessageReceived }); From ff2d51701e6589a7ac2c99b668cdeaf429ab5935 Mon Sep 17 00:00:00 2001 From: Roxanne Skelly <roxanne@highfidelity.io> Date: Fri, 25 Jan 2019 13:41:16 -0800 Subject: [PATCH 03/18] QML Marketplace Add keyboard while in search General cleanup --- .../hifi/commerce/marketplace/Marketplace.qml | 996 ++++++++++-------- .../commerce/marketplace/MarketplaceItem.qml | 577 +++++----- .../marketplace/MarketplaceListItem.qml | 376 ++++--- 3 files changed, 1058 insertions(+), 891 deletions(-) diff --git a/interface/resources/qml/hifi/commerce/marketplace/Marketplace.qml b/interface/resources/qml/hifi/commerce/marketplace/Marketplace.qml index e5ce431de8..77820ccaca 100644 --- a/interface/resources/qml/hifi/commerce/marketplace/Marketplace.qml +++ b/interface/resources/qml/hifi/commerce/marketplace/Marketplace.qml @@ -24,18 +24,21 @@ import "../common/sendAsset" import "../.." as HifiCommon Rectangle { - HifiConstants { id: hifi; } + HifiConstants { + id: hifi + } - id: root; + id: root - property string activeView: "initialize"; - property bool keyboardRaised: false; - property int currentSortIndex: 0; - property string sortString: ""; - property string categoryString: ""; - property string searchString: ""; - - anchors.fill: (typeof parent === undefined) ? undefined : parent; + property string activeView: "initialize" + property int currentSortIndex: 0 + property string sortString: "" + property string categoryString: "" + property string searchString: "" + property bool keyboardEnabled: HMD.active + property bool keyboardRaised: false + + anchors.fill: (typeof parent === undefined) ? undefined : parent function getMarketplaceItems() { marketplaceItemView.visible = false; @@ -44,11 +47,11 @@ Rectangle { } Component.onDestruction: { - KeyboardScriptingInterface.raised = false; + keyboard.raised = false; } Connections { - target: MarketplaceScriptingInterface; + target: MarketplaceScriptingInterface onGetMarketplaceCategoriesResult: { if (result.status !== 'success') { @@ -66,7 +69,7 @@ Rectangle { }); }); } - getMarketplaceItems(); + getMarketplaceItems(); } onGetMarketplaceItemsResult: { marketBrowseModel.handlePage(result.status !== "success" && result.message, result); @@ -77,7 +80,6 @@ Rectangle { console.log("Failed to get Marketplace Item", result.data.message); } else { - console.log(JSON.stringify(result.data)); marketplaceItem.item_id = result.data.id; marketplaceItem.image_url = result.data.thumbnail_url; marketplaceItem.name = result.data.title; @@ -91,7 +93,6 @@ Rectangle { marketplaceItem.license = result.data.license; marketplaceItem.available = result.data.availability == "available"; marketplaceItem.created_at = result.data.created_at; - console.log("HEIGHT: " + marketplaceItemContent.height); marketplaceItemScrollView.contentHeight = marketplaceItemContent.height; itemsList.visible = false; marketplaceItemView.visible = true; @@ -100,196 +101,194 @@ Rectangle { } HifiCommerceCommon.CommerceLightbox { - id: lightboxPopup; - visible: false; - anchors.fill: parent; + id: lightboxPopup + visible: false + anchors.fill: parent } // // HEADER BAR START // - Rectangle { - id: headerShadowBase; - anchors.fill: header; - color: "white"; + id: headerShadowBase + anchors.fill: header + color: "white" } DropShadow { - anchors.fill: headerShadowBase; - source: headerShadowBase; - verticalOffset: 4; - horizontalOffset: 4; - radius: 6; - samples: 9; - color: hifi.colors.baseGrayShadow; - z:5; - } + anchors.fill: headerShadowBase + source: headerShadowBase + verticalOffset: 4 + horizontalOffset: 4 + radius: 6 + samples: 9 + color: hifi.colors.baseGrayShadow + z:5 + } Rectangle { id: header; - visible: true; - anchors.left: parent.left; - anchors.top: parent.top; - anchors.right: parent.right; - anchors.topMargin: -1; - anchors.leftMargin: -1; - anchors.rightMargin: -1; - height: childrenRect.height+5; - z: 5; + + visible: true + anchors { + left: parent.left + top: parent.top + right: parent.right + } + + height: childrenRect.height+5 + z: 5 Rectangle { - id: titleBarContainer; - visible: true; - // Size - width: parent.width; - height: 50; - // Anchors - anchors.left: parent.left; - anchors.top: parent.top; - - - // Marketplace icon + id: titleBarContainer + + anchors.left: parent.left + anchors.top: parent.top + width: parent.width + height: 50 + visible: true + Image { - id: marketplaceIcon; - source: "../../../../images/hifi-logo-blackish.svg"; + id: marketplaceIcon + + anchors { + left: parent.left + leftMargin: 8 + verticalCenter: parent.verticalCenter + } height: 20 - width: marketplaceIcon.height; - anchors.left: parent.left; - anchors.leftMargin: 8; - anchors.verticalCenter: parent.verticalCenter; - visible: true; + width: marketplaceIcon.height + source: "../../../../images/hifi-logo-blackish.svg" + visible: true } - // Title Bar text RalewaySemiBold { - id: titleBarText; - text: "Marketplace"; - // Text size - size: hifi.fontSizes.overlayTitle; - // Anchors - anchors.top: parent.top; - anchors.left: marketplaceIcon.right; - anchors.leftMargin: 6; - anchors.bottom: parent.bottom; - width: paintedWidth; - // Style - color: hifi.colors.black; - // Alignment - verticalAlignment: Text.AlignVCenter; + id: titleBarText + + anchors { + top: parent.top + left: marketplaceIcon.right + bottom: parent.bottom + leftMargin: 6 + } + width: paintedWidth + + text: "Marketplace" + size: hifi.fontSizes.overlayTitle + color: hifi.colors.black + verticalAlignment: Text.AlignVCenter } } Rectangle { - id: searchBarContainer; - visible: true; - clip: false; - // Size - width: parent.width; - anchors.top: titleBarContainer.bottom; - height: 50; + id: searchBarContainer + anchors.top: titleBarContainer.bottom + width: parent.width + height: 50 + + visible: true + clip: false - Rectangle { - id: categoriesButton; - anchors.left: parent.left; - anchors.leftMargin: 10; - anchors.verticalCenter: parent.verticalCenter; - height: 34; - width: categoriesText.width + 25; - color: hifi.colors.white; - radius: 4; - border.width: 1; - border.color: hifi.colors.lightGrayText; - + // + // TODO: Possibly change this to be a combo box + // + Rectangle { + id: categoriesButton - // Categories Text - RalewayRegular { - id: categoriesText; - text: "Categories"; - // Text size - size: 14; - // Style - color: hifi.colors.lightGrayText; - elide: Text.ElideRight; - horizontalAlignment: Text.AlignHCenter; - verticalAlignment: Text.AlignVCenter; - width: Math.min(textMetrics.width + 25, 110); - // Anchors - anchors.centerIn: parent; - rightPadding: 10; + anchors { + left: parent.left + leftMargin: 10 + verticalCenter: parent.verticalCenter } + height: 34 + width: categoriesText.width + 25 + + color: hifi.colors.white + radius: 4 + border.width: 1 + border.color: hifi.colors.lightGrayText + + RalewayRegular { + id: categoriesText + + anchors.centerIn: parent + + text: "Categories" + size: 14 + color: hifi.colors.lightGrayText + elide: Text.ElideRight + horizontalAlignment: Text.AlignHCenter + verticalAlignment: Text.AlignVCenter + rightPadding: 10 + } + HiFiGlyphs { - id: categoriesDropdownIcon; - text: hifi.glyphs.caratDn; - // Size - size: 34; - // Anchors - anchors.right: parent.right; - anchors.rightMargin: -8; - anchors.verticalCenter: parent.verticalCenter; - horizontalAlignment: Text.AlignHCenter; - // Style - color: hifi.colors.baseGray; + id: categoriesDropdownIcon + + anchors { + right: parent.right + rightMargin: -8 + verticalCenter: parent.verticalCenter + } + + text: hifi.glyphs.caratDn + size: 34 + horizontalAlignment: Text.AlignHCenter + color: hifi.colors.baseGray } MouseArea { - anchors.fill: parent; - hoverEnabled: enabled; + anchors.fill: parent + + hoverEnabled: enabled onClicked: { categoriesDropdown.visible = !categoriesDropdown.visible; categoriesButton.color = categoriesDropdown.visible ? hifi.colors.lightGray : hifi.colors.white; categoriesDropdown.forceActiveFocus(); } - onEntered: categoriesText.color = hifi.colors.baseGrayShadow; - onExited: categoriesText.color = hifi.colors.baseGray; + onEntered: categoriesText.color = hifi.colors.baseGrayShadow + onExited: categoriesText.color = hifi.colors.baseGray } Component.onCompleted: { - console.log("Getting Marketplace Categories"); MarketplaceScriptingInterface.getMarketplaceCategories(); } } // or RalewayRegular { - id: orText; - text: "or"; - // Text size + id: orText + + anchors.left: categoriesButton.right + anchors.verticalCenter: parent.verticalCenter + width: 25 + + text: "or" size: 18; - // Style - color: hifi.colors.baseGray; - elide: Text.ElideRight; - horizontalAlignment: Text.AlignHCenter; - verticalAlignment: Text.AlignVCenter; - width: Math.min(textMetrics.width + 25, 110); - // Anchors - anchors.left: categoriesButton.right; - rightPadding: 10; - leftPadding: 10; - anchors.verticalCenter: parent.verticalCenter; + color: hifi.colors.baseGray + elide: Text.ElideRight + horizontalAlignment: Text.AlignHCenter + verticalAlignment: Text.AlignVCenter + rightPadding: 10 + leftPadding: 10 } + HifiControlsUit.TextField { - id: searchField; - anchors.verticalCenter: parent.verticalCenter; - anchors.right: parent.right; - anchors.left: orText.right; - anchors.rightMargin: 10; - height: 34; - isSearchField: true; - colorScheme: hifi.colorSchemes.faintGray; - + id: searchField - font.family: "Fira Sans" - font.pixelSize: hifi.fontSizes.textFieldInput; - - placeholderText: "Search Marketplace"; - - TextMetrics { - id: primaryFilterTextMetrics; - font.family: "FiraSans Regular"; - font.pixelSize: hifi.fontSizes.textFieldInput; - font.capitalization: Font.AllUppercase; + anchors { + verticalCenter: parent.verticalCenter + right: parent.right + left: orText.right + rightMargin: 10 } + height: 34 + + isSearchField: true + colorScheme: hifi.colorSchemes.faintGray + font.family: "Fira Sans" + font.pixelSize: hifi.fontSizes.textFieldInput + placeholderText: "Search Marketplace" // workaround for https://bugreports.qt.io/browse/QTBUG-49297 Keys.onPressed: { @@ -311,7 +310,7 @@ Rectangle { break; } } - onTextChanged: root.searchString = text; + onTextChanged: root.searchString = text onAccepted: { root.searchString = searchField.text; getMarketplaceItems(); @@ -319,15 +318,12 @@ Rectangle { } onActiveFocusChanged: { - if (!activeFocus) { - dropdownContainer.visible = false; - } + root.keyboardRaised = activeFocus; } } } } - // // HEADER BAR END // @@ -336,14 +332,15 @@ Rectangle { // CATEGORIES LIST START // Item { - id: categoriesDropdown; + id: categoriesDropdown + anchors.fill: parent; - visible: false; - z: 10; - + + visible: false + z: 10 MouseArea { - anchors.fill: parent; - propagateComposedEvents: true; + anchors.fill: parent + propagateComposedEvents: true onClicked: { categoriesDropdown.visible = false; categoriesButton.color = hifi.colors.white; @@ -351,57 +348,72 @@ Rectangle { } Rectangle { - anchors.left: parent.left; - anchors.bottom: parent.bottom; - anchors.top: parent.top; - anchors.topMargin: 100; - width: parent.width/3; - color: hifi.colors.white; + anchors { + left: parent.left; + bottom: parent.bottom; + top: parent.top; + topMargin: 100; + } + width: parent.width/3 + + color: hifi.colors.white ListModel { - id: categoriesModel; + id: categoriesModel } ListView { id: categoriesListView; - anchors.fill: parent; - anchors.rightMargin: 10; - width: parent.width; - clip: true; + + anchors.fill: parent + anchors.rightMargin: 10 + width: parent.width + + clip: true - model: categoriesModel; + model: categoriesModel delegate: ItemDelegate { - height: 34; - width: parent.width; - clip: true; + height: 34 + width: parent.width + + clip: true contentItem: Rectangle { - id: categoriesItem; - anchors.fill: parent; - color: hifi.colors.white; - visible: true; + id: categoriesItem + + anchors.fill: parent + + color: hifi.colors.white + visible: true RalewayRegular { - id: categoriesItemText; - text: model.name; - anchors.leftMargin: 15; - anchors.fill:parent; - color: ListView.isCurrentItem ? hifi.colors.lightBlueHighlight : hifi.colors.baseGray; - horizontalAlignment: Text.AlignLeft; - verticalAlignment: Text.AlignVCenter; - size: 14; + id: categoriesItemText + + anchors.leftMargin: 15 + anchors.fill:parent + + text: model.name + color: ListView.isCurrentItem ? hifi.colors.lightBlueHighlight : hifi.colors.baseGray + horizontalAlignment: Text.AlignLeft + verticalAlignment: Text.AlignVCenter + size: 14 } } + MouseArea { - anchors.fill: parent; - hoverEnabled: true; - propagateComposedEvents: false; - z: 10; + anchors.fill: parent + z: 10 + + hoverEnabled: true + propagateComposedEvents: false + onEntered: { categoriesItem.color = ListView.isCurrentItem ? hifi.colors.white : hifi.colors.lightBlueHighlight; } + onExited: { categoriesItem.color = ListView.isCurrentItem ? hifi.colors.lightBlueHighlight : hifi.colors.white; } + onClicked: { categoriesListView.currentIndex = index; categoriesText.text = categoriesItemText.text; @@ -413,12 +425,17 @@ Rectangle { } } + ScrollBar.vertical: ScrollBar { - parent: categoriesListView.parent; - anchors.top: categoriesListView.top; - anchors.bottom: categoriesListView.bottom; - anchors.left: categoriesListView.right; - contentItem.opacity: 1; + parent: categoriesListView.parent + + anchors { + top: categoriesListView.top; + bottom: categoriesListView.bottom; + left: categoriesListView.right; + } + + contentItem.opacity: 1 } } } @@ -431,19 +448,24 @@ Rectangle { // ITEMS LIST START // Item { - id: itemsList; - anchors.fill: parent; - anchors.topMargin: 100; - anchors.bottomMargin: 50; + id: itemsList + + anchors { + fill: parent + topMargin: 100 + bottomMargin: 50 + } + visible: true; HifiModels.PSFListModel { - id: marketBrowseModel; - itemsPerPage: 7; - listModelName: 'marketBrowse'; - listView: marketBrowseList; - getPage: function () { + id: marketBrowseModel + itemsPerPage: 7 + listModelName: 'marketBrowse' + listView: marketBrowseList + + getPage: function () { MarketplaceScriptingInterface.getMarketplaceItems( root.searchString == "" ? undefined : root.searchString, "", @@ -456,90 +478,98 @@ Rectangle { marketBrowseModel.itemsPerPage ); } + processPage: function(data) { - console.log(JSON.stringify(data)); - data.items.forEach(function (item) { - console.log(JSON.stringify(item)); - }); return data.items; } } ListView { - id: marketBrowseList; - model: marketBrowseModel; - // Anchors - anchors.fill: parent; - anchors.rightMargin: 10; - orientation: ListView.Vertical; - focus: true; - clip: true; + id: marketBrowseList + + anchors.fill: parent + anchors.rightMargin: 10 + + model: marketBrowseModel + + orientation: ListView.Vertical + focus: true + clip: true delegate: MarketplaceListItem { - item_id: model.id; - image_url:model.thumbnail_url; - name: model.title; - likes: model.likes; - liked: model.has_liked; - creator: model.creator; - category: model.primary_category; - price: model.cost; - available: model.availability == "available"; - anchors.topMargin: 10; - - - Component.onCompleted: { - console.log("Rendering marketplace list item " + model.id); - console.log(marketBrowseModel.count); - } - + item_id: model.id + + anchors.topMargin: 10 + + image_url:model.thumbnail_url + name: model.title + likes: model.likes + liked: model.has_liked + creator: model.creator + category: model.primary_category + price: model.cost + available: model.availability == "available" + onShowItem: { MarketplaceScriptingInterface.getMarketplaceItem(item_id); } - + onBuy: { sendToScript({method: 'marketplace_checkout', itemId: item_id}); } - + onCategoryClicked: { root.categoryString = category; categoriesText.text = category; getMarketplaceItems(); } - + onRequestReload: getMarketplaceItems(); } + ScrollBar.vertical: ScrollBar { - parent: marketBrowseList.parent; - anchors.top: marketBrowseList.top; - anchors.bottom: marketBrowseList.bottom; - anchors.left: marketBrowseList.right; - contentItem.opacity: 1; - } - headerPositioning: ListView.InlineHeader; + parent: marketBrowseList.parent + + anchors { + top: marketBrowseList.top + bottom: marketBrowseList.bottom + left: marketBrowseList.right + } + + contentItem.opacity: 1 + } + + headerPositioning: ListView.InlineHeader + header: Item { - id: itemsHeading; + id: itemsHeading - height: childrenRect.height; - width: parent.width; + height: childrenRect.height + width: parent.width Item { - id: breadcrumbs; - anchors.left: parent.left; - anchors.right: parent.right; - height: 34; - visible: categoriesListView.currentIndex >= 0; + id: breadcrumbs + + anchors.left: parent.left + anchors.right: parent.right + height: 34 + visible: categoriesListView.currentIndex >= 0 + RalewayRegular { - id: categoriesItemText; - text: "Home /"; - anchors.leftMargin: 15; - anchors.fill:parent; - color: hifi.colors.blueHighlight; - horizontalAlignment: Text.AlignLeft; - verticalAlignment: Text.AlignVCenter; - size: 18; + id: categoriesItemText + + anchors.leftMargin: 15 + anchors.fill:parent + + text: "Home /" + color: hifi.colors.blueHighlight + horizontalAlignment: Text.AlignLeft + verticalAlignment: Text.AlignVCenter + size: 18 } + MouseArea { - anchors.fill: parent; + anchors.fill: parent + onClicked: { categoriesListView.currentIndex = -1; categoriesText.text = "Categories"; @@ -550,72 +580,91 @@ Rectangle { } Item { - id: searchScope; - anchors.top: breadcrumbs.bottom; - anchors.left: parent.left; - anchors.right: parent.right; - height: 50; + id: searchScope + + anchors { + top: breadcrumbs.bottom + left: parent.left + right: parent.right + } + height: 50 RalewaySemiBold { id: searchScopeText; + + anchors { + leftMargin: 15 + fill:parent + topMargin: 10 + } + text: "Featured"; - anchors.leftMargin: 15; - anchors.fill:parent; - anchors.topMargin: 10; - color: hifi.colors.baseGray; - horizontalAlignment: Text.AlignLeft; - verticalAlignment: Text.AlignVCenter; - size: 22; + color: hifi.colors.baseGray + horizontalAlignment: Text.AlignLeft + verticalAlignment: Text.AlignVCenter + size: 22 } } Item { - id: sort; - anchors.top: searchScope.bottom; - anchors.left: parent.left; - anchors.right: parent.right; - anchors.topMargin: 10; - anchors.leftMargin: 15; - height: childrenRect.height; + id: sort + + anchors { + top: searchScope.bottom; + left: parent.left; + right: parent.right; + topMargin: 10; + leftMargin: 15; + } + height: childrenRect.height + RalewayRegular { - id: sortText; - text: "Sort:"; - anchors.leftMargin: 15; - anchors.rightMargin: 20; - height: 34; - color: hifi.colors.lightGrayText; - horizontalAlignment: Text.AlignLeft; - verticalAlignment: Text.AlignVCenter; - size: 14; + id: sortText + + anchors.leftMargin: 15 + anchors.rightMargin: 20 + height: 34 + + text: "Sort:" + color: hifi.colors.lightGrayText + horizontalAlignment: Text.AlignLeft + verticalAlignment: Text.AlignVCenter + size: 14 } Rectangle { - radius: 4; - border.width: 1; - border.color: hifi.colors.faintGray; - anchors.left: sortText.right; - anchors.top: parent.top; - width: 322; - height: 36; - anchors.leftMargin: 20; - - + anchors { + left: sortText.right + top: parent.top + leftMargin: 20 + } + width: 322 + height: 36 + + radius: 4 + border.width: 1 + border.color: hifi.colors.faintGray + ListModel { - id: sortModel; + id: sortModel + ListElement { name: "Name"; - glyph: ";"; - sortString: "alpha"; + glyph: ";" + sortString: "alpha" } + ListElement { name: "Date"; glyph: ";"; sortString: "recent"; } + ListElement { name: "Popular"; glyph: ";"; sortString: "likes"; } + ListElement { name: "My Likes"; glyph: ";"; @@ -624,30 +673,34 @@ Rectangle { } ListView { - id: sortListView; - anchors.top: parent.top; - anchors.bottom: parent.bottom; - anchors.left: parent.left; - anchors.topMargin:1; - anchors.bottomMargin:1; - anchors.leftMargin:1; - anchors.rightMargin:1; - width: childrenRect.width; - height: 34; - orientation: ListView.Horizontal; - focus: true; - clip: true; - highlightFollowsCurrentItem: false; + id: sortListView + + anchors { + top: parent.top + bottom: parent.bottom + left: parent.left + topMargin:1 + bottomMargin:1 + leftMargin:1 + rightMargin:1 + } + width: childrenRect.width + height: 34 + + orientation: ListView.Horizontal + focus: true + clip: true + highlightFollowsCurrentItem: false delegate: SortButton { - width: 80; - height: parent.height; - glyph: model.glyph; - text: model.name; + width: 80 + height: parent.height + + glyph: model.glyph + text: model.name - checked: { - ListView.isCurrentItem; - } + checked: ListView.isCurrentItem + onClicked: { root.currentSortIndex = index; sortListView.positionViewAtIndex(index, ListView.Beginning); @@ -657,12 +710,14 @@ Rectangle { } } highlight: Rectangle { - width: 80; - height: parent.height; - color: hifi.colors.faintGray; - x: sortListView.currentItem.x; + width: 80 + height: parent.height + + color: hifi.colors.faintGray + x: sortListView.currentItem.x } - model: sortModel; + + model: sortModel } } } @@ -678,57 +733,62 @@ Rectangle { // ITEM START // Item { - id: marketplaceItemView; - anchors.fill: parent; - width: parent.width; - anchors.topMargin: 120; - visible: false; + id: marketplaceItemView + + anchors.fill: parent + anchors.topMargin: 120 + width: parent.width + + visible: false ScrollView { - id: marketplaceItemScrollView; + id: marketplaceItemScrollView + anchors.fill: parent; - clip: true; - ScrollBar.vertical.policy: ScrollBar.AlwaysOn; - contentWidth: parent.width; + clip: true + ScrollBar.vertical.policy: ScrollBar.AlwaysOn + contentWidth: parent.width Rectangle { - id: marketplaceItemContent; - width: parent.width; - height: childrenRect.height + 100; + id: marketplaceItemContent + + width: parent.width + height: childrenRect.height + 100 - // Title Bar text RalewaySemiBold { - id: backText; - text: "Back"; - // Text size - size: hifi.fontSizes.overlayTitle; - // Anchors - anchors.top: parent.top; - anchors.left: parent.left - anchors.leftMargin: 15; - anchors.bottomMargin: 10; - width: paintedWidth; - height: paintedHeight; - // Style - color: hifi.colors.blueHighlight; - // Alignment - verticalAlignment: Text.AlignVCenter; + id: backText + + anchors { + top: parent.top + left: parent.left + leftMargin: 15 + bottomMargin: 10 + } + width: paintedWidth + height: paintedHeight + + text: "Back" + size: hifi.fontSizes.overlayTitle + color: hifi.colors.blueHighlight + verticalAlignment: Text.AlignVCenter } + MouseArea { - anchors.fill: backText; + anchors.fill: backText onClicked: { getMarketplaceItems(); } } - + MarketplaceItem { - id: marketplaceItem; - anchors.top: backText.bottom; - width: parent.width; - height: childrenRect.height; - anchors.topMargin: 15; + id: marketplaceItem + + anchors.topMargin: 15 + anchors.top: backText.bottom + width: parent.width + height: childrenRect.height onBuy: { sendToScript({method: 'marketplace_checkout', itemId: item_id}); @@ -756,68 +816,77 @@ Rectangle { // Rectangle { - id: footer; - anchors.bottom: parent.bottom; - anchors.left: parent.left; - anchors.right: parent.right; - height: 50; + id: footer + + anchors { + bottom: parent.bottom + left: parent.left + right: parent.right + } + height: 50 - color: hifi.colors.blueHighlight; + color: hifi.colors.blueHighlight Item { - anchors.fill: parent; - anchors.rightMargin: 15; - anchors.leftMargin: 15; - + anchors { + fill: parent + rightMargin: 15 + leftMargin: 15 + } + HiFiGlyphs { - id: footerGlyph; - text: hifi.glyphs.info; - // Size - size: 34; - // Anchors - anchors.left: parent.left; - anchors.top: parent.top; - anchors.bottom: parent.bottom; - - anchors.rightMargin: 10; - // Style - color: hifi.colors.white; - // Alignment - horizontalAlignment: Text.AlignHCenter; - verticalAlignment: Text.AlignVCenter; + id: footerGlyph + + anchors { + left: parent.left + top: parent.top + bottom: parent.bottom + rightMargin: 10 + } + + text: hifi.glyphs.info + size: 34 + color: hifi.colors.white + horizontalAlignment: Text.AlignHCenter + verticalAlignment: Text.AlignVCenter } RalewaySemiBold { - id: footerInfo; - text: "Get items from Clara.io!"; - anchors.left: footerGlyph.right; - anchors.top: parent.top; - anchors.bottom: parent.bottom; - color: hifi.colors.white; - horizontalAlignment: Text.AlignLeft; - verticalAlignment: Text.AlignVCenter; - size: 18; + id: footerInfo + + anchors { + left: footerGlyph.right + top: parent.top + bottom: parent.bottom + } + + text: "Get items from Clara.io!" + color: hifi.colors.white + horizontalAlignment: Text.AlignLeft + verticalAlignment: Text.AlignVCenter + size: 18 } HifiControlsUit.Button { - text: "SEE ALL MARKETS"; - anchors.right: parent.right; - anchors.top: parent.top; - anchors.bottom: parent.bottom; - anchors.topMargin: 10; - anchors.bottomMargin: 10; - anchors.leftMargin: 10; - anchors.rightMargin: 10; - width: 180; - + anchors { + right: parent.right + top: parent.top + bottom: parent.bottom + topMargin: 10 + bottomMargin: 10 + leftMargin: 10 + rightMargin: 10 + } + + text: "SEE ALL MARKETS" + width: 180 + onClicked: { sendToScript({method: 'marketplace_marketplaces'}); } - } + } } } - - // // FOOTER END // @@ -827,46 +896,57 @@ Rectangle { // LICENSE START // Rectangle { - id: licenseInfo; - anchors.fill: root; - anchors.topMargin: 100; - anchors.bottomMargin: 0; - visible: false; - - - HifiControlsUit.WebView { - id: licenseInfoWebView; - anchors.bottomMargin: 1; - anchors.topMargin: 50; - anchors.leftMargin: 1; - anchors.rightMargin: 1; - anchors.fill: parent; + id: licenseInfo + + anchors { + fill: root + topMargin: 100 + bottomMargin: 0 } - Item { - id: licenseClose; - anchors.top: parent.top; - anchors.right: parent.right; - anchors.topMargin: 10; - anchors.rightMargin: 10; + + visible: false; + + HifiControlsUit.WebView { + id: licenseInfoWebView - width: 30; - height: 30; + anchors { + bottomMargin: 1 + topMargin: 50 + leftMargin: 1 + rightMargin: 1 + fill: parent + } + } + + Item { + id: licenseClose + + anchors { + top: parent.top + right: parent.right + topMargin: 10 + rightMargin: 10 + } + width: 30 + height: 30 + HiFiGlyphs { - anchors.fill: parent; - height: 30; - text: hifi.glyphs.close; - // Size - size: 34; - // Anchors - anchors.rightMargin: 0; - anchors.verticalCenter: parent.verticalCenter; - horizontalAlignment: Text.AlignHCenter; - // Style - color: hifi.colors.baseGray; + anchors { + fill: parent + rightMargin: 0 + verticalCenter: parent.verticalCenter + } + height: 30 + + text: hifi.glyphs.close + size: 34 + horizontalAlignment: Text.AlignHCenter + color: hifi.colors.baseGray } MouseArea { - anchors.fill: licenseClose; + anchors.fill: licenseClose + onClicked: licenseInfo.visible = false; } } @@ -875,6 +955,19 @@ Rectangle { // LICENSE END // + HifiControlsUit.Keyboard { + id: keyboard + + anchors { + bottom: parent.bottom + left: parent.left + right: parent.right + } + + raised: parent.keyboardEnabled && parent.keyboardRaised + numeric: false + } + // // Function Name: fromScript() // @@ -890,7 +983,6 @@ Rectangle { // function fromScript(message) { - console.log("FROM SCRIPT " + JSON.stringify(message)); switch (message.method) { case 'updateMarketplaceQMLItem': if (!message.params.itemId) { diff --git a/interface/resources/qml/hifi/commerce/marketplace/MarketplaceItem.qml b/interface/resources/qml/hifi/commerce/marketplace/MarketplaceItem.qml index a7f2991920..9b95b36918 100644 --- a/interface/resources/qml/hifi/commerce/marketplace/MarketplaceItem.qml +++ b/interface/resources/qml/hifi/commerce/marketplace/MarketplaceItem.qml @@ -25,37 +25,39 @@ import "../.." as HifiCommon Rectangle { id: root; - property string item_id: ""; - property string image_url: ""; - property string name: ""; - property int likes: 0; - property bool liked: false; - property string creator: ""; - property var categories: []; - property int price: 0; - property var attributions: []; - property string description: ""; - property string license: ""; - property string posted: ""; - property bool available: false; - property string created_at: ""; + + property string item_id: "" + property string image_url: "" + property string name: "" + property int likes: 0 + property bool liked: false + property string creator: "" + property var categories: [] + property int price: 0 + property var attributions: [] + property string description: "" + property string license: "" + property string posted: "" + property bool available: false + property string created_at: "" onCategoriesChanged: { categoriesListModel.clear(); categories.forEach(function(category) { - console.log("category is " + category); categoriesListModel.append({"category":category}); }); } - signal buy(); - signal categoryClicked(string category); - signal showLicense(string url); + signal buy() + signal categoryClicked(string category) + signal showLicense(string url) - HifiConstants { id: hifi; } + HifiConstants { + id: hifi + } Connections { - target: MarketplaceScriptingInterface; + target: MarketplaceScriptingInterface onMarketplaceItemLikeResult: { if (result.status !== 'success') { @@ -95,80 +97,91 @@ Rectangle { return a.toDateString() + " " + drawnHour + ':' + min + amOrPm; } - anchors.left: parent.left; - anchors.right: parent.right; - anchors.leftMargin: 15; - anchors.rightMargin: 15; + anchors { + left: parent.left; + right: parent.right; + leftMargin: 15; + rightMargin: 15; + } height: childrenRect.height; - - + Rectangle { - id: header; - anchors.left: parent.left; - anchors.right: parent.right; - anchors.top: parent.top; + id: header + anchors { + left: parent.left; + right: parent.right; + top: parent.top; + } height: 50; RalewaySemiBold { - id: nameText; - text: root.name; - // Text size - size: 24; - // Anchors - anchors.top: parent.top; - anchors.left: parent.left; - anchors.bottom: parent.bottom; - width: paintedWidth; - // Style - color: hifi.colors.baseGray; - // Alignment - verticalAlignment: Text.AlignVCenter; + id: nameText + + anchors { + top: parent.top + left: parent.left + bottom: parent.bottom + } + width: paintedWidth + + text: root.name + size: 24 + color: hifi.colors.baseGray + verticalAlignment: Text.AlignVCenter } + Item { - id: likes; - anchors.top: parent.top; - anchors.right: parent.right; - anchors.bottom: parent.bottom; - anchors.rightMargin: 5; - + id: likes + + anchors { + top: parent.top; + right: parent.right; + bottom: parent.bottom; + rightMargin: 5; + } + RalewaySemiBold { - id: heart; - text: "\u2665"; - // Size - size: 20; - // Anchors - anchors.top: parent.top; - anchors.right: parent.right; - anchors.rightMargin: 0; - anchors.verticalCenter: parent.verticalCenter; - horizontalAlignment: Text.AlignHCenter; - // Style - color: root.liked ? hifi.colors.redAccent : hifi.colors.lightGrayText; + id: heart + + anchors { + top: parent.top + right: parent.right + rightMargin: 0 + verticalCenter: parent.verticalCenter + } + + text: "\u2665" + size: 20 + horizontalAlignment: Text.AlignHCenter + color: root.liked ? hifi.colors.redAccent : hifi.colors.lightGrayText } RalewaySemiBold { - id: likesText; - text: root.likes; - // Text size - size: hifi.fontSizes.overlayTitle; - // Anchors - anchors.top: parent.top; - anchors.right: heart.left; - anchors.rightMargin: 5; - anchors.leftMargin: 15; - anchors.bottom: parent.bottom; - width: paintedWidth; - // Style - color: hifi.colors.baseGray; - // Alignment - verticalAlignment: Text.AlignVCenter; + id: likesText + + anchors { + top: parent.top + right: heart.left + rightMargin: 5 + leftMargin: 15 + bottom: parent.bottom + } + width: paintedWidth + + text: root.likes + size: hifi.fontSizes.overlayTitle + color: hifi.colors.baseGray + verticalAlignment: Text.AlignVCenter } + MouseArea { - anchors.left: likesText.left; - anchors.right: heart.right; - anchors.top: likesText.top; - anchors.bottom: likesText.bottom; + anchors { + left: likesText.left + right: heart.right + top: likesText.top + bottom: likesText.bottom + } onClicked: { MarketplaceScriptingInterface.marketplaceItemLike(root.item_id, !root.liked); @@ -176,178 +189,195 @@ Rectangle { } } } + Image { - id: itemImage; - source: root.image_url; - anchors.top: header.bottom; - anchors.left: parent.left; - anchors.right: parent.right; + id: itemImage + + anchors { + top: header.bottom + left: parent.left + right: parent.right + } height: width*0.5625 + + source: root.image_url fillMode: Image.PreserveAspectCrop; } + Item { - id: footer; - anchors.left: parent.left; - anchors.right: parent.right; - anchors.top: itemImage.bottom; - height: childrenRect.height; + id: footer + + anchors { + left: parent.left; + right: parent.right; + top: itemImage.bottom; + } + height: childrenRect.height HifiControlsUit.Button { - id: buyButton; - text: root.available ? (root.price ? root.price : "FREE") : "UNAVAILABLE (not for sale)"; - enabled: root.available; - buttonGlyph: root.available ? (root.price ? hifi.glyphs.hfc : "") : ""; - anchors.right: parent.right; - anchors.top: parent.top; - anchors.left: parent.left; - anchors.topMargin: 15; - height: 50; - color: hifi.buttons.blue; + id: buyButton + + anchors { + right: parent.right + top: parent.top + left: parent.left + topMargin: 15 + } + height: 50 + + text: root.available ? (root.price ? root.price : "FREE") : "UNAVAILABLE (not for sale)" + enabled: root.available + buttonGlyph: root.available ? (root.price ? hifi.glyphs.hfc : "") : "" + color: hifi.buttons.blue onClicked: root.buy(); } Item { - id: creatorItem; - anchors.top: buyButton.bottom; - anchors.leftMargin: 15; - anchors.topMargin: 15; - width: parent.width; - height: childrenRect.height; + id: creatorItem + + anchors { + top: buyButton.bottom + leftMargin: 15 + topMargin: 15 + } + width: parent.width + height: childrenRect.height RalewaySemiBold { - id: creatorLabel; - text: "CREATOR:"; - // Text size - size: 14; - // Anchors - anchors.top: parent.top; - anchors.left: parent.left; - width: paintedWidth; - // Style - color: hifi.colors.lightGrayText; - // Alignment - verticalAlignment: Text.AlignVCenter; + id: creatorLabel + + anchors.top: parent.top + anchors.left: parent.left + width: paintedWidth + + text: "CREATOR:" + size: 14 + color: hifi.colors.lightGrayText + verticalAlignment: Text.AlignVCenter } RalewaySemiBold { - id: creatorText; - text: root.creator; - // Text size - size: 18; - // Anchors - anchors.top: creatorLabel.bottom; - anchors.left: parent.left; - anchors.topMargin: 10; - width: paintedWidth; - // Style - color: hifi.colors.blueHighlight; - // Alignment - verticalAlignment: Text.AlignVCenter; + id: creatorText + + anchors { + top: creatorLabel.bottom + left: parent.left + topMargin: 10 + } + width: paintedWidth + text: root.creator + + size: 18 + color: hifi.colors.blueHighlight + verticalAlignment: Text.AlignVCenter } } Item { - id: posted; - anchors.top: creatorItem.bottom; - anchors.leftMargin: 15; - anchors.topMargin: 15; - width: parent.width; - height: childrenRect.height; - RalewaySemiBold { - id: postedLabel; - text: "POSTED:"; - // Text size - size: 14; - // Anchors - anchors.top: parent.top; - anchors.left: parent.left; + id: posted + + anchors { + top: creatorItem.bottom + leftMargin: 15 + topMargin: 15 + } + width: parent.width + height: childrenRect.height - width: paintedWidth; - // Style - color: hifi.colors.lightGrayText; - // Alignment - verticalAlignment: Text.AlignVCenter; + RalewaySemiBold { + id: postedLabel + + anchors.top: parent.top + anchors.left: parent.left + width: paintedWidth + + text: "POSTED:" + size: 14 + color: hifi.colors.lightGrayText + verticalAlignment: Text.AlignVCenter } RalewaySemiBold { - id: created_at; + id: created_at + + anchors { + top: postedLabel.bottom + left: parent.left + right: parent.right + topMargin: 10 + } + text: { getFormattedDate(root.created_at); } - // Text size - size: 14; - // Anchors - anchors.top: postedLabel.bottom; - anchors.left: parent.left; - anchors.right: parent.right; - anchors.topMargin: 10; - // Style - color: hifi.colors.lightGray; - // Alignment - verticalAlignment: Text.AlignVCenter; + size: 14 + color: hifi.colors.lightGray + verticalAlignment: Text.AlignVCenter } } Rectangle { - anchors.top: posted.bottom; - anchors.leftMargin: 15; - anchors.topMargin: 15; + anchors { + top: posted.bottom; + leftMargin: 15; + topMargin: 15; + } width: parent.width; height: 1; + color: hifi.colors.lightGray; } Item { id: licenseItem; - anchors.top: posted.bottom; - anchors.left: parent.left; - anchors.topMargin: 30; - width: parent.width; - height: childrenRect.height; + + anchors { + top: posted.bottom + left: parent.left + topMargin: 30 + } + width: parent.width + height: childrenRect.height + RalewaySemiBold { - id: licenseLabel; - text: "SHARED UNDER:"; - // Text size - size: 14; - // Anchors + id: licenseLabel + anchors.top: parent.top; anchors.left: parent.left; width: paintedWidth; - // Style + + text: "SHARED UNDER:"; + size: 14; color: hifi.colors.lightGrayText; - // Alignment verticalAlignment: Text.AlignVCenter; } - + RalewaySemiBold { - id: licenseText; - text: root.license; - // Text size - size: 14; - // Anchors - anchors.top: licenseLabel.bottom; - anchors.left: parent.left; - width: paintedWidth; - // Style - color: hifi.colors.lightGray; - // Alignment - verticalAlignment: Text.AlignVCenter; + id: licenseText + + anchors.top: licenseLabel.bottom + anchors.left: parent.left + width: paintedWidth + + text: root.license + size: 14 + color: hifi.colors.lightGray + verticalAlignment: Text.AlignVCenter } + RalewaySemiBold { - id: licenseHelp; - text: "More about this license"; - // Text size - size: 14; - // Anchors + id: licenseHelp + anchors.top: licenseText.bottom; anchors.left: parent.left; width: paintedWidth; - // Style - color: hifi.colors.blueHighlight; - // Alignment - verticalAlignment: Text.AlignVCenter; + + text: "More about this license" + size: 14 + color: hifi.colors.blueHighlight + verticalAlignment: Text.AlignVCenter MouseArea { - anchors.fill: parent; + anchors.fill: parent onClicked: { licenseInfo.visible = true; @@ -371,7 +401,6 @@ Rectangle { } if(url) { licenseInfoWebView.url = url; - } } } @@ -379,91 +408,97 @@ Rectangle { } Item { - id: descriptionItem; - anchors.top: licenseItem.bottom; - anchors.topMargin: 15; - anchors.left: parent.left; - anchors.right: parent.right; - height: childrenRect.height; - RalewaySemiBold { - id: descriptionLabel; - text: "DESCRIPTION:"; - // Text size - size: 14; - // Anchors - anchors.top: parent.top; - anchors.left: parent.left; - width: paintedWidth; - // Style - color: hifi.colors.lightGrayText; - // Alignment - verticalAlignment: Text.AlignVCenter; + id: descriptionItem + + anchors { + top: licenseItem.bottom + topMargin: 15 + left: parent.left + right: parent.right } + height: childrenRect.height + RalewaySemiBold { - id: descriptionText; - text: root.description; - // Text size - size: 14; - // Anchors - anchors.top: descriptionLabel.bottom; - anchors.left: parent.left; - width: parent.width; - // Style - color: hifi.colors.lightGray; - // Alignment - verticalAlignment: Text.AlignVCenter; - wrapMode: Text.Wrap; + id: descriptionLabel + + anchors.top: parent.top + anchors.left: parent.left + width: paintedWidth + + text: "DESCRIPTION:" + size: 14 + color: hifi.colors.lightGrayText + verticalAlignment: Text.AlignVCenter + } + + RalewaySemiBold { + id: descriptionText + + anchors.top: descriptionLabel.bottom + anchors.left: parent.left + width: parent.width + + text: root.description + size: 14 + color: hifi.colors.lightGray + verticalAlignment: Text.AlignVCenter + wrapMode: Text.Wrap } } Item { - id: categoriesList; - anchors.top: descriptionItem.bottom; - anchors.topMargin: 15; - anchors.left: parent.left; - anchors.right: parent.right; - width: parent.width; - height: childrenRect.height; + id: categoriesList + + anchors { + top: descriptionItem.bottom + topMargin: 15 + left: parent.left + right: parent.right + } + width: parent.width + height: childrenRect.height + RalewaySemiBold { - id: categoryLabel; - text: "IN:"; - // Text size - size: 14; - // Anchors - anchors.top: parent.top; - anchors.left: parent.left; - width: paintedWidth; - // Style - color: hifi.colors.lightGrayText; - // Alignment - verticalAlignment: Text.AlignVCenter; + id: categoryLabel + + anchors.top: parent.top + anchors.left: parent.left + width: paintedWidth + + text: "IN:" + size: 14 + color: hifi.colors.lightGrayText + verticalAlignment: Text.AlignVCenter } ListModel { - id: categoriesListModel; + id: categoriesListModel } ListView { - anchors.left: parent.left; - anchors.right: parent.right; - anchors.top: categoryLabel.bottom; - model: categoriesListModel; - height: 20*model.count; + anchors { + left: parent.left; + right: parent.right; + top: categoryLabel.bottom; + } + + height: 20*model.count + + model: categoriesListModel delegate: RalewaySemiBold { - id: categoryText; - text: model.category; - // Text size - size: 14; - // Anchors - anchors.leftMargin: 15; - width: paintedWidth; - height: 20; - // Style - color: hifi.colors.blueHighlight; - // Alignment - verticalAlignment: Text.AlignVCenter; + id: categoryText + + anchors.leftMargin: 15 + width: paintedWidth + + text: model.category + size: 14 + height: 20 + color: hifi.colors.blueHighlight + verticalAlignment: Text.AlignVCenter MouseArea { - anchors.fill: categoryText; + anchors.fill: categoryText + onClicked: root.categoryClicked(model.category); } } diff --git a/interface/resources/qml/hifi/commerce/marketplace/MarketplaceListItem.qml b/interface/resources/qml/hifi/commerce/marketplace/MarketplaceListItem.qml index 4b8471e8b9..46e6a9d920 100644 --- a/interface/resources/qml/hifi/commerce/marketplace/MarketplaceListItem.qml +++ b/interface/resources/qml/hifi/commerce/marketplace/MarketplaceListItem.qml @@ -24,98 +24,115 @@ import "../common/sendAsset" import "../.." as HifiCommon Rectangle { - id: root; - property string item_id: ""; - property string image_url: ""; - property string name: ""; - property int likes: 0; - property bool liked: false; - property string creator: ""; - property string category: ""; - property int price: 0; - property bool available: false; + id: root - signal buy(); - signal showItem(); - signal categoryClicked(string category); - signal requestReload(); + property string item_id: "" + property string image_url: "" + property string name: "" + property int likes: 0 + property bool liked: false + property string creator: "" + property string category: "" + property int price: 0 + property bool available: false + + signal buy() + signal showItem() + signal categoryClicked(string category) + signal requestReload() - HifiConstants { id: hifi; } + HifiConstants { id: hifi } - width: parent.width; - height: childrenRect.height+50; + width: parent.width + height: childrenRect.height+50 DropShadow { - anchors.fill: shadowBase; - source: shadowBase; - verticalOffset: 4; - horizontalOffset: 4; - radius: 6; - samples: 9; - color: hifi.colors.baseGrayShadow; + anchors.fill: shadowBase + + source: shadowBase + verticalOffset: 4 + horizontalOffset: 4 + radius: 6 + samples: 9 + color: hifi.colors.baseGrayShadow } Rectangle { - id: shadowBase; - anchors.fill: itemRect; - color: "white"; + id: shadowBase + + anchors.fill: itemRect + + color: "white" } Rectangle { - id: itemRect; - height: childrenRect.height; - anchors.left: parent.left; - anchors.right: parent.right; - anchors.top: parent.top; - anchors.topMargin: 20; - anchors.bottomMargin: 10; - anchors.leftMargin: 15; - anchors.rightMargin: 15; + id: itemRect + anchors { + left: parent.left + right: parent.right + top: parent.top + topMargin: 20 + bottomMargin: 10 + leftMargin: 15 + rightMargin: 15 + } + height: childrenRect.height + MouseArea { - anchors.fill: parent; + anchors.fill: parent + + hoverEnabled: true + onClicked: root.showItem(); - onEntered: { hoverRect.visible = true; console.log("entered"); } - onExited: { hoverRect.visible = false; console.log("exited"); } - hoverEnabled: true + onEntered: hoverRect.visible = true; + onExited: hoverRect.visible = false; + } Rectangle { - id: header; - anchors.left: parent.left; - anchors.right: parent.right; - anchors.top: parent.top; - - height: 50; + id: header + + anchors { + left: parent.left + right: parent.right + top: parent.top + } + height: 50 RalewaySemiBold { - id: nameText; - text: root.name; - // Text size - size: hifi.fontSizes.overlayTitle; - // Anchors - anchors.top: parent.top; - anchors.left: parent.left; - anchors.right: parent.right; - anchors.rightMargin: 50; - anchors.leftMargin: 15; - anchors.bottom: parent.bottom; - elide: Text.ElideRight; - width: paintedWidth; - // Style - color: hifi.colors.blueHighlight; - // Alignment - verticalAlignment: Text.AlignVCenter; + id: nameText + + anchors { + top: parent.top + left: parent.left + right: parent.right + rightMargin: 50 + leftMargin: 15 + bottom: parent.bottom + } + width: paintedWidth + + text: root.name + size: hifi.fontSizes.overlayTitle + elide: Text.ElideRight + color: hifi.colors.blueHighlight + verticalAlignment: Text.AlignVCenter } + Item { - id: likes; - anchors.top: parent.top; - anchors.right: parent.right; - anchors.rightMargin: 15; - anchors.bottom: parent.bottom; - width: childrenRect.width; + id: likes + + anchors { + top: parent.top + right: parent.right + rightMargin: 15 + bottom: parent.bottom + } + width: heart.width + likesText.width + Connections { - target: MarketplaceScriptingInterface; + target: MarketplaceScriptingInterface onMarketplaceItemLikeResult: { if (result.status !== 'success') { @@ -126,41 +143,47 @@ Rectangle { } RalewaySemiBold { - id: heart; - text: "\u2665"; - // Size - size: 20; - // Anchors - anchors.top: parent.top; - anchors.right: parent.right; - anchors.rightMargin: 0; - anchors.verticalCenter: parent.verticalCenter; + id: heart + + anchors { + top: parent.top + right: parent.right + rightMargin: 0 + verticalCenter: parent.verticalCenter + } + + text: "\u2665" + size: 20 horizontalAlignment: Text.AlignHCenter; - // Style - color: root.liked ? hifi.colors.redAccent : hifi.colors.lightGrayText; + color: root.liked ? hifi.colors.redAccent : hifi.colors.lightGrayText } RalewaySemiBold { - id: likesText; - text: root.likes; - // Text size - size: hifi.fontSizes.overlayTitle; - // Anchors - anchors.top: parent.top; - anchors.right: heart.left; - anchors.rightMargin: 5; - anchors.leftMargin: 15; - anchors.bottom: parent.bottom; - width: paintedWidth; - // Style - color: hifi.colors.baseGray; - // Alignment - verticalAlignment: Text.AlignVCenter; + id: likesText + + anchors { + top: parent.top + right: heart.left + rightMargin: 5 + leftMargin: 15 + bottom: parent.bottom + } + width: paintedWidth + + text: root.likes + size: hifi.fontSizes.overlayTitle + color: hifi.colors.baseGray + verticalAlignment: Text.AlignVCenter } + MouseArea { - anchors.fill: parent; + anchors { + left: likesText.left + right: heart.right + top: parent.top + bottom: parent.bottom + } onClicked: { - console.log("like " + root.item_id); root.liked = !root.liked; root.likes = root.liked ? root.likes + 1 : root.likes - 1; MarketplaceScriptingInterface.marketplaceItemLike(root.item_id, root.liked); @@ -168,111 +191,128 @@ Rectangle { } } } + Image { - id: itemImage; - source: root.image_url; - anchors.top: header.bottom; - anchors.left: parent.left; - anchors.right: parent.right; + id: itemImage + + anchors { + top: header.bottom + left: parent.left + right: parent.right + } height: width*0.5625 - fillMode: Image.PreserveAspectCrop; + + source: root.image_url + fillMode: Image.PreserveAspectCrop } + Item { - id: footer; - anchors.left: parent.left; - anchors.right: parent.right; - anchors.top: itemImage.bottom; - height: 60; + id: footer + + anchors { + left: parent.left + right: parent.right + top: itemImage.bottom + } + height: 60 RalewaySemiBold { - id: creatorLabel; - text: "CREATOR:"; - // Text size - size: 14; - // Anchors - anchors.top: parent.top; - anchors.left: parent.left; - anchors.leftMargin: 15; - width: paintedWidth; - // Style - color: hifi.colors.lightGrayText; - // Alignment - verticalAlignment: Text.AlignVCenter; + id: creatorLabel + + anchors { + top: parent.top + left: parent.left + leftMargin: 15 + } + width: paintedWidth + + text: "CREATOR:" + size: 14 + color: hifi.colors.lightGrayText + verticalAlignment: Text.AlignVCenter } RalewaySemiBold { - id: creatorText; + id: creatorText + + anchors { + top: creatorLabel.top; + left: creatorLabel.right; + leftMargin: 15; + } + width: paintedWidth; + text: root.creator; - // Text size size: 14; - // Anchors - anchors.top: creatorLabel.top; - anchors.left: creatorLabel.right; - anchors.leftMargin: 15; - width: paintedWidth; - // Style color: hifi.colors.lightGray; - // Alignment verticalAlignment: Text.AlignVCenter; } + RalewaySemiBold { - id: categoryLabel; + id: categoryLabel + + anchors { + top: creatorLabel.bottom + left: parent.left + leftMargin: 15 + } + width: paintedWidth; + text: "IN:"; - // Text size size: 14; - // Anchors - anchors.top: creatorLabel.bottom; - anchors.left: parent.left; - anchors.leftMargin: 15; - width: paintedWidth; - // Style color: hifi.colors.lightGrayText; - // Alignment verticalAlignment: Text.AlignVCenter; } - + RalewaySemiBold { - id: categoryText; - text: root.category; - // Text size - size: 14; - // Anchors - anchors.top: categoryLabel.top; - anchors.left: categoryLabel.right; - anchors.leftMargin: 15; - width: paintedWidth; - // Style + id: categoryText + + anchors { + top: categoryLabel.top + left: categoryLabel.right + leftMargin: 15 + } + width: paintedWidth + + text: root.category + size: 14 color: hifi.colors.blueHighlight; - // Alignment verticalAlignment: Text.AlignVCenter; MouseArea { - anchors.fill: parent; + anchors.fill: parent + onClicked: root.categoryClicked(root.category); } } HifiControlsUit.Button { - text: root.price ? root.price : "FREE"; - buttonGlyph: root.price ? hifi.glyphs.hfc : ""; - anchors.right: parent.right; - anchors.top: parent.top; - anchors.bottom: parent.bottom; - anchors.rightMargin: 15; - anchors.topMargin:10; - anchors.bottomMargin: 10; + anchors { + right: parent.right + top: parent.top + bottom: parent.bottom + rightMargin: 15 + topMargin:10 + bottomMargin: 10 + } + + text: root.price ? root.price : "FREE" + buttonGlyph: root.price ? hifi.glyphs.hfc : "" color: hifi.buttons.blue; - + onClicked: root.buy(); } } + Rectangle { - id: hoverRect; - anchors.fill: parent; - border.color: hifi.colors.blueHighlight; - border.width: 2; - color: "#00000000"; - visible: false; + id: hoverRect + + anchors.fill: parent + + border.color: hifi.colors.blueHighlight + border.width: 2 + color: "#00000000" + visible: false } } } From dfac0d88a25abd56cde17679dc3f08df379790a6 Mon Sep 17 00:00:00 2001 From: Roxanne Skelly <roxanne@highfidelity.io> Date: Fri, 25 Jan 2019 16:41:13 -0800 Subject: [PATCH 04/18] QmlMarketplace - Add 'logged out' behavior, fix issue with search scope display --- .../hifi/commerce/marketplace/Marketplace.qml | 178 ++++++++++++++++-- .../commerce/marketplace/MarketplaceItem.qml | 21 ++- .../marketplace/MarketplaceListItem.qml | 9 +- interface/src/Application.cpp | 1 + scripts/system/marketplaces/marketplaces.js | 2 +- 5 files changed, 184 insertions(+), 27 deletions(-) diff --git a/interface/resources/qml/hifi/commerce/marketplace/Marketplace.qml b/interface/resources/qml/hifi/commerce/marketplace/Marketplace.qml index 77820ccaca..10b59ac83b 100644 --- a/interface/resources/qml/hifi/commerce/marketplace/Marketplace.qml +++ b/interface/resources/qml/hifi/commerce/marketplace/Marketplace.qml @@ -37,18 +37,42 @@ Rectangle { property string searchString: "" property bool keyboardEnabled: HMD.active property bool keyboardRaised: false + property string searchScopeString: "Featured" + property bool isLoggedIn: false; anchors.fill: (typeof parent === undefined) ? undefined : parent function getMarketplaceItems() { marketplaceItemView.visible = false; - itemsList.visible = true; + itemsList.visible = true; marketBrowseModel.getFirstPage(); + { + if(root.searchString !== undefined && root.searchString !== "") { + root.searchScopeString = "Search Results: \"" + root.searchString + "\""; + } else if (root.categoryString !== "") { + root.searchScopeString = root.categoryString; + } else { + root.searchScopeString = "Featured"; + } + } } - + + Component.onCompleted: { + Commerce.getLoginStatus(); + } + Component.onDestruction: { keyboard.raised = false; } + + Connections { + target: GlobalServices + + onMyUsernameChanged: { + console.log("LOGIN STATUS CHANGING"); + Commerce.getLoginStatus(); + } + } Connections { target: MarketplaceScriptingInterface @@ -61,7 +85,7 @@ Rectangle { categoriesModel.append({ id: -1, name: "Everything" - }); + }); result.data.items.forEach(function(category) { categoriesModel.append({ id: category.id, @@ -84,14 +108,16 @@ Rectangle { marketplaceItem.image_url = result.data.thumbnail_url; marketplaceItem.name = result.data.title; marketplaceItem.likes = result.data.likes; - marketplaceItem.liked = result.data.has_liked; + if(result.data.has_liked !== undefined) { + marketplaceItem.liked = result.data.has_liked; + } marketplaceItem.creator = result.data.creator; marketplaceItem.categories = result.data.categories; marketplaceItem.price = result.data.cost; marketplaceItem.description = result.data.description; marketplaceItem.attributions = result.data.attributions; marketplaceItem.license = result.data.license; - marketplaceItem.available = result.data.availability == "available"; + marketplaceItem.available = result.data.availability === "available"; marketplaceItem.created_at = result.data.created_at; marketplaceItemScrollView.contentHeight = marketplaceItemContent.height; itemsList.visible = false; @@ -100,6 +126,15 @@ Rectangle { } } + Connections { + target: Commerce + + onLoginStatusResult: { + root.isLoggedIn = isLoggedIn; + itemsLoginStatus.visible = !isLoggedIn; + } + } + HifiCommerceCommon.CommerceLightbox { id: lightboxPopup visible: false @@ -423,7 +458,6 @@ Rectangle { getMarketplaceItems(); } } - } ScrollBar.vertical: ScrollBar { @@ -452,12 +486,12 @@ Rectangle { anchors { fill: parent - topMargin: 100 + topMargin: 120 bottomMargin: 50 } - + visible: true; - + HifiModels.PSFListModel { id: marketBrowseModel @@ -467,7 +501,7 @@ Rectangle { getPage: function () { MarketplaceScriptingInterface.getMarketplaceItems( - root.searchString == "" ? undefined : root.searchString, + root.searchString === "" ? undefined : root.searchString, "", root.categoryString.toLowerCase(), "", @@ -503,11 +537,12 @@ Rectangle { image_url:model.thumbnail_url name: model.title likes: model.likes - liked: model.has_liked + liked: model.has_liked ? model.has_liked : false creator: model.creator category: model.primary_category price: model.cost - available: model.availability == "available" + available: model.availability === "available" + isLoggedIn: root.isLoggedIn; onShowItem: { MarketplaceScriptingInterface.getMarketplaceItem(item_id); @@ -546,9 +581,65 @@ Rectangle { height: childrenRect.height width: parent.width + Rectangle { + id: itemsLoginStatus; + anchors { + left: parent.left + right: parent.right + leftMargin: 15 + rightMargin: 15 + } + height: root.isLoggedIn ? 0 : 80 + + visible: !root.isLoggedIn + color: hifi.colors.greenHighlight + border.color: hifi.colors.greenShadow + border.width: 1 + radius: 4 + z: 10000 + + HifiControlsUit.Button { + id: loginButton; + anchors { + left: parent.left + top: parent.top + bottom: parent.bottom + leftMargin: 15 + topMargin:10 + bottomMargin: 10 + } + width: 80; + + text: root.price ? root.price : "LOG IN" + + onClicked: { + sendToScript({method: 'needsLogIn_loginClicked'}); + } + } + + RalewayRegular { + id: itemsLoginText + + anchors { + leftMargin: 15 + top: parent.top; + bottom: parent.bottom; + right: parent.right; + left: loginButton.right + } + + text: "to get items from the Marketplace." + color: hifi.colors.baseGray + horizontalAlignment: Text.AlignLeft + verticalAlignment: Text.AlignVCenter + size: 18 + } + } + Item { id: breadcrumbs + anchors.top: itemsLoginStatus.bottom; anchors.left: parent.left anchors.right: parent.right height: 34 @@ -598,7 +689,7 @@ Rectangle { topMargin: 10 } - text: "Featured"; + text: searchScopeString color: hifi.colors.baseGray horizontalAlignment: Text.AlignLeft verticalAlignment: Text.AlignVCenter @@ -751,8 +842,64 @@ Rectangle { contentWidth: parent.width Rectangle { - id: marketplaceItemContent + id: itemLoginStatus; + anchors { + left: parent.left + right: parent.right + leftMargin: 15 + rightMargin: 15 + } + height: root.isLoggedIn ? 0 : 80 + visible: !root.isLoggedIn + color: hifi.colors.greenHighlight + border.color: hifi.colors.greenShadow + border.width: 1 + radius: 4 + z: 10000 + + HifiControlsUit.Button { + id: loginButton; + anchors { + left: parent.left + top: parent.top + bottom: parent.bottom + leftMargin: 15 + topMargin:10 + bottomMargin: 10 + } + width: 80; + + text: root.price ? root.price : "LOG IN" + + onClicked: { + sendToScript({method: 'needsLogIn_loginClicked'}); + } + } + + RalewayRegular { + id: itemsLoginText + + anchors { + leftMargin: 15 + top: parent.top; + bottom: parent.bottom; + right: parent.right; + left: loginButton.right + } + + text: "to get items from the Marketplace." + color: hifi.colors.baseGray + horizontalAlignment: Text.AlignLeft + verticalAlignment: Text.AlignVCenter + size: 18 + } + } + + + Rectangle { + id: marketplaceItemContent + anchors.top: itemLoginStatus.bottom; width: parent.width height: childrenRect.height + 100 @@ -785,11 +932,14 @@ Rectangle { MarketplaceItem { id: marketplaceItem + anchors.topMargin: 15 anchors.top: backText.bottom width: parent.width height: childrenRect.height + isLoggedIn: root.isLoggedIn; + onBuy: { sendToScript({method: 'marketplace_checkout', itemId: item_id}); } diff --git a/interface/resources/qml/hifi/commerce/marketplace/MarketplaceItem.qml b/interface/resources/qml/hifi/commerce/marketplace/MarketplaceItem.qml index 9b95b36918..5795d0d67d 100644 --- a/interface/resources/qml/hifi/commerce/marketplace/MarketplaceItem.qml +++ b/interface/resources/qml/hifi/commerce/marketplace/MarketplaceItem.qml @@ -40,6 +40,7 @@ Rectangle { property string posted: "" property bool available: false property string created_at: "" + property bool isLoggedIn: false; onCategoriesChanged: { categoriesListModel.clear(); @@ -184,7 +185,9 @@ Rectangle { } onClicked: { - MarketplaceScriptingInterface.marketplaceItemLike(root.item_id, !root.liked); + if (isLoggedIn) { + MarketplaceScriptingInterface.marketplaceItemLike(root.item_id, !root.liked); + } } } } @@ -382,21 +385,21 @@ Rectangle { onClicked: { licenseInfo.visible = true; var url; - if (root.license == "No Rights Reserved (CC0)") { + if (root.license === "No Rights Reserved (CC0)") { url = "https://creativecommons.org/publicdomain/zero/1.0/" - } else if (root.license == "Attribution (CC BY)") { + } else if (root.license === "Attribution (CC BY)") { url = "https://creativecommons.org/licenses/by/4.0/" - } else if (root.license == "Attribution-ShareAlike (CC BY-SA)") { + } else if (root.license === "Attribution-ShareAlike (CC BY-SA)") { url = "https://creativecommons.org/licenses/by-sa/4.0/" - } else if (root.license == "Attribution-NoDerivs (CC BY-ND)") { + } else if (root.license === "Attribution-NoDerivs (CC BY-ND)") { url = "https://creativecommons.org/licenses/by-nd/4.0/" - } else if (root.license == "Attribution-NonCommercial (CC BY-NC)") { + } else if (root.license === "Attribution-NonCommercial (CC BY-NC)") { url = "https://creativecommons.org/licenses/by-nc/4.0/" - } else if (root.license == "Attribution-NonCommercial-ShareAlike (CC BY-NC-SA)") { + } else if (root.license === "Attribution-NonCommercial-ShareAlike (CC BY-NC-SA)") { url = "https://creativecommons.org/licenses/by-nc-sa/4.0/" - } else if (root.license == "Attribution-NonCommercial-NoDerivs (CC BY-NC-ND)") { + } else if (root.license === "Attribution-NonCommercial-NoDerivs (CC BY-NC-ND)") { url = "https://creativecommons.org/licenses/by-nc-nd/4.0/" - } else if (root.license == "Proof of Provenance License (PoP License)") { + } else if (root.license === "Proof of Provenance License (PoP License)") { url = "https://digitalassetregistry.com/PoP-License/v1/" } if(url) { diff --git a/interface/resources/qml/hifi/commerce/marketplace/MarketplaceListItem.qml b/interface/resources/qml/hifi/commerce/marketplace/MarketplaceListItem.qml index 46e6a9d920..eb99106cf4 100644 --- a/interface/resources/qml/hifi/commerce/marketplace/MarketplaceListItem.qml +++ b/interface/resources/qml/hifi/commerce/marketplace/MarketplaceListItem.qml @@ -35,6 +35,7 @@ Rectangle { property string category: "" property int price: 0 property bool available: false + property bool isLoggedIn: false; signal buy() signal showItem() @@ -184,9 +185,11 @@ Rectangle { bottom: parent.bottom } onClicked: { - root.liked = !root.liked; - root.likes = root.liked ? root.likes + 1 : root.likes - 1; - MarketplaceScriptingInterface.marketplaceItemLike(root.item_id, root.liked); + if(isLoggedIn) { + root.liked = !root.liked; + root.likes = root.liked ? root.likes + 1 : root.likes - 1; + MarketplaceScriptingInterface.marketplaceItemLike(root.item_id, root.liked); + } } } } diff --git a/interface/src/Application.cpp b/interface/src/Application.cpp index 2f0ca7ea2e..7ae7ebf65c 100644 --- a/interface/src/Application.cpp +++ b/interface/src/Application.cpp @@ -2990,6 +2990,7 @@ void Application::initializeUi() { QUrl{ "hifi/dialogs/security/SecurityImageModel.qml" }, QUrl{ "hifi/dialogs/security/SecurityImageSelection.qml" }, QUrl{ "hifi/tablet/TabletMenu.qml" }, + QUrl{ "hifi/commerce/marketplace/Marketplace.qml" }, }, commerceCallback); QmlContextCallback marketplaceCallback = [](QQmlContext* context) { diff --git a/scripts/system/marketplaces/marketplaces.js b/scripts/system/marketplaces/marketplaces.js index 2ca79b49f0..655d286049 100644 --- a/scripts/system/marketplaces/marketplaces.js +++ b/scripts/system/marketplaces/marketplaces.js @@ -613,7 +613,7 @@ var onQmlMessageReceived = function onQmlMessageReceived(message) { case 'passphrasePopup_cancelClicked': case 'needsLogIn_cancelClicked': // Should/must NOT check for wallet setup. - ui.open(MARKETPLACE_URL_INITIAL, MARKETPLACES_INJECT_SCRIPT_URL); + openMarketplace(); break; case 'needsLogIn_loginClicked': openLoginWindow(); From 8357f1f9b5cbc792ff0ad45cbb4de752452e0f4b Mon Sep 17 00:00:00 2001 From: Roxanne Skelly <roxanne@highfidelity.io> Date: Mon, 28 Jan 2019 11:10:35 -0800 Subject: [PATCH 05/18] CR fixes --- .../resources/qml/hifi/commerce/marketplace/Marketplace.qml | 4 +++- .../qml/hifi/commerce/marketplace/MarketplaceListItem.qml | 4 ++-- 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/interface/resources/qml/hifi/commerce/marketplace/Marketplace.qml b/interface/resources/qml/hifi/commerce/marketplace/Marketplace.qml index 10b59ac83b..cbeedfbb79 100644 --- a/interface/resources/qml/hifi/commerce/marketplace/Marketplace.qml +++ b/interface/resources/qml/hifi/commerce/marketplace/Marketplace.qml @@ -728,7 +728,7 @@ Rectangle { top: parent.top leftMargin: 20 } - width: 322 + width: root.isLoggedIn ? 322 : 242 height: 36 radius: 4 @@ -790,6 +790,8 @@ Rectangle { glyph: model.glyph text: model.name + visible: root.isLoggedIn || model.sortString != "my_likes" + checked: ListView.isCurrentItem onClicked: { diff --git a/interface/resources/qml/hifi/commerce/marketplace/MarketplaceListItem.qml b/interface/resources/qml/hifi/commerce/marketplace/MarketplaceListItem.qml index eb99106cf4..2f37637e40 100644 --- a/interface/resources/qml/hifi/commerce/marketplace/MarketplaceListItem.qml +++ b/interface/resources/qml/hifi/commerce/marketplace/MarketplaceListItem.qml @@ -185,7 +185,7 @@ Rectangle { bottom: parent.bottom } onClicked: { - if(isLoggedIn) { + if (isLoggedIn) { root.liked = !root.liked; root.likes = root.liked ? root.likes + 1 : root.likes - 1; MarketplaceScriptingInterface.marketplaceItemLike(root.item_id, root.liked); @@ -203,7 +203,7 @@ Rectangle { left: parent.left right: parent.right } - height: width*0.5625 + height: width * 0.5625 source: root.image_url fillMode: Image.PreserveAspectCrop From 2fe78383edc9c2f268b0a1ebaf9c4f1a97034257 Mon Sep 17 00:00:00 2001 From: Roxanne Skelly <roxanne@highfidelity.io> Date: Mon, 28 Jan 2019 15:14:49 -0800 Subject: [PATCH 06/18] QmlMarketplace - Fix issues found during test * command line --url hifiapp:MARKET was not bringing up correct marketplace * Search field was not being cleared when 'home' button was clicked after a search * tablet button was not lit when marketplace was launched --- .../resources/qml/hifi/commerce/marketplace/Marketplace.qml | 1 + interface/src/commerce/QmlCommerce.cpp | 3 +-- scripts/system/marketplaces/marketplaces.js | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/interface/resources/qml/hifi/commerce/marketplace/Marketplace.qml b/interface/resources/qml/hifi/commerce/marketplace/Marketplace.qml index cbeedfbb79..8ba5d0bac0 100644 --- a/interface/resources/qml/hifi/commerce/marketplace/Marketplace.qml +++ b/interface/resources/qml/hifi/commerce/marketplace/Marketplace.qml @@ -665,6 +665,7 @@ Rectangle { categoriesListView.currentIndex = -1; categoriesText.text = "Categories"; root.categoryString = ""; + searchField.text = ""; getMarketplaceItems(); } } diff --git a/interface/src/commerce/QmlCommerce.cpp b/interface/src/commerce/QmlCommerce.cpp index 5236c5a7fb..046e697b6d 100644 --- a/interface/src/commerce/QmlCommerce.cpp +++ b/interface/src/commerce/QmlCommerce.cpp @@ -54,11 +54,10 @@ void QmlCommerce::openSystemApp(const QString& appName) { {"GOTO", "hifi/tablet/TabletAddressDialog.qml"}, {"PEOPLE", "hifi/Pal.qml"}, {"WALLET", "hifi/commerce/wallet/Wallet.qml"}, - {"MARKET", "/marketplace.html"} + {"MARKET", "hifi/commerce/marketplace/Marketplace.qml"} }; static const QMap<QString, QString> systemInject{ - {"MARKET", "/scripts/system/html/js/marketplacesInject.js"} }; diff --git a/scripts/system/marketplaces/marketplaces.js b/scripts/system/marketplaces/marketplaces.js index 655d286049..9d571d9284 100644 --- a/scripts/system/marketplaces/marketplaces.js +++ b/scripts/system/marketplaces/marketplaces.js @@ -703,7 +703,7 @@ var onMarketplaceScreen = false; var onWalletScreen = false; var onTabletScreenChanged = function onTabletScreenChanged(type, url) { ui.setCurrentVisibleScreenMetadata(type, url); - onMarketplaceScreen = type === "Web" && url.indexOf(MARKETPLACE_URL) !== -1; + onMarketplaceScreen = type === "QML" && url.indexOf(MARKETPLACE_QML_PATH) !== -1; onInspectionCertificateScreen = type === "QML" && url.indexOf(MARKETPLACE_INSPECTIONCERTIFICATE_QML_PATH) !== -1; var onWalletScreenNow = url.indexOf(MARKETPLACE_WALLET_QML_PATH) !== -1; var onCommerceScreenNow = type === "QML" && ( From 0fdbca8ade2d24f931b9e6537241cb36cab97cc0 Mon Sep 17 00:00:00 2001 From: Roxanne Skelly <roxanne@highfidelity.io> Date: Tue, 29 Jan 2019 16:07:27 -0800 Subject: [PATCH 07/18] QmlMarketplace Bugfixes * Fix upgrades * Certificate 'View in Marketplace' wasn't working * command-line hifiapp:MARKET wasn't launching * Home link wasn't disappearing where it should * Log In button on marketplace wasn't working * Other minor UI bugfixes --- .../qml/hifi/commerce/checkout/Checkout.qml | 2 +- .../common/EmulatedMarketplaceHeader.qml | 163 +----------------- .../InspectionCertificate.qml | 12 +- .../hifi/commerce/marketplace/Marketplace.qml | 52 +++--- .../commerce/marketplace/MarketplaceItem.qml | 5 +- .../hifi/commerce/purchases/PurchasedItem.qml | 24 +-- .../qml/hifi/commerce/purchases/Purchases.qml | 59 +------ .../qml/hifi/commerce/wallet/WalletHome.qml | 2 +- scripts/system/commerce/wallet.js | 12 +- scripts/system/marketplaces/marketplaces.js | 26 ++- 10 files changed, 66 insertions(+), 291 deletions(-) diff --git a/interface/resources/qml/hifi/commerce/checkout/Checkout.qml b/interface/resources/qml/hifi/commerce/checkout/Checkout.qml index c76f5a428a..2d5f77f006 100644 --- a/interface/resources/qml/hifi/commerce/checkout/Checkout.qml +++ b/interface/resources/qml/hifi/commerce/checkout/Checkout.qml @@ -662,7 +662,7 @@ Rectangle { anchors.right: parent.right; text: "Cancel" onClicked: { - sendToScript({method: 'checkout_cancelClicked', params: itemId}); + sendToScript({method: 'checkout_cancelClicked', itemId: itemId}); } } } diff --git a/interface/resources/qml/hifi/commerce/common/EmulatedMarketplaceHeader.qml b/interface/resources/qml/hifi/commerce/common/EmulatedMarketplaceHeader.qml index 0d0af875d1..759d61b924 100644 --- a/interface/resources/qml/hifi/commerce/common/EmulatedMarketplaceHeader.qml +++ b/interface/resources/qml/hifi/commerce/common/EmulatedMarketplaceHeader.qml @@ -24,11 +24,8 @@ Item { HifiConstants { id: hifi; } id: root; - property string referrerURL: (Account.metaverseServerURL + "/marketplace?"); - readonly property int additionalDropdownHeight: usernameDropdown.height - myUsernameButton.anchors.bottomMargin; - property alias usernameDropdownVisible: usernameDropdown.visible; - height: mainContainer.height + additionalDropdownHeight; + height: mainContainer.height; Connections { target: Commerce; @@ -93,77 +90,7 @@ Item { MouseArea { anchors.fill: parent; onClicked: { - sendToParent({method: "header_marketplaceImageClicked", referrerURL: root.referrerURL}); - } - } - } - - Item { - id: buttonAndUsernameContainer; - anchors.left: marketplaceHeaderImage.right; - anchors.leftMargin: 8; - anchors.top: parent.top; - anchors.bottom: parent.bottom; - anchors.bottomMargin: 10; - anchors.right: securityImage.left; - anchors.rightMargin: 6; - - TextMetrics { - id: textMetrics; - font.family: "Raleway" - text: usernameText.text; - } - - Rectangle { - id: myUsernameButton; - anchors.right: parent.right; - anchors.verticalCenter: parent.verticalCenter; - height: 40; - width: usernameText.width + 25; - color: "white"; - radius: 4; - border.width: 1; - border.color: hifi.colors.lightGray; - - // Username Text - RalewayRegular { - id: usernameText; - text: Account.username; - // Text size - size: 18; - // Style - color: hifi.colors.baseGray; - elide: Text.ElideRight; - horizontalAlignment: Text.AlignHCenter; - verticalAlignment: Text.AlignVCenter; - width: Math.min(textMetrics.width + 25, 110); - // Anchors - anchors.centerIn: parent; - rightPadding: 10; - } - - HiFiGlyphs { - id: dropdownIcon; - text: hifi.glyphs.caratDn; - // Size - size: 50; - // Anchors - anchors.right: parent.right; - anchors.rightMargin: -14; - anchors.verticalCenter: parent.verticalCenter; - horizontalAlignment: Text.AlignHCenter; - // Style - color: hifi.colors.baseGray; - } - - MouseArea { - anchors.fill: parent; - hoverEnabled: enabled; - onClicked: { - usernameDropdown.visible = !usernameDropdown.visible; - } - onEntered: usernameText.color = hifi.colors.baseGrayShadow; - onExited: usernameText.color = hifi.colors.baseGray; + sendToParent({method: "header_marketplaceImageClicked"}); } } } @@ -205,92 +132,6 @@ Item { } } - Item { - id: usernameDropdown; - z: 998; - visible: false; - anchors.top: buttonAndUsernameContainer.bottom; - anchors.topMargin: -buttonAndUsernameContainer.anchors.bottomMargin; - anchors.right: buttonAndUsernameContainer.right; - height: childrenRect.height; - width: 150; - - Rectangle { - id: myItemsButton; - color: hifi.colors.white; - anchors.top: parent.top; - anchors.left: parent.left; - anchors.right: parent.right; - height: 50; - - RalewaySemiBold { - anchors.fill: parent; - text: "My Submissions" - color: hifi.colors.baseGray; - horizontalAlignment: Text.AlignHCenter; - verticalAlignment: Text.AlignVCenter; - size: 18; - } - - MouseArea { - anchors.fill: parent; - hoverEnabled: true; - onEntered: { - myItemsButton.color = hifi.colors.blueHighlight; - } - onExited: { - myItemsButton.color = hifi.colors.white; - } - onClicked: { - sendToParent({method: "header_myItemsClicked"}); - } - } - } - - Rectangle { - id: logOutButton; - color: hifi.colors.white; - anchors.top: myItemsButton.bottom; - anchors.left: parent.left; - anchors.right: parent.right; - height: 50; - - RalewaySemiBold { - anchors.fill: parent; - text: "Log Out" - color: hifi.colors.baseGray; - horizontalAlignment: Text.AlignHCenter; - verticalAlignment: Text.AlignVCenter; - size: 18; - } - - MouseArea { - anchors.fill: parent; - hoverEnabled: true; - onEntered: { - logOutButton.color = hifi.colors.blueHighlight; - } - onExited: { - logOutButton.color = hifi.colors.white; - } - onClicked: { - Account.logOut(); - } - } - } - } - - DropShadow { - z: 997; - visible: usernameDropdown.visible; - anchors.fill: usernameDropdown; - horizontalOffset: 3; - verticalOffset: 3; - radius: 8.0; - samples: 17; - color: "#80000000"; - source: usernameDropdown; - } } diff --git a/interface/resources/qml/hifi/commerce/inspectionCertificate/InspectionCertificate.qml b/interface/resources/qml/hifi/commerce/inspectionCertificate/InspectionCertificate.qml index 232e17d851..8ca34af28a 100644 --- a/interface/resources/qml/hifi/commerce/inspectionCertificate/InspectionCertificate.qml +++ b/interface/resources/qml/hifi/commerce/inspectionCertificate/InspectionCertificate.qml @@ -22,7 +22,6 @@ Rectangle { HifiConstants { id: hifi; } id: root; - property string marketplaceUrl: ""; property string entityId: ""; property string certificateId: ""; property string itemName: "--"; @@ -30,6 +29,7 @@ Rectangle { property string itemEdition: "--"; property string dateAcquired: "--"; property string itemCost: "--"; + property string marketplace_item_id: ""; property string certTitleTextColor: hifi.colors.darkGray; property string certTextColor: hifi.colors.white; property string infoTextColor: hifi.colors.blueAccent; @@ -69,7 +69,7 @@ Rectangle { errorText.text = "Information about this certificate is currently unavailable. Please try again later."; } } else { - root.marketplaceUrl = result.data.marketplace_item_url; + root.marketplace_item_id = result.data.marketplace_item_id; root.isMyCert = result.isMyCert ? result.isMyCert : false; if (root.certInfoReplaceMode > 3) { @@ -352,7 +352,7 @@ Rectangle { anchors.fill: parent; hoverEnabled: enabled; onClicked: { - sendToScript({method: 'inspectionCertificate_showInMarketplaceClicked', marketplaceUrl: root.marketplaceUrl}); + sendToScript({method: 'inspectionCertificate_showInMarketplaceClicked', itemId: root.marketplace_item_id}); } onEntered: itemName.color = hifi.colors.blueHighlight; onExited: itemName.color = root.certTextColor; @@ -391,7 +391,7 @@ Rectangle { // "Show In Marketplace" button HifiControlsUit.Button { id: showInMarketplaceButton; - enabled: root.marketplaceUrl; + enabled: root.marketplace_item_id && marketplace_item_id !== ""; color: hifi.buttons.blue; colorScheme: hifi.colorSchemes.light; anchors.bottom: parent.bottom; @@ -401,7 +401,7 @@ Rectangle { height: 40; text: "View In Market" onClicked: { - sendToScript({method: 'inspectionCertificate_showInMarketplaceClicked', marketplaceUrl: root.marketplaceUrl}); + sendToScript({method: 'inspectionCertificate_showInMarketplaceClicked', itemId: root.marketplace_item_id}); } } } @@ -620,7 +620,7 @@ Rectangle { root.itemOwner = "--"; root.itemEdition = "--"; root.dateAcquired = "--"; - root.marketplaceUrl = ""; + root.marketplace_item_id = ""; root.itemCost = "--"; root.isMyCert = false; errorText.text = ""; diff --git a/interface/resources/qml/hifi/commerce/marketplace/Marketplace.qml b/interface/resources/qml/hifi/commerce/marketplace/Marketplace.qml index 8ba5d0bac0..d32d298acd 100644 --- a/interface/resources/qml/hifi/commerce/marketplace/Marketplace.qml +++ b/interface/resources/qml/hifi/commerce/marketplace/Marketplace.qml @@ -131,7 +131,6 @@ Rectangle { onLoginStatusResult: { root.isLoggedIn = isLoggedIn; - itemsLoginStatus.visible = !isLoggedIn; } } @@ -179,38 +178,27 @@ Rectangle { anchors.left: parent.left anchors.top: parent.top width: parent.width - height: 50 + height: 60 visible: true Image { - id: marketplaceIcon + id: marketplaceHeaderImage; + source: "../common/images/marketplaceHeaderImage.png"; + anchors.top: parent.top; + anchors.topMargin: 2; + anchors.bottom: parent.bottom; + anchors.bottomMargin: 0; + anchors.left: parent.left; + anchors.leftMargin: 8; + width: 140; + fillMode: Image.PreserveAspectFit; - anchors { - left: parent.left - leftMargin: 8 - verticalCenter: parent.verticalCenter + MouseArea { + anchors.fill: parent; + onClicked: { + sendToParent({method: "header_marketplaceImageClicked"}); + } } - height: 20 - width: marketplaceIcon.height - source: "../../../../images/hifi-logo-blackish.svg" - visible: true - } - - RalewaySemiBold { - id: titleBarText - - anchors { - top: parent.top - left: marketplaceIcon.right - bottom: parent.bottom - leftMargin: 6 - } - width: paintedWidth - - text: "Marketplace" - size: hifi.fontSizes.overlayTitle - color: hifi.colors.black - verticalAlignment: Text.AlignVCenter } } @@ -403,7 +391,7 @@ Rectangle { anchors.fill: parent anchors.rightMargin: 10 width: parent.width - + currentIndex: -1; clip: true model: categoriesModel @@ -587,7 +575,7 @@ Rectangle { left: parent.left right: parent.right leftMargin: 15 - rightMargin: 15 + top: parent.top+15 } height: root.isLoggedIn ? 0 : 80 @@ -944,7 +932,7 @@ Rectangle { isLoggedIn: root.isLoggedIn; onBuy: { - sendToScript({method: 'marketplace_checkout', itemId: item_id}); + sendToScript({method: 'marketplace_checkout', itemId: item_id, itemEdition: edition}); } onShowLicense: { @@ -1142,7 +1130,7 @@ Rectangle { console.log("A message with method 'updateMarketplaceQMLItem' was sent without an itemId!"); return; } - + marketplaceItem.edition = message.params.edition ? message.params.edition : -1; MarketplaceScriptingInterface.getMarketplaceItem(message.params.itemId); break; } diff --git a/interface/resources/qml/hifi/commerce/marketplace/MarketplaceItem.qml b/interface/resources/qml/hifi/commerce/marketplace/MarketplaceItem.qml index 5795d0d67d..0478f38764 100644 --- a/interface/resources/qml/hifi/commerce/marketplace/MarketplaceItem.qml +++ b/interface/resources/qml/hifi/commerce/marketplace/MarketplaceItem.qml @@ -41,6 +41,7 @@ Rectangle { property bool available: false property string created_at: "" property bool isLoggedIn: false; + property int edition: -1; onCategoriesChanged: { categoriesListModel.clear(); @@ -228,8 +229,8 @@ Rectangle { } height: 50 - text: root.available ? (root.price ? root.price : "FREE") : "UNAVAILABLE (not for sale)" - enabled: root.available + text: root.edition >= 0 ? "UPGRADE FOR FREE" : (root.available ? (root.price ? root.price : "FREE") : "UNAVAILABLE (not for sale)") + enabled: root.edition >= 0 || root.available buttonGlyph: root.available ? (root.price ? hifi.glyphs.hfc : "") : "" color: hifi.buttons.blue diff --git a/interface/resources/qml/hifi/commerce/purchases/PurchasedItem.qml b/interface/resources/qml/hifi/commerce/purchases/PurchasedItem.qml index f7dc26df5f..df6e216b32 100644 --- a/interface/resources/qml/hifi/commerce/purchases/PurchasedItem.qml +++ b/interface/resources/qml/hifi/commerce/purchases/PurchasedItem.qml @@ -28,6 +28,7 @@ Item { property string purchaseStatus; property string itemName; property string itemId; + property string updateItemId; property string itemPreviewImageUrl; property string itemHref; property string certificateId; @@ -45,9 +46,9 @@ Item { property bool cardBackVisible; property bool isInstalled; property string wornEntityID; - property string upgradeUrl; + property string updatedItemId; property string upgradeTitle; - property bool updateAvailable: root.upgradeUrl !== ""; + property bool updateAvailable: root.updateItemId && root.updateItemId !== ""; property bool valid; property string originalStatusText; @@ -175,7 +176,7 @@ Item { Item { property alias buttonGlyphText: buttonGlyph.text; - property alias buttonText: buttonText.text; + property alias itemButtonText: buttonText.text; property alias glyphSize: buttonGlyph.size; property string buttonColor: hifi.colors.black; property string buttonColor_hover: hifi.colors.blueHighlight; @@ -243,7 +244,7 @@ Item { onLoaded: { item.enabled = root.valid; item.buttonGlyphText = hifi.glyphs.gift; - item.buttonText = "Gift"; + item.itemButtonText = "Gift"; item.buttonClicked = function() { sendToPurchases({ method: 'flipCard', closeAll: true }); sendToPurchases({ @@ -270,7 +271,7 @@ Item { onLoaded: { item.buttonGlyphText = hifi.glyphs.market; - item.buttonText = "View in Marketplace"; + item.itemButtonText = "View in Marketplace"; item.buttonClicked = function() { sendToPurchases({ method: 'flipCard', closeAll: true }); sendToPurchases({method: 'purchases_itemInfoClicked', itemId: root.itemId}); @@ -288,7 +289,7 @@ Item { onLoaded: { item.buttonGlyphText = hifi.glyphs.certificate; - item.buttonText = "View Certificate"; + item.itemButtonText = "View Certificate"; item.buttonClicked = function() { sendToPurchases({ method: 'flipCard', closeAll: true }); sendToPurchases({method: 'purchases_itemCertificateClicked', itemCertificateId: root.certificateId}); @@ -307,7 +308,7 @@ Item { onLoaded: { item.buttonGlyphText = hifi.glyphs.uninstall; - item.buttonText = "Uninstall"; + item.itemButtonText = "Uninstall"; item.buttonClicked = function() { sendToPurchases({ method: 'flipCard', closeAll: true }); Commerce.uninstallApp(root.itemHref); @@ -330,15 +331,14 @@ Item { onLoaded: { item.buttonGlyphText = hifi.glyphs.update; - item.buttonText = "Update"; + item.itemButtonText = "Update"; item.buttonColor = "#E2334D"; item.buttonClicked = function() { sendToPurchases({ method: 'flipCard', closeAll: true }); sendToPurchases({ method: 'updateItemClicked', - itemId: root.itemId, + itemId: root.updateAvailable ? root.updateItemId : root.itemId, itemEdition: root.itemEdition, - upgradeUrl: root.upgradeUrl, itemHref: root.itemHref, itemType: root.itemType, isInstalled: root.isInstalled, @@ -378,10 +378,10 @@ Item { function updateProperties() { if (updateButton.visible && uninstallButton.visible) { - item.buttonText = ""; + item.itemButtonText = ""; item.glyphSize = 20; } else { - item.buttonText = "Send to Trash"; + item.itemButtonText = "Send to Trash"; item.glyphSize = 30; } } diff --git a/interface/resources/qml/hifi/commerce/purchases/Purchases.qml b/interface/resources/qml/hifi/commerce/purchases/Purchases.qml index 9433618b6b..bcc2a2821c 100644 --- a/interface/resources/qml/hifi/commerce/purchases/Purchases.qml +++ b/interface/resources/qml/hifi/commerce/purchases/Purchases.qml @@ -29,7 +29,6 @@ Rectangle { id: root; property string activeView: "initialize"; - property string referrerURL: ""; property bool securityImageResultReceived: false; property bool purchasesReceived: false; property bool punctuationMode: false; @@ -154,55 +153,10 @@ Rectangle { } } - // - // TITLE BAR START - // - HifiCommerceCommon.EmulatedMarketplaceHeader { - id: titleBarContainer; - z: 997; - visible: false; - height: 100; - // Size - width: parent.width; - // Anchors - anchors.left: parent.left; - anchors.top: parent.top; - - Connections { - onSendToParent: { - if (msg.method === 'needsLogIn' && root.activeView !== "needsLogIn") { - root.activeView = "needsLogIn"; - } else if (msg.method === 'showSecurityPicLightbox') { - lightboxPopup.titleText = "Your Security Pic"; - lightboxPopup.bodyImageSource = msg.securityImageSource; - lightboxPopup.bodyText = lightboxPopup.securityPicBodyText; - lightboxPopup.button1text = "CLOSE"; - lightboxPopup.button1method = function() { - lightboxPopup.visible = false; - } - lightboxPopup.visible = true; - } else { - sendToScript(msg); - } - } - } - } - MouseArea { - enabled: titleBarContainer.usernameDropdownVisible; - anchors.fill: parent; - onClicked: { - titleBarContainer.usernameDropdownVisible = false; - } - } - // - // TITLE BAR END - // - Rectangle { id: initialize; visible: root.activeView === "initialize"; - anchors.top: titleBarContainer.bottom; - anchors.topMargin: -titleBarContainer.additionalDropdownHeight; + anchors.top: parent.top; anchors.bottom: parent.bottom; anchors.left: parent.left; anchors.right: parent.right; @@ -219,8 +173,7 @@ Rectangle { id: installedAppsContainer; z: 998; visible: false; - anchors.top: titleBarContainer.bottom; - anchors.topMargin: -titleBarContainer.additionalDropdownHeight; + anchors.top: parent.top; anchors.left: parent.left; anchors.bottom: parent.bottom; width: parent.width; @@ -422,8 +375,8 @@ Rectangle { // Anchors anchors.left: parent.left; anchors.right: parent.right; - anchors.top: titleBarContainer.bottom; - anchors.topMargin: 8 - titleBarContainer.additionalDropdownHeight; + anchors.top: parent.top; + anchors.topMargin: 8; anchors.bottom: parent.bottom; // @@ -585,6 +538,7 @@ Rectangle { delegate: PurchasedItem { itemName: title; itemId: id; + updateItemId: model.upgrade_id ? model.upgrade_id : ""; itemPreviewImageUrl: preview; itemHref: download_url; certificateId: certificate_id; @@ -596,7 +550,6 @@ Rectangle { cardBackVisible: model.cardBackVisible || false; isInstalled: model.isInstalled || false; wornEntityID: model.wornEntityID; - upgradeUrl: model.upgrade_url; upgradeTitle: model.upgrade_title; itemType: model.item_type; valid: model.valid; @@ -1083,8 +1036,6 @@ Rectangle { function fromScript(message) { switch (message.method) { case 'updatePurchases': - referrerURL = message.referrerURL || ""; - titleBarContainer.referrerURL = message.referrerURL || ""; filterBar.text = message.filterText ? message.filterText : ""; break; case 'purchases_showMyItems': diff --git a/interface/resources/qml/hifi/commerce/wallet/WalletHome.qml b/interface/resources/qml/hifi/commerce/wallet/WalletHome.qml index cf293a06df..eb8aa0f809 100644 --- a/interface/resources/qml/hifi/commerce/wallet/WalletHome.qml +++ b/interface/resources/qml/hifi/commerce/wallet/WalletHome.qml @@ -335,7 +335,7 @@ Item { if (link.indexOf("users/") !== -1) { sendSignalToWallet({method: 'transactionHistory_usernameLinkClicked', usernameLink: link}); } else { - sendSignalToWallet({method: 'transactionHistory_linkClicked', marketplaceLink: link}); + sendSignalToWallet({method: 'transactionHistory_linkClicked', itemId: model.marketplace_item}); } } } diff --git a/scripts/system/commerce/wallet.js b/scripts/system/commerce/wallet.js index 7cacfd7935..17ff918243 100644 --- a/scripts/system/commerce/wallet.js +++ b/scripts/system/commerce/wallet.js @@ -420,10 +420,10 @@ function fromQml(message) { case 'purchases': case 'marketplace cta': case 'mainPage': - ui.open(MARKETPLACE_URL, MARKETPLACES_INJECT_SCRIPT_URL); + openMarketplace(); break; - default: // User needs to return to an individual marketplace item URL - ui.open(MARKETPLACE_URL + '/items/' + message.referrer, MARKETPLACES_INJECT_SCRIPT_URL); + default: + openMarketplace(); break; } break; @@ -435,13 +435,13 @@ function fromQml(message) { case 'maybeEnableHmdPreview': break; // do nothing here, handled in marketplaces.js case 'transactionHistory_linkClicked': - ui.open(message.marketplaceLink, MARKETPLACES_INJECT_SCRIPT_URL); + openMarketplace(message.itemId); break; case 'goToMarketplaceMainPage': - ui.open(MARKETPLACE_URL, MARKETPLACES_INJECT_SCRIPT_URL); + openMarketplace(); break; case 'goToMarketplaceItemPage': - ui.open(MARKETPLACE_URL + '/items/' + message.itemId, MARKETPLACES_INJECT_SCRIPT_URL); + openMarketplace(message.itemId); break; case 'refreshConnections': print('Refreshing Connections...'); diff --git a/scripts/system/marketplaces/marketplaces.js b/scripts/system/marketplaces/marketplaces.js index 9d571d9284..b7a6b951a9 100644 --- a/scripts/system/marketplaces/marketplaces.js +++ b/scripts/system/marketplaces/marketplaces.js @@ -19,6 +19,7 @@ var selectionDisplay = null; // for gridTool.js to ignore var AppUi = Script.require('appUi'); Script.include("/~/system/libraries/gridTool.js"); Script.include("/~/system/libraries/connectionUtils.js"); +Script.include("/~/system/libraries/accountUtils.js"); var MARKETPLACE_CHECKOUT_QML_PATH = "hifi/commerce/checkout/Checkout.qml"; var MARKETPLACE_INSPECTIONCERTIFICATE_QML_PATH = "hifi/commerce/inspectionCertificate/InspectionCertificate.qml"; @@ -156,13 +157,12 @@ function onMarketplaceOpen(referrer) { } } -function openMarketplace(optionalItem) { +function openMarketplace(optionalItem, edition) { ui.open(MARKETPLACE_QML_PATH); - if (optionalItem) { ui.tablet.sendToQml({ method: 'updateMarketplaceQMLItem', - params: { itemId: optionalItem } + params: { itemId: optionalItem, edition: edition } }); } } @@ -486,7 +486,6 @@ function onWebEventReceived(message) { } else if (message.type === "WALLET_SETUP") { setupWallet('marketplace cta'); } else if (message.type === "MY_ITEMS") { - referrerURL = MARKETPLACE_URL_INITIAL; filterText = ""; ui.open(MARKETPLACE_PURCHASES_QML_PATH); wireQmlEventBridge(true); @@ -519,7 +518,6 @@ var onQmlMessageReceived = function onQmlMessageReceived(message) { if (message.messageSrc === "HTML") { return; } - console.log(JSON.stringify(message)); switch (message.method) { case 'gotoBank': ui.close(); @@ -548,11 +546,10 @@ var onQmlMessageReceived = function onQmlMessageReceived(message) { openWallet(); break; case 'checkout_cancelClicked': - openMarketplace(message.params); + openMarketplace(message.itemId); break; case 'header_goToPurchases': case 'checkout_goToPurchases': - referrerURL = MARKETPLACE_URL_INITIAL; filterText = message.filterText; ui.open(MARKETPLACE_PURCHASES_QML_PATH); break; @@ -602,13 +599,13 @@ var onQmlMessageReceived = function onQmlMessageReceived(message) { } break; case 'header_marketplaceImageClicked': - openMarketplace(message.referrerURL); + openMarketplace(); break; case 'purchases_goToMarketplaceClicked': openMarketplace(); break; case 'updateItemClicked': - openMarketplace(message.upgradeUrl + "?edition=" + message.itemEdition); + openMarketplace(message.itemId, message.itemEdition); break; case 'passphrasePopup_cancelClicked': case 'needsLogIn_cancelClicked': @@ -638,10 +635,10 @@ var onQmlMessageReceived = function onQmlMessageReceived(message) { ContextOverlay.requestOwnershipVerification(message.entity); break; case 'inspectionCertificate_showInMarketplaceClicked': - openMarketplace(message.marketplaceUrl); + console.log("INSPECTION CERTIFICATE SHOW IN MARKETPLACE CLICKED: " + message.itemId); + openMarketplace(message.itemId); break; case 'header_myItemsClicked': - referrerURL = MARKETPLACE_URL_INITIAL; filterText = ""; ui.open(MARKETPLACE_PURCHASES_QML_PATH); wireQmlEventBridge(true); @@ -750,11 +747,8 @@ var onTabletScreenChanged = function onTabletScreenChanged(type, url) { Keyboard.raised = false; } - if (type === "Web" && url.indexOf(MARKETPLACE_URL) !== -1) { - ContextOverlay.isInMarketplaceInspectionMode = true; - } else { - ContextOverlay.isInMarketplaceInspectionMode = false; - } + ContextOverlay.isInMarketplaceInspectionMode = false; + if (onInspectionCertificateScreen) { setCertificateInfo(contextOverlayEntity); From 39ad36a4d03c2fdaad0216ddaeb2209c66cc3cf0 Mon Sep 17 00:00:00 2001 From: Roxanne Skelly <roxanne@highfidelity.io> Date: Fri, 1 Feb 2019 15:27:17 -0800 Subject: [PATCH 08/18] QmlMarketplace - bugfixes and Markup rendering of descriptions * Render markup in descriptions (bold, italic, quote, etc.) * Back button from marketplaces list now works properly * Layout fixes --- .../hifi/commerce/marketplace/Marketplace.qml | 133 +++++++++++------ .../commerce/marketplace/MarketplaceItem.qml | 136 +++++++++++++----- scripts/system/html/js/marketplacesInject.js | 9 +- scripts/system/marketplaces/marketplaces.js | 13 +- 4 files changed, 210 insertions(+), 81 deletions(-) diff --git a/interface/resources/qml/hifi/commerce/marketplace/Marketplace.qml b/interface/resources/qml/hifi/commerce/marketplace/Marketplace.qml index d32d298acd..a4cf260173 100644 --- a/interface/resources/qml/hifi/commerce/marketplace/Marketplace.qml +++ b/interface/resources/qml/hifi/commerce/marketplace/Marketplace.qml @@ -32,7 +32,7 @@ Rectangle { property string activeView: "initialize" property int currentSortIndex: 0 - property string sortString: "" + property string sortString: "recent" property string categoryString: "" property string searchString: "" property bool keyboardEnabled: HMD.active @@ -45,6 +45,7 @@ Rectangle { function getMarketplaceItems() { marketplaceItemView.visible = false; itemsList.visible = true; + licenseInfo.visible = false; marketBrowseModel.getFirstPage(); { if(root.searchString !== undefined && root.searchString !== "") { @@ -69,7 +70,6 @@ Rectangle { target: GlobalServices onMyUsernameChanged: { - console.log("LOGIN STATUS CHANGING"); Commerce.getLoginStatus(); } } @@ -333,7 +333,7 @@ Rectangle { break; } } - onTextChanged: root.searchString = text + onAccepted: { root.searchString = searchField.text; getMarketplaceItems(); @@ -474,11 +474,11 @@ Rectangle { anchors { fill: parent - topMargin: 120 + topMargin: 115 bottomMargin: 50 } - visible: true; + visible: true HifiModels.PSFListModel { id: marketBrowseModel @@ -565,17 +565,22 @@ Rectangle { header: Item { id: itemsHeading - + height: childrenRect.height width: parent.width - + + Rectangle { + id: itemsSpacer; + height: 20 + } + Rectangle { id: itemsLoginStatus; anchors { + top: itemsSpacer.bottom left: parent.left right: parent.right leftMargin: 15 - top: parent.top+15 } height: root.isLoggedIn ? 0 : 80 @@ -598,7 +603,7 @@ Rectangle { } width: 80; - text: root.price ? root.price : "LOG IN" + text: "LOG IN" onClicked: { sendToScript({method: 'needsLogIn_loginClicked'}); @@ -687,6 +692,7 @@ Rectangle { } Item { id: sort + visible: searchString === undefined || searchString === "" anchors { top: searchScope.bottom; @@ -695,7 +701,7 @@ Rectangle { topMargin: 10; leftMargin: 15; } - height: childrenRect.height + height: visible ? childrenRect.height : 0 RalewayRegular { id: sortText @@ -771,6 +777,7 @@ Rectangle { focus: true clip: true highlightFollowsCurrentItem: false + currentIndex: 1; delegate: SortButton { width: 80 @@ -818,25 +825,39 @@ Rectangle { id: marketplaceItemView anchors.fill: parent - anchors.topMargin: 120 + anchors.topMargin: 115 + anchors.bottomMargin: 50 width: parent.width visible: false - + ScrollView { id: marketplaceItemScrollView - anchors.fill: parent; + anchors.fill: parent clip: true ScrollBar.vertical.policy: ScrollBar.AlwaysOn contentWidth: parent.width + contentHeight: childrenRect.height + + function resize() { + contentHeight = (marketplaceItemContent.y - itemSpacer.y + marketplaceItemContent.height); + } + + Item { + id: itemSpacer + anchors.top: parent.top + height: 15 + } Rectangle { id: itemLoginStatus; anchors { left: parent.left right: parent.right + top: itemSpacer.bottom + topMargin: 10 leftMargin: 15 rightMargin: 15 } @@ -861,7 +882,7 @@ Rectangle { } width: 80; - text: root.price ? root.price : "LOG IN" + text: "LOG IN" onClicked: { sendToScript({method: 'needsLogIn_loginClicked'}); @@ -890,9 +911,9 @@ Rectangle { Rectangle { id: marketplaceItemContent - anchors.top: itemLoginStatus.bottom; + anchors.top: itemLoginStatus.bottom width: parent.width - height: childrenRect.height + 100 + height: childrenRect.height; RalewaySemiBold { id: backText @@ -900,6 +921,7 @@ Rectangle { anchors { top: parent.top left: parent.left + topMargin: 10 leftMargin: 15 bottomMargin: 10 } @@ -944,6 +966,10 @@ Rectangle { categoriesText.text = category; getMarketplaceItems(); } + + onResized: { + marketplaceItemScrollView.resize(); + } } } } @@ -975,37 +1001,64 @@ Rectangle { leftMargin: 15 } - HiFiGlyphs { - id: footerGlyph + Item { + id: footerText + anchors.fill: parent + visible: itemsList.visible + + HiFiGlyphs { + id: footerGlyph + + anchors { + left: parent.left + top: parent.top + bottom: parent.bottom + rightMargin: 10 + } + + text: hifi.glyphs.info + size: 34 + color: hifi.colors.white + horizontalAlignment: Text.AlignHCenter + verticalAlignment: Text.AlignVCenter + } + + RalewaySemiBold { + id: footerInfo + + anchors { + left: footerGlyph.right + top: parent.top + bottom: parent.bottom + } + + text: "Get items from Clara.io!" + color: hifi.colors.white + horizontalAlignment: Text.AlignLeft + verticalAlignment: Text.AlignVCenter + size: 18 + } + } + + HifiControlsUit.Button { anchors { left: parent.left top: parent.top bottom: parent.bottom + topMargin: 10 + bottomMargin: 10 + leftMargin: 10 rightMargin: 10 } - text: hifi.glyphs.info - size: 34 - color: hifi.colors.white - horizontalAlignment: Text.AlignHCenter - verticalAlignment: Text.AlignVCenter - } + visible: marketplaceItemView.visible + text: "< BACK" + width: 100 - RalewaySemiBold { - id: footerInfo - - anchors { - left: footerGlyph.right - top: parent.top - bottom: parent.bottom + onClicked: { + getMarketplaceItems(); } - - text: "Get items from Clara.io!" - color: hifi.colors.white - horizontalAlignment: Text.AlignLeft - verticalAlignment: Text.AlignVCenter - size: 18 } HifiControlsUit.Button { @@ -1023,7 +1076,7 @@ Rectangle { width: 180 onClicked: { - sendToScript({method: 'marketplace_marketplaces'}); + sendToScript({method: 'marketplace_marketplaces', itemId: marketplaceItemView.visible ? marketplaceItem.item_id : undefined}); } } } @@ -1041,7 +1094,7 @@ Rectangle { anchors { fill: root - topMargin: 100 + topMargin: 120 bottomMargin: 0 } @@ -1052,7 +1105,7 @@ Rectangle { anchors { bottomMargin: 1 - topMargin: 50 + topMargin: 60 leftMargin: 1 rightMargin: 1 fill: parent diff --git a/interface/resources/qml/hifi/commerce/marketplace/MarketplaceItem.qml b/interface/resources/qml/hifi/commerce/marketplace/MarketplaceItem.qml index 0478f38764..b7e9a711d2 100644 --- a/interface/resources/qml/hifi/commerce/marketplace/MarketplaceItem.qml +++ b/interface/resources/qml/hifi/commerce/marketplace/MarketplaceItem.qml @@ -15,6 +15,7 @@ import Hifi 1.0 as Hifi import QtQuick 2.9 import QtQuick.Controls 2.2 import QtGraphicalEffects 1.0 +import QtWebEngine 1.5 import stylesUit 1.0 import controlsUit 1.0 as HifiControlsUit import "../../../controls" as HifiControls @@ -49,6 +50,11 @@ Rectangle { categoriesListModel.append({"category":category}); }); } + + onDescriptionChanged: { + descriptionTextModel.clear(); + descriptionTextModel.append({text: description}) + } signal buy() signal categoryClicked(string category) @@ -63,7 +69,7 @@ Rectangle { onMarketplaceItemLikeResult: { if (result.status !== 'success') { - console.log("Failed to get Marketplace Categories", result.data.message); + console.log("Like/Unlike item", result.data.message); } else { root.liked = !root.liked; root.likes = root.liked ? root.likes + 1 : root.likes - 1; @@ -98,24 +104,33 @@ Rectangle { var sec = addLeadingZero(a.getSeconds()); return a.toDateString() + " " + drawnHour + ':' + min + amOrPm; } + function evalHeight() { + height = footer.y - header.y + footer.height; + } + + signal resized() + + onHeightChanged: { + resized(); + } anchors { - left: parent.left; - right: parent.right; - leftMargin: 15; - rightMargin: 15; + left: parent.left + right: parent.right + leftMargin: 15 + rightMargin: 15 } - height: childrenRect.height; + height: footer.y - header.y + footer.height Rectangle { id: header anchors { - left: parent.left; - right: parent.right; - top: parent.top; + left: parent.left + right: parent.right + top: parent.top } - height: 50; + height: 50 RalewaySemiBold { id: nameText @@ -137,10 +152,10 @@ Rectangle { id: likes anchors { - top: parent.top; - right: parent.right; - bottom: parent.bottom; - rightMargin: 5; + top: parent.top + right: parent.right + bottom: parent.bottom + rightMargin: 5 } RalewaySemiBold { @@ -216,7 +231,11 @@ Rectangle { right: parent.right; top: itemImage.bottom; } - height: childrenRect.height + height: categoriesList.y - buyButton.y + categoriesList.height + + function evalHeight() { + height = categoriesList.y - buyButton.y + categoriesList.height; + } HifiControlsUit.Button { id: buyButton @@ -309,7 +328,7 @@ Rectangle { top: postedLabel.bottom left: parent.left right: parent.right - topMargin: 10 + topMargin: 5 } text: { getFormattedDate(root.created_at); } @@ -360,6 +379,7 @@ Rectangle { anchors.top: licenseLabel.bottom anchors.left: parent.left + anchors.topMargin: 5 width: paintedWidth text: root.license @@ -371,9 +391,10 @@ Rectangle { RalewaySemiBold { id: licenseHelp - anchors.top: licenseText.bottom; - anchors.left: parent.left; - width: paintedWidth; + anchors.top: licenseText.bottom + anchors.left: parent.left + anchors.topMargin: 5 + width: paintedWidth text: "More about this license" size: 14 @@ -413,6 +434,7 @@ Rectangle { Item { id: descriptionItem + property string text: "" anchors { top: licenseItem.bottom @@ -421,13 +443,16 @@ Rectangle { right: parent.right } height: childrenRect.height - + onHeightChanged: { + footer.evalHeight(); + } RalewaySemiBold { id: descriptionLabel anchors.top: parent.top anchors.left: parent.left width: paintedWidth + height: 20 text: "DESCRIPTION:" size: 14 @@ -435,18 +460,60 @@ Rectangle { verticalAlignment: Text.AlignVCenter } - RalewaySemiBold { - id: descriptionText + //RalewaySemiBold { + // id: descriptionText + // + // anchors.top: descriptionLabel.bottom + // anchors.left: parent.left + // anchors.topMargin: 5 + // width: parent.width + // + // text: root.description + // size: 14 + // color: hifi.colors.lightGray + // verticalAlignment: Text.AlignVCenter + // wrapMode: Text.Wrap + //} + + + ListModel { + id: descriptionTextModel + } + + ListView { + id: descriptionTextView; anchors.top: descriptionLabel.bottom anchors.left: parent.left - width: parent.width + anchors.right: parent.right - text: root.description - size: 14 - color: hifi.colors.lightGray - verticalAlignment: Text.AlignVCenter - wrapMode: Text.Wrap + model: descriptionTextModel + interactive: false + + delegate: Component { + Rectangle { + id: descriptionWebRect + width: parent.width + height: 5 + WebEngineView { + id: descriptionWebView + anchors.fill: parent + + Component.onCompleted: { + loadHtml("<html><head><style>body { color: #393939; font-family: Arial !important;}</style></head><body>"+model.text+"</body></html>"); + } + + onContentsSizeChanged: { + descriptionWebRect.height = contentsSize.height; + descriptionTextView.height = contentsSize.height; + } + + onNewViewRequested: function(request) { + sendToScript({method: 'marketplace_open_link', link: request.requestedUrl}); + } + } + } + } } } @@ -460,7 +527,7 @@ Rectangle { right: parent.right } width: parent.width - height: childrenRect.height + height: childrenRect.height + 50 RalewaySemiBold { id: categoryLabel @@ -480,12 +547,13 @@ Rectangle { ListView { anchors { - left: parent.left; - right: parent.right; - top: categoryLabel.bottom; + left: parent.left + right: parent.right + top: categoryLabel.bottom + bottomMargin: 15 } - height: 20*model.count + height: 24*model.count+10 model: categoriesListModel delegate: RalewaySemiBold { @@ -496,7 +564,7 @@ Rectangle { text: model.category size: 14 - height: 20 + height: 24 color: hifi.colors.blueHighlight verticalAlignment: Text.AlignVCenter diff --git a/scripts/system/html/js/marketplacesInject.js b/scripts/system/html/js/marketplacesInject.js index 74bf8d3fec..8d408169ba 100644 --- a/scripts/system/html/js/marketplacesInject.js +++ b/scripts/system/html/js/marketplacesInject.js @@ -76,9 +76,12 @@ if (document.referrer !== "") { window.history.back(); } else { - EventBridge.emitWebEvent(JSON.stringify({ - type: GOTO_MARKETPLACE - })); + var params = { type: GOTO_MARKETPLACE }; + var itemIdMatch = location.search.match(/itemId=([^&]*)/); + if (itemIdMatch && itemIdMatch.length === 2) { + params.itemId = itemIdMatch[1]; + } + EventBridge.emitWebEvent(JSON.stringify(params)); } }); $("#all-markets").on("click", function () { diff --git a/scripts/system/marketplaces/marketplaces.js b/scripts/system/marketplaces/marketplaces.js index b7a6b951a9..c085763fad 100644 --- a/scripts/system/marketplaces/marketplaces.js +++ b/scripts/system/marketplaces/marketplaces.js @@ -41,7 +41,6 @@ var GOTO_DIRECTORY = "GOTO_DIRECTORY"; var GOTO_MARKETPLACE = "GOTO_MARKETPLACE"; var QUERY_CAN_WRITE_ASSETS = "QUERY_CAN_WRITE_ASSETS"; var CAN_WRITE_ASSETS = "CAN_WRITE_ASSETS"; -var WARN_USER_NO_PERMISSIONS = "WARN_USER_NO_PERMISSIONS"; var CLARA_DOWNLOAD_TITLE = "Preparing Download"; var messageBox = null; @@ -437,9 +436,8 @@ function rezEntity(itemHref, itemType, marketplaceItemTesterId) { var referrerURL; // Used for updating Purchases QML var filterText; // Used for updating Purchases QML function onWebEventReceived(message) { - message = JSON.parse(message); if (message.type === GOTO_MARKETPLACE) { - openMarketplace(); + openMarketplace(message.itemId); } else if (message.type === GOTO_DIRECTORY) { // This is the chooser between marketplaces. Only OUR markteplace // requires/makes-use-of wallet, so doesn't go through openMarketplace bottleneck. @@ -569,7 +567,14 @@ var onQmlMessageReceived = function onQmlMessageReceived(message) { case 'marketplace_marketplaces': // This is the chooser between marketplaces. Only OUR markteplace // requires/makes-use-of wallet, so doesn't go through openMarketplace bottleneck. - ui.open(MARKETPLACES_URL, MARKETPLACES_INJECT_SCRIPT_URL); + var url = MARKETPLACES_URL; + if(message.itemId) { + url = url + "?itemId=" + message.itemId + } + ui.open(url, MARKETPLACES_INJECT_SCRIPT_URL); + break; + case 'marketplace_open_link': + ui.open(message.link); break; case 'checkout_rezClicked': case 'purchases_rezClicked': From 1d7265f668f0974daf21d07e671b9ed125485119 Mon Sep 17 00:00:00 2001 From: Andrew Meadows <andrew@highfidelity.io> Date: Mon, 4 Feb 2019 09:45:06 -0800 Subject: [PATCH 09/18] trust the data in the packet, Luke --- libraries/octree/src/OctreePacketData.cpp | 27 ----------------------- 1 file changed, 27 deletions(-) diff --git a/libraries/octree/src/OctreePacketData.cpp b/libraries/octree/src/OctreePacketData.cpp index a79f0a0c2b..8ab502e951 100755 --- a/libraries/octree/src/OctreePacketData.cpp +++ b/libraries/octree/src/OctreePacketData.cpp @@ -729,12 +729,6 @@ int OctreePacketData::unpackDataFromBytes(const unsigned char *dataBytes, QVecto uint16_t length; memcpy(&length, dataBytes, sizeof(uint16_t)); dataBytes += sizeof(length); - - // FIXME - this size check is wrong if we allow larger packets - if (length * sizeof(glm::vec3) > MAX_OCTREE_UNCOMRESSED_PACKET_SIZE) { - result.resize(0); - return sizeof(uint16_t); - } result.resize(length); memcpy(result.data(), dataBytes, length * sizeof(glm::vec3)); return sizeof(uint16_t) + length * sizeof(glm::vec3); @@ -744,14 +738,7 @@ int OctreePacketData::unpackDataFromBytes(const unsigned char *dataBytes, QVecto uint16_t length; memcpy(&length, dataBytes, sizeof(uint16_t)); dataBytes += sizeof(length); - - // FIXME - this size check is wrong if we allow larger packets - if (length * sizeof(glm::quat) > MAX_OCTREE_UNCOMRESSED_PACKET_SIZE) { - result.resize(0); - return sizeof(uint16_t); - } result.resize(length); - const unsigned char *start = dataBytes; for (int i = 0; i < length; i++) { dataBytes += unpackOrientationQuatFromBytes(dataBytes, result[i]); @@ -764,12 +751,6 @@ int OctreePacketData::unpackDataFromBytes(const unsigned char* dataBytes, QVecto uint16_t length; memcpy(&length, dataBytes, sizeof(uint16_t)); dataBytes += sizeof(length); - - // FIXME - this size check is wrong if we allow larger packets - if (length * sizeof(float) > MAX_OCTREE_UNCOMRESSED_PACKET_SIZE) { - result.resize(0); - return sizeof(uint16_t); - } result.resize(length); memcpy(result.data(), dataBytes, length * sizeof(float)); return sizeof(uint16_t) + length * sizeof(float); @@ -779,14 +760,7 @@ int OctreePacketData::unpackDataFromBytes(const unsigned char* dataBytes, QVecto uint16_t length; memcpy(&length, dataBytes, sizeof(uint16_t)); dataBytes += sizeof(length); - - // FIXME - this size check is wrong if we allow larger packets - if (length / 8 > MAX_OCTREE_UNCOMRESSED_PACKET_SIZE) { - result.resize(0); - return sizeof(uint16_t); - } result.resize(length); - int bit = 0; unsigned char current = 0; const unsigned char *start = dataBytes; @@ -797,7 +771,6 @@ int OctreePacketData::unpackDataFromBytes(const unsigned char* dataBytes, QVecto result[i] = (bool)(current & (1 << bit)); bit = (bit + 1) % BITS_IN_BYTE; } - return (dataBytes - start) + (int)sizeof(uint16_t); } From 1840f874ab4e29e3c78a3ce9c649cac0e2724404 Mon Sep 17 00:00:00 2001 From: Roxanne Skelly <roxanne@roxiware.com> Date: Mon, 4 Feb 2019 13:29:06 -0800 Subject: [PATCH 10/18] Add Attributions to Qml Marketplace --- .../hifi/commerce/marketplace/Marketplace.qml | 6 +- .../commerce/marketplace/MarketplaceItem.qml | 118 ++++++++++++++++-- 2 files changed, 111 insertions(+), 13 deletions(-) diff --git a/interface/resources/qml/hifi/commerce/marketplace/Marketplace.qml b/interface/resources/qml/hifi/commerce/marketplace/Marketplace.qml index a4cf260173..8458e28ba8 100644 --- a/interface/resources/qml/hifi/commerce/marketplace/Marketplace.qml +++ b/interface/resources/qml/hifi/commerce/marketplace/Marketplace.qml @@ -369,7 +369,7 @@ Rectangle { categoriesButton.color = hifi.colors.white; } } - + Rectangle { anchors { left: parent.left; @@ -1057,7 +1057,9 @@ Rectangle { width: 100 onClicked: { - getMarketplaceItems(); + marketplaceItemView.visible = false; + itemsList.visible = true; + licenseInfo.visible = false; } } diff --git a/interface/resources/qml/hifi/commerce/marketplace/MarketplaceItem.qml b/interface/resources/qml/hifi/commerce/marketplace/MarketplaceItem.qml index b7e9a711d2..cb158a2b69 100644 --- a/interface/resources/qml/hifi/commerce/marketplace/MarketplaceItem.qml +++ b/interface/resources/qml/hifi/commerce/marketplace/MarketplaceItem.qml @@ -50,7 +50,15 @@ Rectangle { categoriesListModel.append({"category":category}); }); } - + + onAttributionsChanged: { + attributionsModel.clear(); + root.attributions.forEach(function(attribution) { + console.log("ATTRIBUITION:" + JSON.stringify(attribution)); + attributionsModel.append(attribution); + }); + } + onDescriptionChanged: { descriptionTextModel.clear(); descriptionTextModel.append({text: description}) @@ -339,24 +347,112 @@ Rectangle { } Rectangle { - anchors { - top: posted.bottom; - leftMargin: 15; - topMargin: 15; - } - width: parent.width; - height: 1; - color: hifi.colors.lightGray; + anchors { + top: posted.bottom + leftMargin: 15 + topMargin: 15 + } + width: parent.width + height: 1 + + color: hifi.colors.lightGrayText } + Item { + id: attributions + + anchors { + top: posted.bottom + topMargin: 30 + left: parent.left + right: parent.right + } + width: parent.width + height: attributionsModel.count > 0 ? childrenRect.height : 0 + visible: attributionsModel.count > 0 + + RalewaySemiBold { + id: attributionsLabel + + anchors.top: parent.top + anchors.left: parent.left + width: paintedWidth + + text: "ATTRIBUTIONS:" + size: 14 + color: hifi.colors.lightGrayText + verticalAlignment: Text.AlignVCenter + } + ListModel { + id: attributionsModel + } + + ListView { + anchors { + left: parent.left + right: parent.right + top: attributionsLabel.bottom + bottomMargin: 15 + } + + height: 24*model.count+10 + + model: attributionsModel + delegate: Item { + RalewaySemiBold { + id: attributionName + + anchors.leftMargin: 15 + width: paintedWidth + + text: model.name + size: 14 + height: 24 + color: hifi.colors.baseGray + verticalAlignment: Text.AlignVCenter + } + + RalewaySemiBold { + id: attributionLink + + anchors.leftMargin: 15 + anchors.left: attributionName.right + width: paintedWidth + + text: "Link" + size: 14 + height: 24 + color: hifi.colors.blueHighlight + verticalAlignment: Text.AlignVCenter + + MouseArea { + anchors.fill: attributionLink + + onClicked: sendToScript({method: 'marketplace_open_link', link: model.link}); + } + } + } + } + Rectangle { + + anchors { + bottom: attributions.bottom + leftMargin: 15 + } + width: parent.width + height: 1 + + color: hifi.colors.lightGrayText + } + } Item { id: licenseItem; anchors { - top: posted.bottom + top: attributions.bottom left: parent.left - topMargin: 30 + topMargin: 15 } width: parent.width height: childrenRect.height From e23c589de7f7db344256cc7471d8f29d8ee719ca Mon Sep 17 00:00:00 2001 From: Roxanne Skelly <roxanne@roxiware.com> Date: Mon, 4 Feb 2019 15:04:18 -0800 Subject: [PATCH 11/18] QmlMarketplace - fix issue with footer not appearing in marketplaces list --- .../qml/hifi/commerce/marketplace/MarketplaceItem.qml | 6 ++++-- scripts/system/marketplaces/marketplaces.js | 2 ++ 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/interface/resources/qml/hifi/commerce/marketplace/MarketplaceItem.qml b/interface/resources/qml/hifi/commerce/marketplace/MarketplaceItem.qml index cb158a2b69..3d5b1c3bc8 100644 --- a/interface/resources/qml/hifi/commerce/marketplace/MarketplaceItem.qml +++ b/interface/resources/qml/hifi/commerce/marketplace/MarketplaceItem.qml @@ -262,7 +262,7 @@ Rectangle { color: hifi.buttons.blue onClicked: root.buy(); - } + } Item { id: creatorItem @@ -596,7 +596,9 @@ Rectangle { anchors.fill: parent Component.onCompleted: { - loadHtml("<html><head><style>body { color: #393939; font-family: Arial !important;}</style></head><body>"+model.text+"</body></html>"); + descriptionWebView.enabled = false; + loadHtml("<html><head><style>body { overflow: hidden; color: #393939; font-family: Arial !important;}</style></head><body>"+model.text+"</body></html>"); + descriptionWebView.enabled = true; } onContentsSizeChanged: { diff --git a/scripts/system/marketplaces/marketplaces.js b/scripts/system/marketplaces/marketplaces.js index c085763fad..e059081741 100644 --- a/scripts/system/marketplaces/marketplaces.js +++ b/scripts/system/marketplaces/marketplaces.js @@ -41,6 +41,7 @@ var GOTO_DIRECTORY = "GOTO_DIRECTORY"; var GOTO_MARKETPLACE = "GOTO_MARKETPLACE"; var QUERY_CAN_WRITE_ASSETS = "QUERY_CAN_WRITE_ASSETS"; var CAN_WRITE_ASSETS = "CAN_WRITE_ASSETS"; +var WARN_USER_NO_PERMISSIONS = "WARN_USER_NO_PERMISSIONS"; var CLARA_DOWNLOAD_TITLE = "Preparing Download"; var messageBox = null; @@ -436,6 +437,7 @@ function rezEntity(itemHref, itemType, marketplaceItemTesterId) { var referrerURL; // Used for updating Purchases QML var filterText; // Used for updating Purchases QML function onWebEventReceived(message) { + message = JSON.parse(message); if (message.type === GOTO_MARKETPLACE) { openMarketplace(message.itemId); } else if (message.type === GOTO_DIRECTORY) { From db9b44a71af5c1c9689af23d4b556a5ff53ce3dc Mon Sep 17 00:00:00 2001 From: Roxanne Skelly <roxanne@roxiware.com> Date: Mon, 4 Feb 2019 15:30:53 -0800 Subject: [PATCH 12/18] QmlMarketplace - The QML Text object has sufficient markup to handle all of our needs, so use that instead of webengineview --- .../commerce/marketplace/MarketplaceItem.qml | 62 ++++--------------- 1 file changed, 11 insertions(+), 51 deletions(-) diff --git a/interface/resources/qml/hifi/commerce/marketplace/MarketplaceItem.qml b/interface/resources/qml/hifi/commerce/marketplace/MarketplaceItem.qml index 3d5b1c3bc8..303a193d92 100644 --- a/interface/resources/qml/hifi/commerce/marketplace/MarketplaceItem.qml +++ b/interface/resources/qml/hifi/commerce/marketplace/MarketplaceItem.qml @@ -556,61 +556,21 @@ Rectangle { verticalAlignment: Text.AlignVCenter } - //RalewaySemiBold { - // id: descriptionText - // - // anchors.top: descriptionLabel.bottom - // anchors.left: parent.left - // anchors.topMargin: 5 - // width: parent.width - // - // text: root.description - // size: 14 - // color: hifi.colors.lightGray - // verticalAlignment: Text.AlignVCenter - // wrapMode: Text.Wrap - //} - - - ListModel { - id: descriptionTextModel - } - - ListView { - id: descriptionTextView; + RalewaySemiBold { + id: descriptionText anchors.top: descriptionLabel.bottom anchors.left: parent.left - anchors.right: parent.right - - model: descriptionTextModel - interactive: false + anchors.topMargin: 5 + width: parent.width - delegate: Component { - Rectangle { - id: descriptionWebRect - width: parent.width - height: 5 - WebEngineView { - id: descriptionWebView - anchors.fill: parent - - Component.onCompleted: { - descriptionWebView.enabled = false; - loadHtml("<html><head><style>body { overflow: hidden; color: #393939; font-family: Arial !important;}</style></head><body>"+model.text+"</body></html>"); - descriptionWebView.enabled = true; - } - - onContentsSizeChanged: { - descriptionWebRect.height = contentsSize.height; - descriptionTextView.height = contentsSize.height; - } - - onNewViewRequested: function(request) { - sendToScript({method: 'marketplace_open_link', link: request.requestedUrl}); - } - } - } + text: root.description + size: 14 + color: hifi.colors.lightGray + verticalAlignment: Text.AlignVCenter + wrapMode: Text.Wrap + onLinkActivated: { + sendToScript({method: 'marketplace_open_link', link: link}); } } } From 364a1698feadc52f5509fa605118b874dc3474d7 Mon Sep 17 00:00:00 2001 From: Roxanne Skelly <roxanne@roxiware.com> Date: Mon, 4 Feb 2019 17:24:30 -0800 Subject: [PATCH 13/18] QmlMarketplace - Height of Marketplace Item was improperly calcualted with attributions --- .../commerce/marketplace/MarketplaceItem.qml | 24 +++++++++++-------- 1 file changed, 14 insertions(+), 10 deletions(-) diff --git a/interface/resources/qml/hifi/commerce/marketplace/MarketplaceItem.qml b/interface/resources/qml/hifi/commerce/marketplace/MarketplaceItem.qml index 303a193d92..bb757bedca 100644 --- a/interface/resources/qml/hifi/commerce/marketplace/MarketplaceItem.qml +++ b/interface/resources/qml/hifi/commerce/marketplace/MarketplaceItem.qml @@ -53,15 +53,13 @@ Rectangle { onAttributionsChanged: { attributionsModel.clear(); - root.attributions.forEach(function(attribution) { - console.log("ATTRIBUITION:" + JSON.stringify(attribution)); - attributionsModel.append(attribution); - }); - } - - onDescriptionChanged: { - descriptionTextModel.clear(); - descriptionTextModel.append({text: description}) + if(root.attributions) { + root.attributions.forEach(function(attribution) { + console.log("ATTRIBUITION:" + JSON.stringify(attribution)); + attributionsModel.append(attribution); + }); + } + footer.evalHeight(); } signal buy() @@ -243,6 +241,7 @@ Rectangle { function evalHeight() { height = categoriesList.y - buyButton.y + categoriesList.height; + console.log("HEIGHT: " + height); } HifiControlsUit.Button { @@ -378,6 +377,7 @@ Rectangle { anchors.top: parent.top anchors.left: parent.left width: paintedWidth + height: paintedHeight text: "ATTRIBUTIONS:" size: 14 @@ -572,6 +572,8 @@ Rectangle { onLinkActivated: { sendToScript({method: 'marketplace_open_link', link: link}); } + + onHeightChanged: { footer.evalHeight(); } } } @@ -585,7 +587,9 @@ Rectangle { right: parent.right } width: parent.width - height: childrenRect.height + 50 + height: categoriesListModel.count*24 + categoryLabel.height + (isLoggedIn ? 50 : 150) + + onHeightChanged: { footer.evalHeight(); } RalewaySemiBold { id: categoryLabel From 5a4960b3001cdb32d88af6b0b4d5ca36a466ccf7 Mon Sep 17 00:00:00 2001 From: Andrew Meadows <andrew@highfidelity.io> Date: Tue, 5 Feb 2019 08:57:18 -0800 Subject: [PATCH 14/18] add crash::doAssert() for debug purposes --- libraries/shared/src/CrashHelpers.cpp | 15 +++++++++++++++ libraries/shared/src/CrashHelpers.h | 1 + 2 files changed, 16 insertions(+) diff --git a/libraries/shared/src/CrashHelpers.cpp b/libraries/shared/src/CrashHelpers.cpp index f8ca90bc4c..1676318f3e 100644 --- a/libraries/shared/src/CrashHelpers.cpp +++ b/libraries/shared/src/CrashHelpers.cpp @@ -11,6 +11,16 @@ #include "CrashHelpers.h" +#ifdef NDEBUG +// undefine NDEBUG so doAssert() works for all builds +#undef NDEBUG +#include <assert.h> +#define NDEBUG +#else +#include <assert.h> +#endif + + namespace crash { class B; @@ -34,6 +44,11 @@ A::~A() { _b->virtualFunction(); } +// only use doAssert() for debug purposes +void doAssert(bool value) { + assert(value); +} + void pureVirtualCall() { qCDebug(shared) << "About to make a pure virtual call"; B b; diff --git a/libraries/shared/src/CrashHelpers.h b/libraries/shared/src/CrashHelpers.h index ad988c8906..247aea5cde 100644 --- a/libraries/shared/src/CrashHelpers.h +++ b/libraries/shared/src/CrashHelpers.h @@ -18,6 +18,7 @@ namespace crash { +void doAssert(bool value); // works for Release void pureVirtualCall(); void doubleFree(); void nullDeref(); From e50892b3d2b3b67292fabeda51443fe589a0b740 Mon Sep 17 00:00:00 2001 From: Andrew Meadows <andrew@highfidelity.io> Date: Tue, 5 Feb 2019 08:57:55 -0800 Subject: [PATCH 15/18] MyAvatar is unmovable until physics is enabled --- interface/src/avatar/MyAvatar.cpp | 60 +++++++++++++------------------ interface/src/avatar/MyAvatar.h | 1 + 2 files changed, 26 insertions(+), 35 deletions(-) diff --git a/interface/src/avatar/MyAvatar.cpp b/interface/src/avatar/MyAvatar.cpp index 92d9270d20..a0deb721e2 100755 --- a/interface/src/avatar/MyAvatar.cpp +++ b/interface/src/avatar/MyAvatar.cpp @@ -847,6 +847,7 @@ void MyAvatar::simulate(float deltaTime, bool inView) { updateOrientation(deltaTime); updatePosition(deltaTime); + updateViewBoom(); } // update sensorToWorldMatrix for camera and hand controllers @@ -3323,21 +3324,22 @@ void MyAvatar::updateActionMotor(float deltaTime) { direction = Vectors::ZERO; } + float sensorToWorldScale = getSensorToWorldScale(); if (state == CharacterController::State::Hover) { // we're flying --> complex acceleration curve that builds on top of current motor speed and caps at some max speed float motorSpeed = glm::length(_actionMotorVelocity); - float finalMaxMotorSpeed = getSensorToWorldScale() * DEFAULT_AVATAR_MAX_FLYING_SPEED * _walkSpeedScalar; + float finalMaxMotorSpeed = sensorToWorldScale * DEFAULT_AVATAR_MAX_FLYING_SPEED * _walkSpeedScalar; float speedGrowthTimescale = 2.0f; float speedIncreaseFactor = 1.8f * _walkSpeedScalar; motorSpeed *= 1.0f + glm::clamp(deltaTime / speedGrowthTimescale, 0.0f, 1.0f) * speedIncreaseFactor; - const float maxBoostSpeed = getSensorToWorldScale() * MAX_BOOST_SPEED; + const float maxBoostSpeed = sensorToWorldScale * MAX_BOOST_SPEED; if (_isPushing) { if (motorSpeed < maxBoostSpeed) { // an active action motor should never be slower than this float boostCoefficient = (maxBoostSpeed - motorSpeed) / maxBoostSpeed; - motorSpeed += getSensorToWorldScale() * MIN_AVATAR_SPEED * boostCoefficient; + motorSpeed += sensorToWorldScale * MIN_AVATAR_SPEED * boostCoefficient; } else if (motorSpeed > finalMaxMotorSpeed) { motorSpeed = finalMaxMotorSpeed; } @@ -3348,45 +3350,21 @@ void MyAvatar::updateActionMotor(float deltaTime) { const glm::vec2 currentVel = { direction.x, direction.z }; float scaledSpeed = scaleSpeedByDirection(currentVel, _walkSpeed.get(), _walkBackwardSpeed.get()); // _walkSpeedScalar is a multiplier if we are in sprint mode, otherwise 1.0 - _actionMotorVelocity = getSensorToWorldScale() * (scaledSpeed * _walkSpeedScalar) * direction; - } - - float previousBoomLength = _boomLength; - float boomChange = getDriveKey(ZOOM); - _boomLength += 2.0f * _boomLength * boomChange + boomChange * boomChange; - _boomLength = glm::clamp<float>(_boomLength, ZOOM_MIN, ZOOM_MAX); - - // May need to change view if boom length has changed - if (previousBoomLength != _boomLength) { - qApp->changeViewAsNeeded(_boomLength); + _actionMotorVelocity = sensorToWorldScale * (scaledSpeed * _walkSpeedScalar) * direction; } } void MyAvatar::updatePosition(float deltaTime) { - if (_motionBehaviors & AVATAR_MOTION_ACTION_MOTOR_ENABLED) { - updateActionMotor(deltaTime); - } - - vec3 velocity = getWorldVelocity(); - float sensorToWorldScale = getSensorToWorldScale(); - float sensorToWorldScale2 = sensorToWorldScale * sensorToWorldScale; - const float MOVING_SPEED_THRESHOLD_SQUARED = 0.0001f; // 0.01 m/s - if (!_characterController.isEnabledAndReady()) { - // _characterController is not in physics simulation but it can still compute its target velocity - updateMotors(); - _characterController.computeNewVelocity(deltaTime, velocity); - - float speed2 = glm::length(velocity); - if (speed2 > sensorToWorldScale2 * MIN_AVATAR_SPEED_SQUARED) { - // update position ourselves - applyPositionDelta(deltaTime * velocity); + if (_characterController.isEnabledAndReady()) { + if (_motionBehaviors & AVATAR_MOTION_ACTION_MOTOR_ENABLED) { + updateActionMotor(deltaTime); } - measureMotionDerivatives(deltaTime); - _moving = speed2 > sensorToWorldScale2 * MOVING_SPEED_THRESHOLD_SQUARED; - } else { + float sensorToWorldScale = getSensorToWorldScale(); + float sensorToWorldScale2 = sensorToWorldScale * sensorToWorldScale; + vec3 velocity = getWorldVelocity(); float speed2 = glm::length2(velocity); + const float MOVING_SPEED_THRESHOLD_SQUARED = 0.0001f; // 0.01 m/s _moving = speed2 > sensorToWorldScale2 * MOVING_SPEED_THRESHOLD_SQUARED; - if (_moving) { // scan for walkability glm::vec3 position = getWorldPosition(); @@ -3398,6 +3376,18 @@ void MyAvatar::updatePosition(float deltaTime) { } } +void MyAvatar::updateViewBoom() { + float previousBoomLength = _boomLength; + float boomChange = getDriveKey(ZOOM); + _boomLength += 2.0f * _boomLength * boomChange + boomChange * boomChange; + _boomLength = glm::clamp<float>(_boomLength, ZOOM_MIN, ZOOM_MAX); + + // May need to change view if boom length has changed + if (previousBoomLength != _boomLength) { + qApp->changeViewAsNeeded(_boomLength); + } +} + void MyAvatar::updateCollisionSound(const glm::vec3 &penetration, float deltaTime, float frequency) { // COLLISION SOUND API in Audio has been removed } diff --git a/interface/src/avatar/MyAvatar.h b/interface/src/avatar/MyAvatar.h index 0d27988543..c53eae65d4 100755 --- a/interface/src/avatar/MyAvatar.h +++ b/interface/src/avatar/MyAvatar.h @@ -1732,6 +1732,7 @@ private: void updateOrientation(float deltaTime); void updateActionMotor(float deltaTime); void updatePosition(float deltaTime); + void updateViewBoom(); void updateCollisionSound(const glm::vec3& penetration, float deltaTime, float frequency); void initHeadBones(); void initAnimGraph(); From 2b402609535e3b758df1ecd707ae9f162abd54d6 Mon Sep 17 00:00:00 2001 From: Roxanne Skelly <roxanne@highfidelity.io> Date: Tue, 5 Feb 2019 09:27:12 -0800 Subject: [PATCH 16/18] QmlMarketplace - disable HTML for quest --- .../hifi/commerce/marketplace/Marketplace.qml | 43 +++++++++++---- .../commerce/marketplace/MarketplaceItem.qml | 54 +++++++++++-------- interface/src/Application.cpp | 7 +++ .../PlatformInfoScriptingInterface.cpp | 8 +++ .../PlatformInfoScriptingInterface.h | 6 +++ 5 files changed, 85 insertions(+), 33 deletions(-) diff --git a/interface/resources/qml/hifi/commerce/marketplace/Marketplace.qml b/interface/resources/qml/hifi/commerce/marketplace/Marketplace.qml index a4cf260173..1db133c249 100644 --- a/interface/resources/qml/hifi/commerce/marketplace/Marketplace.qml +++ b/interface/resources/qml/hifi/commerce/marketplace/Marketplace.qml @@ -39,7 +39,8 @@ Rectangle { property bool keyboardRaised: false property string searchScopeString: "Featured" property bool isLoggedIn: false; - + property bool supports3DHTML: true; + anchors.fill: (typeof parent === undefined) ? undefined : parent function getMarketplaceItems() { @@ -60,6 +61,8 @@ Rectangle { Component.onCompleted: { Commerce.getLoginStatus(); + + supports3DHTML = PlatformInfo.has3DHTML(); } Component.onDestruction: { @@ -103,7 +106,7 @@ Rectangle { if (result.status !== 'success') { console.log("Failed to get Marketplace Item", result.data.message); } else { - + marketplaceItem.supports3DHTML = root.supports3DHTML; marketplaceItem.item_id = result.data.id; marketplaceItem.image_url = result.data.thumbnail_url; marketplaceItem.name = result.data.title; @@ -958,8 +961,16 @@ Rectangle { } onShowLicense: { - licenseInfoWebView.url = url; - licenseInfo.visible = true; + var xhr = new XMLHttpRequest; + xhr.open("GET", url); + xhr.onreadystatechange = function() { + if (xhr.readyState == XMLHttpRequest.DONE) { + console.log(xhr.responseText); + licenseText.text = xhr.responseText; + licenseInfo.visible = true; + } + }; + xhr.send(); } onCategoryClicked: { root.categoryString = category; @@ -1001,11 +1012,13 @@ Rectangle { leftMargin: 15 } + + Item { id: footerText anchors.fill: parent - visible: itemsList.visible + visible: root.supports3DHTML && itemsList.visible HiFiGlyphs { id: footerGlyph @@ -1072,6 +1085,8 @@ Rectangle { rightMargin: 10 } + visible: root.supports3DHTML + text: "SEE ALL MARKETS" width: 180 @@ -1100,16 +1115,24 @@ Rectangle { visible: false; - HifiControlsUit.WebView { - id: licenseInfoWebView - + ScrollView { anchors { bottomMargin: 1 topMargin: 60 - leftMargin: 1 - rightMargin: 1 + leftMargin: 15 fill: parent } + + RalewayRegular { + id: licenseText + + width:440 + wrapMode: Text.Wrap + + text: "" + size: 18; + color: hifi.colors.baseGray + } } Item { diff --git a/interface/resources/qml/hifi/commerce/marketplace/MarketplaceItem.qml b/interface/resources/qml/hifi/commerce/marketplace/MarketplaceItem.qml index b7e9a711d2..0c6d661609 100644 --- a/interface/resources/qml/hifi/commerce/marketplace/MarketplaceItem.qml +++ b/interface/resources/qml/hifi/commerce/marketplace/MarketplaceItem.qml @@ -43,6 +43,8 @@ Rectangle { property string created_at: "" property bool isLoggedIn: false; property int edition: -1; + property bool supports3DHTML: false; + onCategoriesChanged: { categoriesListModel.clear(); @@ -52,8 +54,13 @@ Rectangle { } onDescriptionChanged: { - descriptionTextModel.clear(); - descriptionTextModel.append({text: description}) + + if(root.supports3DHTML) { + descriptionTextModel.clear(); + descriptionTextModel.append({text: description}); + } else { + descriptionText.text = description; + } } signal buy() @@ -410,22 +417,22 @@ Rectangle { if (root.license === "No Rights Reserved (CC0)") { url = "https://creativecommons.org/publicdomain/zero/1.0/" } else if (root.license === "Attribution (CC BY)") { - url = "https://creativecommons.org/licenses/by/4.0/" + url = "https://creativecommons.org/licenses/by/4.0/legalcode.txt" } else if (root.license === "Attribution-ShareAlike (CC BY-SA)") { - url = "https://creativecommons.org/licenses/by-sa/4.0/" + url = "https://creativecommons.org/licenses/by-sa/4.0/legalcode.txt" } else if (root.license === "Attribution-NoDerivs (CC BY-ND)") { - url = "https://creativecommons.org/licenses/by-nd/4.0/" + url = "https://creativecommons.org/licenses/by-nd/4.0/legalcode.txt" } else if (root.license === "Attribution-NonCommercial (CC BY-NC)") { - url = "https://creativecommons.org/licenses/by-nc/4.0/" + url = "https://creativecommons.org/licenses/by-nc/4.0/legalcode.txt" } else if (root.license === "Attribution-NonCommercial-ShareAlike (CC BY-NC-SA)") { - url = "https://creativecommons.org/licenses/by-nc-sa/4.0/" + url = "https://creativecommons.org/licenses/by-nc-sa/4.0/legalcode.txt" } else if (root.license === "Attribution-NonCommercial-NoDerivs (CC BY-NC-ND)") { - url = "https://creativecommons.org/licenses/by-nc-nd/4.0/" + url = "https://creativecommons.org/licenses/by-nc-nd/4.0/legalcode.txt" } else if (root.license === "Proof of Provenance License (PoP License)") { url = "https://digitalassetregistry.com/PoP-License/v1/" } if(url) { - licenseInfoWebView.url = url; + showLicense(url) } } } @@ -460,20 +467,21 @@ Rectangle { verticalAlignment: Text.AlignVCenter } - //RalewaySemiBold { - // id: descriptionText - // - // anchors.top: descriptionLabel.bottom - // anchors.left: parent.left - // anchors.topMargin: 5 - // width: parent.width - // - // text: root.description - // size: 14 - // color: hifi.colors.lightGray - // verticalAlignment: Text.AlignVCenter - // wrapMode: Text.Wrap - //} + RalewaySemiBold { + id: descriptionText + + anchors.top: descriptionLabel.bottom + anchors.left: parent.left + anchors.topMargin: 5 + width: parent.width + + visible: !root.supports3DHTML + + text: root.description + size: 14 + color: hifi.colors.lightGray + wrapMode: Text.Wrap + } ListModel { diff --git a/interface/src/Application.cpp b/interface/src/Application.cpp index 39ab3a8b1c..48c8f35ede 100644 --- a/interface/src/Application.cpp +++ b/interface/src/Application.cpp @@ -3000,6 +3000,13 @@ void Application::initializeUi() { QUrl{ "hifi/commerce/marketplace/Marketplace.qml" }, }, marketplaceCallback); + QmlContextCallback platformInfoCallback = [](QQmlContext* context) { + context->setContextProperty("PlatformInfo", new PlatformInfoScriptingInterface()); + }; + OffscreenQmlSurface::addWhitelistContextHandler({ + QUrl{ "hifi/commerce/marketplace/Marketplace.qml" }, + }, platformInfoCallback); + QmlContextCallback ttsCallback = [](QQmlContext* context) { context->setContextProperty("TextToSpeech", DependencyManager::get<TTSScriptingInterface>().data()); }; diff --git a/interface/src/scripting/PlatformInfoScriptingInterface.cpp b/interface/src/scripting/PlatformInfoScriptingInterface.cpp index b6e4df0d40..b390ab7119 100644 --- a/interface/src/scripting/PlatformInfoScriptingInterface.cpp +++ b/interface/src/scripting/PlatformInfoScriptingInterface.cpp @@ -133,3 +133,11 @@ bool PlatformInfoScriptingInterface::hasRiftControllers() { bool PlatformInfoScriptingInterface::hasViveControllers() { return qApp->hasViveControllers(); } + +bool PlatformInfoScriptingInterface::has3DHTML() { +#if defined(Q_OS_ANDROID) + return false; +#else + return true; +#endif +} diff --git a/interface/src/scripting/PlatformInfoScriptingInterface.h b/interface/src/scripting/PlatformInfoScriptingInterface.h index 3ed57965c9..aece09b008 100644 --- a/interface/src/scripting/PlatformInfoScriptingInterface.h +++ b/interface/src/scripting/PlatformInfoScriptingInterface.h @@ -65,6 +65,12 @@ public slots: * @function Window.hasRift * @returns {boolean} <code>true</code> if running on Windows, otherwise <code>false</code>.*/ bool hasViveControllers(); + + /**jsdoc + * Returns true if device supports 3d HTML + * @function Window.hasRift + * @returns {boolean} <code>true</code> if device supports 3d HTML, otherwise <code>false</code>.*/ + bool has3DHTML(); }; #endif // hifi_PlatformInfoScriptingInterface_h From cbd83f972c312107bca27bb1d7ee6d7f07c5a60b Mon Sep 17 00:00:00 2001 From: Andrew Meadows <andrew@highfidelity.io> Date: Tue, 5 Feb 2019 10:17:10 -0800 Subject: [PATCH 17/18] remove unsued variable --- interface/src/avatar/MyAvatar.cpp | 1 - 1 file changed, 1 deletion(-) diff --git a/interface/src/avatar/MyAvatar.cpp b/interface/src/avatar/MyAvatar.cpp index a0deb721e2..098d7943c7 100755 --- a/interface/src/avatar/MyAvatar.cpp +++ b/interface/src/avatar/MyAvatar.cpp @@ -75,7 +75,6 @@ const float PITCH_SPEED_DEFAULT = 75.0f; // degrees/sec const float MAX_BOOST_SPEED = 0.5f * DEFAULT_AVATAR_MAX_WALKING_SPEED; // action motor gets additive boost below this speed const float MIN_AVATAR_SPEED = 0.05f; -const float MIN_AVATAR_SPEED_SQUARED = MIN_AVATAR_SPEED * MIN_AVATAR_SPEED; // speed is set to zero below this float MIN_SCRIPTED_MOTOR_TIMESCALE = 0.005f; float DEFAULT_SCRIPTED_MOTOR_TIMESCALE = 1.0e6f; From 16ef30ced0fb7bc9ec0619b1e6649e2d4d701dc8 Mon Sep 17 00:00:00 2001 From: Andrew Meadows <andrew@highfidelity.io> Date: Tue, 5 Feb 2019 17:14:25 -0800 Subject: [PATCH 18/18] don't flushRepeatedMessages() in LogHandler dtor --- libraries/shared/src/LogHandler.cpp | 1 - 1 file changed, 1 deletion(-) diff --git a/libraries/shared/src/LogHandler.cpp b/libraries/shared/src/LogHandler.cpp index 65651373be..c51d9bf611 100644 --- a/libraries/shared/src/LogHandler.cpp +++ b/libraries/shared/src/LogHandler.cpp @@ -38,7 +38,6 @@ LogHandler::LogHandler() { } LogHandler::~LogHandler() { - flushRepeatedMessages(); } const char* stringForLogType(LogMsgType msgType) {