mirror of
https://github.com/overte-org/overte.git
synced 2025-08-06 20:58:38 +02:00
QML Marketplace support
Support QML UI for the Marketplace as some devices do not handle web on 3d surfaces. Checkpoint code
This commit is contained in:
parent
0ab13f1e48
commit
8010d86210
6 changed files with 565 additions and 104 deletions
|
@ -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;
|
||||||
|
}
|
||||||
|
}
|
|
@ -232,6 +232,7 @@
|
||||||
#include "commerce/Ledger.h"
|
#include "commerce/Ledger.h"
|
||||||
#include "commerce/Wallet.h"
|
#include "commerce/Wallet.h"
|
||||||
#include "commerce/QmlCommerce.h"
|
#include "commerce/QmlCommerce.h"
|
||||||
|
#include "commerce/QmlMarketplace.h"
|
||||||
#include "ResourceRequestObserver.h"
|
#include "ResourceRequestObserver.h"
|
||||||
|
|
||||||
#include "webbrowser/WebBrowserSuggestionsEngine.h"
|
#include "webbrowser/WebBrowserSuggestionsEngine.h"
|
||||||
|
@ -2913,6 +2914,14 @@ void Application::initializeUi() {
|
||||||
QUrl{ "hifi/dialogs/security/SecurityImageModel.qml" },
|
QUrl{ "hifi/dialogs/security/SecurityImageModel.qml" },
|
||||||
QUrl{ "hifi/dialogs/security/SecurityImageSelection.qml" },
|
QUrl{ "hifi/dialogs/security/SecurityImageSelection.qml" },
|
||||||
}, commerceCallback);
|
}, commerceCallback);
|
||||||
|
|
||||||
|
QmlContextCallback marketplaceCallback = [](QQmlContext* context) {
|
||||||
|
context->setContextProperty("Marketplace", new QmlMarketplace());
|
||||||
|
};
|
||||||
|
OffscreenQmlSurface::addWhitelistContextHandler({
|
||||||
|
QUrl{ "hifi/commerce/marketplace/Marketplace.qml" },
|
||||||
|
}, marketplaceCallback);
|
||||||
|
|
||||||
QmlContextCallback ttsCallback = [](QQmlContext* context) {
|
QmlContextCallback ttsCallback = [](QQmlContext* context) {
|
||||||
context->setContextProperty("TextToSpeech", DependencyManager::get<TTSScriptingInterface>().data());
|
context->setContextProperty("TextToSpeech", DependencyManager::get<TTSScriptingInterface>().data());
|
||||||
};
|
};
|
||||||
|
|
131
interface/src/commerce/QmlMarketplace.cpp
Normal file
131
interface/src/commerce/QmlMarketplace.cpp
Normal file
|
@ -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();
|
||||||
|
}
|
||||||
|
}
|
68
interface/src/commerce/QmlMarketplace.h
Normal file
68
interface/src/commerce/QmlMarketplace.h
Normal file
|
@ -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
|
|
@ -1,8 +1,8 @@
|
||||||
//
|
//
|
||||||
// marketplace.js
|
// marketplace.js
|
||||||
//
|
//
|
||||||
// Created by Eric Levin on 8 Jan 2016
|
// Created by Roxanne Skelly on 1/18/2019
|
||||||
// Copyright 2016 High Fidelity, Inc.
|
// Copyright 2019 High Fidelity, Inc.
|
||||||
//
|
//
|
||||||
// Distributed under the Apache License, Version 2.0.
|
// Distributed under the Apache License, Version 2.0.
|
||||||
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
|
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
|
||||||
|
@ -10,108 +10,27 @@
|
||||||
|
|
||||||
(function() { // BEGIN LOCAL_SCOPE
|
(function() { // BEGIN LOCAL_SCOPE
|
||||||
|
|
||||||
/* global WebTablet */
|
var AppUi = Script.require('appUi');
|
||||||
Script.include("../libraries/WebTablet.js");
|
|
||||||
|
|
||||||
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";
|
ui = new AppUi({
|
||||||
var marketplaceWindow = new OverlayWebWindow({
|
buttonName: BUTTON_NAME,
|
||||||
title: "Marketplace",
|
sortOrder: 10,
|
||||||
source: "about:blank",
|
home: MARKETPLACE_QML_SOURCE
|
||||||
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);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function showMarketplace(marketplaceID) {
|
function shutdown() {
|
||||||
var url = MARKETPLACE_URL;
|
|
||||||
if (marketplaceID) {
|
|
||||||
url = url + "/items/" + marketplaceID;
|
|
||||||
}
|
|
||||||
tablet.gotoWebScreen(url);
|
|
||||||
marketplaceVisible = true;
|
|
||||||
UserActivityLogger.openedMarketplace();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function hideTablet(tablet) {
|
//
|
||||||
if (!tablet) {
|
// Run the functions.
|
||||||
return;
|
//
|
||||||
}
|
startup();
|
||||||
updateButtonState(false);
|
Script.scriptEnding.connect(shutdown);
|
||||||
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);
|
|
||||||
});
|
|
||||||
|
|
||||||
}()); // END LOCAL_SCOPE
|
}()); // END LOCAL_SCOPE
|
||||||
|
|
|
@ -769,16 +769,14 @@ var onTabletScreenChanged = function onTabletScreenChanged(type, url) {
|
||||||
|
|
||||||
|
|
||||||
var BUTTON_NAME = "MARKET";
|
var BUTTON_NAME = "MARKET";
|
||||||
var MARKETPLACE_URL = METAVERSE_SERVER_URL + "/marketplace";
|
var MARKETPLACE_QML_SOURCE = "hifi/commerce/marketplace/Marketplace.qml";
|
||||||
// Append "?" if necessary to signal injected script that it's the initial page.
|
|
||||||
var MARKETPLACE_URL_INITIAL = MARKETPLACE_URL + (MARKETPLACE_URL.indexOf("?") > -1 ? "" : "?");
|
|
||||||
var ui;
|
var ui;
|
||||||
function startup() {
|
function startup() {
|
||||||
ui = new AppUi({
|
ui = new AppUi({
|
||||||
buttonName: BUTTON_NAME,
|
buttonName: BUTTON_NAME,
|
||||||
sortOrder: 9,
|
sortOrder: 9,
|
||||||
inject: MARKETPLACES_INJECT_SCRIPT_URL,
|
home: MARKETPLACE_QML_SOURCE,
|
||||||
home: MARKETPLACE_URL_INITIAL,
|
|
||||||
onScreenChanged: onTabletScreenChanged,
|
onScreenChanged: onTabletScreenChanged,
|
||||||
onMessage: onQmlMessageReceived
|
onMessage: onQmlMessageReceived
|
||||||
});
|
});
|
||||||
|
|
Loading…
Reference in a new issue