diff --git a/interface/resources/qml/hifi/commerce/Checkout.qml b/interface/resources/qml/hifi/commerce/Checkout.qml index 865bb72921..b9d15b61e4 100644 --- a/interface/resources/qml/hifi/commerce/Checkout.qml +++ b/interface/resources/qml/hifi/commerce/Checkout.qml @@ -66,6 +66,14 @@ Rectangle { } } } + onSecurityImageResult: { + securityImage.source = securityImageSelection.getImagePathFromImageID(imageID); + } + } + + SecurityImageSelection { + id: securityImageSelection; + referrerURL: checkoutRoot.itemHref; } // @@ -80,6 +88,20 @@ Rectangle { anchors.left: parent.left; anchors.top: parent.top; + // Security Image + Image { + id: securityImage; + // Anchors + anchors.top: parent.top; + anchors.left: parent.left; + anchors.leftMargin: 16; + height: parent.height - 5; + width: height; + anchors.verticalCenter: parent.verticalCenter; + fillMode: Image.PreserveAspectFit; + mipmap: true; + } + // Title Bar text RalewaySemiBold { id: titleBarText; @@ -87,8 +109,11 @@ Rectangle { // Text size size: hifi.fontSizes.overlayTitle; // Anchors - anchors.fill: parent; + anchors.top: parent.top; + anchors.left: securityImage.right; anchors.leftMargin: 16; + anchors.bottom: parent.bottom; + width: paintedWidth; // Style color: hifi.colors.lightGrayText; // Alignment @@ -381,7 +406,7 @@ Rectangle { // "Buy" button HifiControlsUit.Button { id: buyButton; - enabled: balanceAfterPurchase >= 0 && !alreadyOwned && inventoryReceived && balanceReceived; + enabled: balanceAfterPurchase >= 0 && inventoryReceived && balanceReceived; color: hifi.buttons.black; colorScheme: hifi.colorSchemes.dark; anchors.top: parent.top; @@ -391,9 +416,16 @@ Rectangle { anchors.right: parent.right; anchors.rightMargin: 20; width: parent.width/2 - anchors.rightMargin*2; - text: (inventoryReceived && balanceReceived) ? (alreadyOwned ? "Already Owned" : "Buy") : "--"; + text: (inventoryReceived && balanceReceived) ? (alreadyOwned ? "Already Owned: Get Item" : "Buy") : "--"; onClicked: { - commerce.buy(itemId, parseInt(itemPriceText.text)); + if (!alreadyOwned) { + commerce.buy(itemId, parseInt(itemPriceText.text)); + } else { + if (urlHandler.canHandleUrl(itemHref)) { + urlHandler.handleUrl(itemHref); + } + sendToScript({method: 'checkout_buySuccess', itemId: itemId}); + } } } } @@ -427,6 +459,7 @@ Rectangle { itemHref = message.params.itemHref; commerce.balance(); commerce.inventory(); + commerce.getSecurityImage(); break; default: console.log('Unrecognized message from marketplaces.js:', JSON.stringify(message)); diff --git a/interface/resources/qml/hifi/commerce/Inventory.qml b/interface/resources/qml/hifi/commerce/Inventory.qml index 298abebdab..d7ffae7c3c 100644 --- a/interface/resources/qml/hifi/commerce/Inventory.qml +++ b/interface/resources/qml/hifi/commerce/Inventory.qml @@ -43,6 +43,14 @@ Rectangle { inventoryContentsList.model = inventory.assets; } } + onSecurityImageResult: { + securityImage.source = securityImageSelection.getImagePathFromImageID(imageID); + } + } + + SecurityImageSelection { + id: securityImageSelection; + referrerURL: inventoryRoot.referrerURL; } // @@ -51,12 +59,26 @@ Rectangle { Item { id: titleBarContainer; // Size - width: inventoryRoot.width; + width: parent.width; height: 50; // Anchors anchors.left: parent.left; anchors.top: parent.top; + // Security Image + Image { + id: securityImage; + // Anchors + anchors.top: parent.top; + anchors.left: parent.left; + anchors.leftMargin: 16; + height: parent.height - 5; + width: height; + anchors.verticalCenter: parent.verticalCenter; + fillMode: Image.PreserveAspectFit; + mipmap: true; + } + // Title Bar text RalewaySemiBold { id: titleBarText; @@ -64,8 +86,11 @@ Rectangle { // Text size size: hifi.fontSizes.overlayTitle; // Anchors - anchors.fill: parent; + anchors.top: parent.top; + anchors.left: securityImage.right; anchors.leftMargin: 16; + anchors.bottom: parent.bottom; + width: paintedWidth; // Style color: hifi.colors.lightGrayText; // Alignment @@ -73,6 +98,25 @@ Rectangle { verticalAlignment: Text.AlignVCenter; } + // "Change Security Image" button + HifiControlsUit.Button { + id: changeSecurityImageButton; + color: hifi.buttons.black; + colorScheme: hifi.colorSchemes.dark; + anchors.top: parent.top; + anchors.topMargin: 3; + anchors.bottom: parent.bottom; + anchors.bottomMargin: 3; + anchors.right: parent.right; + anchors.rightMargin: 20; + width: 200; + text: "Change Security Image" + onClicked: { + securityImageSelection.isManuallyChangingSecurityImage = true; + securityImageSelection.visible = true; + } + } + // Separator HifiControlsUit.Separator { anchors.left: parent.left; @@ -166,6 +210,7 @@ Rectangle { } ListView { id: inventoryContentsList; + clip: true; // Anchors anchors.top: inventoryContentsLabel.bottom; anchors.topMargin: 8; @@ -262,6 +307,7 @@ Rectangle { referrerURL = message.referrerURL; commerce.balance(); commerce.inventory(); + commerce.getSecurityImage(); break; default: console.log('Unrecognized message from marketplaces.js:', JSON.stringify(message)); diff --git a/interface/resources/qml/hifi/commerce/SecurityImageModel.qml b/interface/resources/qml/hifi/commerce/SecurityImageModel.qml new file mode 100644 index 0000000000..2fbf28683f --- /dev/null +++ b/interface/resources/qml/hifi/commerce/SecurityImageModel.qml @@ -0,0 +1,42 @@ +// +// SecurityImageModel.qml +// qml/hifi/commerce +// +// SecurityImageModel +// +// Created by Zach Fox on 2017-08-15 +// Copyright 2017 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 QtQuick 2.5 + +ListModel { + id: root; + ListElement{ + sourcePath: "images/01cat.jpg" + securityImageEnumValue: 1; + } + ListElement{ + sourcePath: "images/02car.jpg" + securityImageEnumValue: 2; + } + ListElement{ + sourcePath: "images/03dog.jpg" + securityImageEnumValue: 3; + } + ListElement{ + sourcePath: "images/04stars.jpg" + securityImageEnumValue: 4; + } + ListElement{ + sourcePath: "images/05plane.jpg" + securityImageEnumValue: 5; + } + ListElement{ + sourcePath: "images/06gingerbread.jpg" + securityImageEnumValue: 6; + } +} diff --git a/interface/resources/qml/hifi/commerce/SecurityImageSelection.qml b/interface/resources/qml/hifi/commerce/SecurityImageSelection.qml new file mode 100644 index 0000000000..7775f1ff9c --- /dev/null +++ b/interface/resources/qml/hifi/commerce/SecurityImageSelection.qml @@ -0,0 +1,271 @@ +// +// SecurityImageSelection.qml +// qml/hifi/commerce +// +// SecurityImageSelection +// +// Created by Zach Fox on 2017-08-15 +// Copyright 2017 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.5 +import QtQuick.Controls 1.4 +import "../../styles-uit" +import "../../controls-uit" as HifiControlsUit +import "../../controls" as HifiControls + +// references XXX from root context + +Rectangle { + HifiConstants { id: hifi; } + + id: securityImageSelectionRoot; + property string referrerURL: ""; + property bool isManuallyChangingSecurityImage: false; + anchors.fill: parent; + // Style + color: hifi.colors.baseGray; + z:999; // On top of everything else + visible: false; + + Hifi.QmlCommerce { + id: commerce; + onSecurityImageResult: { + if (!isManuallyChangingSecurityImage) { + securityImageSelectionRoot.visible = (imageID == 0); + } + if (imageID > 0) { + for (var itr = 0; itr < gridModel.count; itr++) { + var thisValue = gridModel.get(itr).securityImageEnumValue; + if (thisValue === imageID) { + securityImageGrid.currentIndex = itr; + break; + } + } + } + } + } + + Component.onCompleted: { + commerce.getSecurityImage(); + } + + // + // TITLE BAR START + // + Item { + id: titleBarContainer; + // Size + width: securityImageSelectionRoot.width; + height: 30; + // Anchors + anchors.left: parent.left; + anchors.top: parent.top; + + // Title Bar text + RalewaySemiBold { + id: titleBarText; + text: "Select a Security Image"; + // Text size + size: hifi.fontSizes.overlayTitle; + // Anchors + anchors.fill: parent; + anchors.leftMargin: 16; + // Style + color: hifi.colors.lightGrayText; + // Alignment + horizontalAlignment: Text.AlignHLeft; + verticalAlignment: Text.AlignVCenter; + } + + // Separator + HifiControlsUit.Separator { + anchors.left: parent.left; + anchors.right: parent.right; + anchors.bottom: parent.bottom; + } + } + // + // TITLE BAR END + // + + // + // EXPLANATION START + // + Item { + id: explanationContainer; + // Size + width: securityImageSelectionRoot.width; + height: 85; + // Anchors + anchors.top: titleBarContainer.bottom; + anchors.left: parent.left; + anchors.right: parent.right; + + RalewayRegular { + id: explanationText; + text: "This image will be displayed on secure Inventory and Marketplace Checkout dialogs.
If you don't see your selected image on these dialogs, do not use them!
"; + // Text size + size: 16; + // Anchors + anchors.top: parent.top; + anchors.topMargin: 4; + anchors.left: parent.left; + anchors.leftMargin: 16; + anchors.right: parent.right; + anchors.rightMargin: 16; + // Style + color: hifi.colors.lightGrayText; + wrapMode: Text.WordWrap; + // Alignment + horizontalAlignment: Text.AlignHLeft; + verticalAlignment: Text.AlignVCenter; + } + + // Separator + HifiControlsUit.Separator { + anchors.left: parent.left; + anchors.right: parent.right; + anchors.bottom: parent.bottom; + } + } + // + // EXPLANATION END + // + + // + // SECURITY IMAGE GRID START + // + Item { + id: securityImageGridContainer; + // Anchors + anchors.left: parent.left; + anchors.leftMargin: 8; + anchors.right: parent.right; + anchors.rightMargin: 8; + anchors.top: explanationContainer.bottom; + anchors.topMargin: 8; + anchors.bottom: actionButtonsContainer.top; + anchors.bottomMargin: 8; + + SecurityImageModel { + id: gridModel; + } + + GridView { + id: securityImageGrid; + clip: true; + // Anchors + anchors.fill: parent; + currentIndex: -1; + cellWidth: width / 2; + cellHeight: height / 3; + model: gridModel; + delegate: Item { + width: securityImageGrid.cellWidth; + height: securityImageGrid.cellHeight; + Item { + anchors.fill: parent; + Image { + width: parent.width - 8; + height: parent.height - 8; + source: sourcePath; + anchors.horizontalCenter: parent.horizontalCenter; + anchors.verticalCenter: parent.verticalCenter; + fillMode: Image.PreserveAspectFit; + mipmap: true; + } + } + MouseArea { + anchors.fill: parent; + onClicked: { + securityImageGrid.currentIndex = index; + } + } + } + highlight: Rectangle { + width: securityImageGrid.cellWidth; + height: securityImageGrid.cellHeight; + color: hifi.colors.blueHighlight; + } + } + } + // + // SECURITY IMAGE GRID END + // + + + // + // ACTION BUTTONS START + // + Item { + id: actionButtonsContainer; + // Size + width: securityImageSelectionRoot.width; + height: 40; + // Anchors + anchors.left: parent.left; + anchors.bottom: parent.bottom; + anchors.bottomMargin: 8; + + // "Cancel" button + HifiControlsUit.Button { + id: cancelButton; + color: hifi.buttons.black; + colorScheme: hifi.colorSchemes.dark; + anchors.top: parent.top; + anchors.topMargin: 3; + anchors.bottom: parent.bottom; + anchors.bottomMargin: 3; + anchors.left: parent.left; + anchors.leftMargin: 20; + width: parent.width/2 - anchors.leftMargin*2; + text: "Cancel" + onClicked: { + if (!securityImageSelectionRoot.isManuallyChangingSecurityImage) { + sendToScript({method: 'securityImageSelection_cancelClicked', referrerURL: securityImageSelectionRoot.referrerURL}); + } else { + securityImageSelectionRoot.visible = false; + } + } + } + + // "Confirm" button + HifiControlsUit.Button { + id: confirmButton; + color: hifi.buttons.black; + colorScheme: hifi.colorSchemes.dark; + anchors.top: parent.top; + anchors.topMargin: 3; + anchors.bottom: parent.bottom; + anchors.bottomMargin: 3; + anchors.right: parent.right; + anchors.rightMargin: 20; + width: parent.width/2 - anchors.rightMargin*2; + text: "Confirm"; + onClicked: { + securityImageSelectionRoot.isManuallyChangingSecurityImage = false; + commerce.chooseSecurityImage(gridModel.get(securityImageGrid.currentIndex).securityImageEnumValue); + } + } + } + // + // ACTION BUTTONS END + // + + // + // FUNCTION DEFINITIONS START + // + signal sendToScript(var message); + + function getImagePathFromImageID(imageID) { + return (imageID ? gridModel.get(imageID - 1).sourcePath : ""); + } + // + // FUNCTION DEFINITIONS END + // +} diff --git a/interface/resources/qml/hifi/commerce/images/01cat.jpg b/interface/resources/qml/hifi/commerce/images/01cat.jpg new file mode 100644 index 0000000000..6e7897cb82 Binary files /dev/null and b/interface/resources/qml/hifi/commerce/images/01cat.jpg differ diff --git a/interface/resources/qml/hifi/commerce/images/02car.jpg b/interface/resources/qml/hifi/commerce/images/02car.jpg new file mode 100644 index 0000000000..5dd8091e57 Binary files /dev/null and b/interface/resources/qml/hifi/commerce/images/02car.jpg differ diff --git a/interface/resources/qml/hifi/commerce/images/03dog.jpg b/interface/resources/qml/hifi/commerce/images/03dog.jpg new file mode 100644 index 0000000000..4a85b80c0c Binary files /dev/null and b/interface/resources/qml/hifi/commerce/images/03dog.jpg differ diff --git a/interface/resources/qml/hifi/commerce/images/04stars.jpg b/interface/resources/qml/hifi/commerce/images/04stars.jpg new file mode 100644 index 0000000000..8f2bf62f83 Binary files /dev/null and b/interface/resources/qml/hifi/commerce/images/04stars.jpg differ diff --git a/interface/resources/qml/hifi/commerce/images/05plane.jpg b/interface/resources/qml/hifi/commerce/images/05plane.jpg new file mode 100644 index 0000000000..6504459d8b Binary files /dev/null and b/interface/resources/qml/hifi/commerce/images/05plane.jpg differ diff --git a/interface/resources/qml/hifi/commerce/images/06gingerbread.jpg b/interface/resources/qml/hifi/commerce/images/06gingerbread.jpg new file mode 100644 index 0000000000..54c37faa2f Binary files /dev/null and b/interface/resources/qml/hifi/commerce/images/06gingerbread.jpg differ diff --git a/interface/src/commerce/QmlCommerce.cpp b/interface/src/commerce/QmlCommerce.cpp index 63bfca4f31..573740727f 100644 --- a/interface/src/commerce/QmlCommerce.cpp +++ b/interface/src/commerce/QmlCommerce.cpp @@ -19,9 +19,11 @@ HIFI_QML_DEF(QmlCommerce) QmlCommerce::QmlCommerce(QQuickItem* parent) : OffscreenQmlDialog(parent) { auto ledger = DependencyManager::get(); + auto wallet = DependencyManager::get(); connect(ledger.data(), &Ledger::buyResult, this, &QmlCommerce::buyResult); connect(ledger.data(), &Ledger::balanceResult, this, &QmlCommerce::balanceResult); connect(ledger.data(), &Ledger::inventoryResult, this, &QmlCommerce::inventoryResult); + connect(wallet.data(), &Wallet::securityImageResult, this, &QmlCommerce::securityImageResult); } void QmlCommerce::buy(const QString& assetId, int cost, const QString& buyerUsername) { @@ -48,4 +50,13 @@ void QmlCommerce::inventory() { auto ledger = DependencyManager::get(); auto wallet = DependencyManager::get(); ledger->inventory(wallet->listPublicKeys()); - } \ No newline at end of file +} + +void QmlCommerce::chooseSecurityImage(uint imageID) { + auto wallet = DependencyManager::get(); + wallet->chooseSecurityImage(imageID); +} +void QmlCommerce::getSecurityImage() { + auto wallet = DependencyManager::get(); + wallet->getSecurityImage(); +} \ No newline at end of file diff --git a/interface/src/commerce/QmlCommerce.h b/interface/src/commerce/QmlCommerce.h index 0b1d232fd7..5b702bfeff 100644 --- a/interface/src/commerce/QmlCommerce.h +++ b/interface/src/commerce/QmlCommerce.h @@ -30,11 +30,14 @@ signals: // because we can't scalably know of out-of-band changes (e.g., another machine interacting with the block chain). void balanceResult(int balance, const QString& failureMessage); void inventoryResult(QJsonObject inventory, const QString& failureMessage); + void securityImageResult(uint imageID); protected: Q_INVOKABLE void buy(const QString& assetId, int cost, const QString& buyerUsername = ""); Q_INVOKABLE void balance(); Q_INVOKABLE void inventory(); + Q_INVOKABLE void chooseSecurityImage(uint imageID); + Q_INVOKABLE void getSecurityImage(); }; #endif // hifi_QmlCommerce_h diff --git a/interface/src/commerce/Wallet.cpp b/interface/src/commerce/Wallet.cpp index 34d89b54b0..9edbeddd6e 100644 --- a/interface/src/commerce/Wallet.cpp +++ b/interface/src/commerce/Wallet.cpp @@ -43,4 +43,12 @@ QStringList Wallet::listPublicKeys() { QString Wallet::signWithKey(const QString& text, const QString& key) { qCInfo(commerce) << "Signing text."; return "fixme signed"; -} \ No newline at end of file +} + +void Wallet::chooseSecurityImage(uint imageID) { + _chosenSecurityImage = (SecurityImage)imageID; + emit securityImageResult(imageID); +} +void Wallet::getSecurityImage() { + emit securityImageResult(_chosenSecurityImage); +} diff --git a/interface/src/commerce/Wallet.h b/interface/src/commerce/Wallet.h index 79de5e81da..86638b5c75 100644 --- a/interface/src/commerce/Wallet.h +++ b/interface/src/commerce/Wallet.h @@ -26,9 +26,29 @@ public: bool generateKeyPair(); QStringList listPublicKeys(); QString signWithKey(const QString& text, const QString& key); + void chooseSecurityImage(uint imageID); + void getSecurityImage(); + +signals: + void securityImageResult(uint imageID); + +protected: + // ALWAYS add SecurityImage enum values to the END of the enum. + // They must be in the same order as the images are listed in + // SecurityImageSelection.qml + enum SecurityImage { + NONE = 0, + Cat, + Car, + Dog, + Stars, + Plane, + Gingerbread + }; private: QStringList _publicKeys{}; + SecurityImage _chosenSecurityImage = SecurityImage::NONE; }; #endif // hifi_Wallet_h diff --git a/scripts/system/marketplaces/marketplaces.js b/scripts/system/marketplaces/marketplaces.js index 9378a1d95b..84d7d44689 100644 --- a/scripts/system/marketplaces/marketplaces.js +++ b/scripts/system/marketplaces/marketplaces.js @@ -21,6 +21,7 @@ var MARKETPLACES_INJECT_SCRIPT_URL = Script.resolvePath("../html/js/marketplacesInject.js"); var MARKETPLACE_CHECKOUT_QML_PATH = Script.resourcesPath() + "qml/hifi/commerce/Checkout.qml"; var MARKETPLACE_INVENTORY_QML_PATH = Script.resourcesPath() + "qml/hifi/commerce/Inventory.qml"; + var MARKETPLACE_SECURITY_QML_PATH = Script.resourcesPath() + "qml/hifi/commerce/SecurityImageSelection.qml"; var HOME_BUTTON_TEXTURE = "http://hifi-content.s3.amazonaws.com/alan/dev/tablet-with-home-button.fbx/tablet-with-home-button.fbm/button-root.png"; // var HOME_BUTTON_TEXTURE = Script.resourcesPath() + "meshes/tablet-with-home-button.fbx/tablet-with-home-button.fbm/button-root.png"; @@ -87,7 +88,7 @@ function onScreenChanged(type, url) { onMarketplaceScreen = type === "Web" && url === MARKETPLACE_URL_INITIAL; - wireEventBridge(type === "QML" && (url === MARKETPLACE_CHECKOUT_QML_PATH || url === MARKETPLACE_INVENTORY_QML_PATH)); + wireEventBridge(type === "QML" && (url === MARKETPLACE_CHECKOUT_QML_PATH || url === MARKETPLACE_INVENTORY_QML_PATH || url === MARKETPLACE_SECURITY_QML_PATH)); // for toolbar mode: change button to active when window is first openend, false otherwise. marketplaceButton.editProperties({ isActive: onMarketplaceScreen }); if (type === "Web" && url.indexOf(MARKETPLACE_URL) !== -1) { @@ -217,8 +218,11 @@ case 'inventory_backClicked': tablet.gotoWebScreen(message.referrerURL, MARKETPLACES_INJECT_SCRIPT_URL); break; + case 'securityImageSelection_cancelClicked': + tablet.gotoWebScreen(message.referrerURL, MARKETPLACES_INJECT_SCRIPT_URL); + break; default: - print('Unrecognized message from Checkout.qml or Inventory.qml: ' + JSON.stringify(message)); + print('Unrecognized message from Checkout.qml, Inventory.qml, or SecurityImageSelection.qml: ' + JSON.stringify(message)); } }