diff --git a/interface/resources/qml/desktop/Desktop.qml b/interface/resources/qml/desktop/Desktop.qml index ca7226a6ab..579b4e7fd6 100644 --- a/interface/resources/qml/desktop/Desktop.qml +++ b/interface/resources/qml/desktop/Desktop.qml @@ -50,7 +50,22 @@ FocusScope { property bool desktopRoot: true // The VR version of the primary menu - property var rootMenu: Menu { objectName: "rootMenu" } + property var rootMenu: Menu { + objectName: "rootMenu" + + // for some reasons it is not possible to use just '({})' here as it gets empty when passed to TableRoot/DesktopRoot + property var exclusionGroupsByMenuItem : ListModel {} + + function addExclusionGroup(menuItem, exclusionGroup) + { + exclusionGroupsByMenuItem.append( + { + 'menuItem' : menuItem.toString(), + 'exclusionGroup' : exclusionGroup.toString() + } + ); + } + } // FIXME: Alpha gradients display as fuschia under QtQuick 2.5 on OSX/AMD // because shaders are 4.2, and do not include #version declarations. diff --git a/interface/resources/qml/hifi/commerce/Checkout.qml b/interface/resources/qml/hifi/commerce/Checkout.qml deleted file mode 100644 index d9b0072917..0000000000 --- a/interface/resources/qml/hifi/commerce/Checkout.qml +++ /dev/null @@ -1,463 +0,0 @@ -// -// Checkout.qml -// qml/hifi/commerce -// -// Checkout -// -// Created by Zach Fox on 2017-08-07 -// 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 -import "./wallet" as HifiWallet - -// references XXX from root context - -Rectangle { - HifiConstants { id: hifi; } - - id: checkoutRoot; - property bool inventoryReceived: false; - property bool balanceReceived: false; - property string itemId: ""; - property string itemHref: ""; - property int balanceAfterPurchase: 0; - property bool alreadyOwned: false; - property int itemPriceFull: 0; - // Style - color: hifi.colors.baseGray; - Hifi.QmlCommerce { - id: commerce; - onBuyResult: { - if (result.status !== 'success') { - buyButton.text = result.message; - buyButton.enabled = false; - } else { - if (urlHandler.canHandleUrl(itemHref)) { - urlHandler.handleUrl(itemHref); - } - sendToScript({method: 'checkout_buySuccess', itemId: itemId}); - } - } - onBalanceResult: { - if (result.status !== 'success') { - console.log("Failed to get balance", result.message); - } else { - balanceReceived = true; - hfcBalanceText.text = parseFloat(result.data.balance/100).toFixed(2); - balanceAfterPurchase = parseFloat(result.data.balance/100) - parseFloat(checkoutRoot.itemPriceFull/100).toFixed(2); - } - } - onInventoryResult: { - if (result.status !== 'success') { - console.log("Failed to get inventory", result.message); - } else { - inventoryReceived = true; - console.log('inventory fixme', JSON.stringify(result)); - if (inventoryContains(result.data.assets, itemId)) { - alreadyOwned = true; - } else { - alreadyOwned = false; - } - } - } - } - - // - // TITLE BAR START - // - Item { - id: titleBarContainer; - // Size - width: checkoutRoot.width; - height: 50; - // Anchors - anchors.left: parent.left; - anchors.top: parent.top; - - // Title Bar text - RalewaySemiBold { - id: titleBarText; - text: "Checkout"; - // Text size - size: hifi.fontSizes.overlayTitle; - // Anchors - anchors.top: parent.top; - anchors.left: parent.left; - anchors.leftMargin: 16; - anchors.bottom: parent.bottom; - width: paintedWidth; - // 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 - // - - // - // ITEM DESCRIPTION START - // - Item { - id: itemDescriptionContainer; - // Size - width: checkoutRoot.width; - height: childrenRect.height + 20; - // Anchors - anchors.left: parent.left; - anchors.top: titleBarContainer.bottom; - - // Item Name text - Item { - id: itemNameContainer; - // Anchors - anchors.top: parent.top; - anchors.topMargin: 4; - anchors.left: parent.left; - anchors.leftMargin: 16; - anchors.right: parent.right; - anchors.rightMargin: 16; - height: childrenRect.height; - - RalewaySemiBold { - id: itemNameTextLabel; - text: "Item Name:"; - // Anchors - anchors.top: parent.top; - anchors.left: parent.left; - width: paintedWidth; - // Text size - size: 16; - // Style - color: hifi.colors.lightGrayText; - // Alignment - horizontalAlignment: Text.AlignHLeft; - verticalAlignment: Text.AlignVCenter; - } - RalewayRegular { - id: itemNameText; - // Text size - size: itemNameTextLabel.size; - // Anchors - anchors.top: parent.top; - anchors.left: itemNameTextLabel.right; - anchors.leftMargin: 16; - width: paintedWidth; - // Style - color: hifi.colors.lightGrayText; - // Alignment - horizontalAlignment: Text.AlignHLeft; - verticalAlignment: Text.AlignVCenter; - } - } - - - // Item Author text - Item { - id: itemAuthorContainer; - // Anchors - anchors.top: itemNameContainer.bottom; - anchors.topMargin: 4; - anchors.left: parent.left; - anchors.leftMargin: 16; - anchors.right: parent.right; - anchors.rightMargin: 16; - height: childrenRect.height; - - RalewaySemiBold { - id: itemAuthorTextLabel; - text: "Item Author:"; - // Anchors - anchors.top: parent.top; - anchors.left: parent.left; - width: paintedWidth; - // Text size - size: 16; - // Style - color: hifi.colors.lightGrayText; - // Alignment - horizontalAlignment: Text.AlignHLeft; - verticalAlignment: Text.AlignVCenter; - } - RalewayRegular { - id: itemAuthorText; - // Text size - size: itemAuthorTextLabel.size; - // Anchors - anchors.top: parent.top; - anchors.left: itemAuthorTextLabel.right; - anchors.leftMargin: 16; - width: paintedWidth; - // Style - color: hifi.colors.lightGrayText; - // Alignment - horizontalAlignment: Text.AlignHLeft; - verticalAlignment: Text.AlignVCenter; - } - } - - // HFC Balance text - Item { - id: hfcBalanceContainer; - // Anchors - anchors.top: itemAuthorContainer.bottom; - anchors.topMargin: 16; - anchors.left: parent.left; - anchors.leftMargin: 16; - anchors.right: parent.right; - anchors.rightMargin: 16; - height: childrenRect.height; - - RalewaySemiBold { - id: hfcBalanceTextLabel; - text: "HFC Balance:"; - // Anchors - anchors.top: parent.top; - anchors.left: parent.left; - width: paintedWidth; - // Text size - size: 20; - // Style - color: hifi.colors.lightGrayText; - // Alignment - horizontalAlignment: Text.AlignHLeft; - verticalAlignment: Text.AlignVCenter; - } - RalewayRegular { - id: hfcBalanceText; - text: "--"; - // Text size - size: hfcBalanceTextLabel.size; - // Anchors - anchors.top: parent.top; - anchors.left: hfcBalanceTextLabel.right; - anchors.leftMargin: 16; - width: paintedWidth; - // Style - color: hifi.colors.lightGrayText; - // Alignment - horizontalAlignment: Text.AlignHLeft; - verticalAlignment: Text.AlignVCenter; - } - } - - // Item Price text - Item { - id: itemPriceContainer; - // Anchors - anchors.top: hfcBalanceContainer.bottom; - anchors.topMargin: 4; - anchors.left: parent.left; - anchors.leftMargin: 16; - anchors.right: parent.right; - anchors.rightMargin: 16; - height: childrenRect.height; - - RalewaySemiBold { - id: itemPriceTextLabel; - text: "Item Price:"; - // Anchors - anchors.top: parent.top; - anchors.left: parent.left; - width: paintedWidth; - // Text size - size: 20; - // Style - color: hifi.colors.lightGrayText; - // Alignment - horizontalAlignment: Text.AlignHLeft; - verticalAlignment: Text.AlignVCenter; - } - RalewayRegular { - id: itemPriceText; - // Text size - size: itemPriceTextLabel.size; - // Anchors - anchors.top: parent.top; - anchors.left: itemPriceTextLabel.right; - anchors.leftMargin: 16; - width: paintedWidth; - // Style - color: hifi.colors.lightGrayText; - // Alignment - horizontalAlignment: Text.AlignHLeft; - verticalAlignment: Text.AlignVCenter; - } - } - - // HFC "Balance After Purchase" text - Item { - id: hfcBalanceAfterPurchaseContainer; - // Anchors - anchors.top: itemPriceContainer.bottom; - anchors.topMargin: 4; - anchors.left: parent.left; - anchors.leftMargin: 16; - anchors.right: parent.right; - anchors.rightMargin: 16; - height: childrenRect.height; - - RalewaySemiBold { - id: hfcBalanceAfterPurchaseTextLabel; - text: "HFC Balance After Purchase:"; - // Anchors - anchors.top: parent.top; - anchors.left: parent.left; - width: paintedWidth; - // Text size - size: 20; - // Style - color: hifi.colors.lightGrayText; - // Alignment - horizontalAlignment: Text.AlignHLeft; - verticalAlignment: Text.AlignVCenter; - } - RalewayRegular { - id: hfcBalanceAfterPurchaseText; - text: balanceAfterPurchase; - // Text size - size: hfcBalanceAfterPurchaseTextLabel.size; - // Anchors - anchors.top: parent.top; - anchors.left: hfcBalanceAfterPurchaseTextLabel.right; - anchors.leftMargin: 16; - width: paintedWidth; - // Style - color: (balanceAfterPurchase >= 0) ? hifi.colors.lightGrayText : hifi.colors.redHighlight; - // Alignment - horizontalAlignment: Text.AlignHLeft; - verticalAlignment: Text.AlignVCenter; - } - } - } - // - // ITEM DESCRIPTION END - // - - - // - // ACTION BUTTONS START - // - Item { - id: actionButtonsContainer; - // Size - width: checkoutRoot.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: { - sendToScript({method: 'checkout_cancelClicked', params: itemId}); - } - } - - // "Buy" button - HifiControlsUit.Button { - id: buyButton; - enabled: balanceAfterPurchase >= 0 && inventoryReceived && balanceReceived; - 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: (inventoryReceived && balanceReceived) ? (alreadyOwned ? "Already Owned: Get Item" : "Buy") : "--"; - onClicked: { - if (!alreadyOwned) { - commerce.buy(itemId, parseFloat(itemPriceText.text*100)); - } else { - if (urlHandler.canHandleUrl(itemHref)) { - urlHandler.handleUrl(itemHref); - } - sendToScript({method: 'checkout_buySuccess', itemId: itemId}); - } - } - } - } - // - // ACTION BUTTONS END - // - - // - // FUNCTION DEFINITIONS START - // - // - // 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) { - switch (message.method) { - case 'updateCheckoutQML': - itemId = message.params.itemId; - itemNameText.text = message.params.itemName; - itemAuthorText.text = message.params.itemAuthor; - checkoutRoot.itemPriceFull = message.params.itemPrice; - itemPriceText.text = parseFloat(checkoutRoot.itemPriceFull/100).toFixed(2); - itemHref = message.params.itemHref; - commerce.balance(); - commerce.inventory(); - break; - default: - console.log('Unrecognized message from marketplaces.js:', JSON.stringify(message)); - } - } - signal sendToScript(var message); - - function inventoryContains(inventoryJson, id) { - for (var idx = 0; idx < inventoryJson.length; idx++) { - if(inventoryJson[idx].id === id) { - return true; - } - } - return false; - } - - // - // FUNCTION DEFINITIONS END - // -} diff --git a/interface/resources/qml/hifi/commerce/Inventory.qml b/interface/resources/qml/hifi/commerce/Inventory.qml deleted file mode 100644 index 20458f9f16..0000000000 --- a/interface/resources/qml/hifi/commerce/Inventory.qml +++ /dev/null @@ -1,280 +0,0 @@ -// -// Inventory.qml -// qml/hifi/commerce -// -// Inventory -// -// Created by Zach Fox on 2017-08-10 -// 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 -import "./wallet" as HifiWallet - -// references XXX from root context - -Rectangle { - HifiConstants { id: hifi; } - - id: inventoryRoot; - property string referrerURL: ""; - // Style - color: hifi.colors.baseGray; - Hifi.QmlCommerce { - id: commerce; - onBalanceResult: { - if (result.status !== 'success') { - console.log("Failed to get balance", result.message); - } else { - hfcBalanceText.text = parseFloat(result.data.balance/100).toFixed(2); - } - } - onInventoryResult: { - if (result.status !== 'success') { - console.log("Failed to get inventory", result.message); - } else { - inventoryContentsList.model = result.data.assets; - } - } - } - - // - // TITLE BAR START - // - Item { - id: titleBarContainer; - // Size - width: parent.width; - height: 50; - // Anchors - anchors.left: parent.left; - anchors.top: parent.top; - - // Title Bar text - RalewaySemiBold { - id: titleBarText; - text: "Inventory"; - // Text size - size: hifi.fontSizes.overlayTitle; - // Anchors - anchors.top: parent.top; - anchors.left: parent.left; - anchors.leftMargin: 16; - anchors.bottom: parent.bottom; - width: paintedWidth; - // 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 - // - - // - // HFC BALANCE START - // - Item { - id: hfcBalanceContainer; - // Size - width: inventoryRoot.width; - height: childrenRect.height + 20; - // Anchors - anchors.left: parent.left; - anchors.leftMargin: 16; - anchors.top: titleBarContainer.bottom; - anchors.topMargin: 4; - - RalewaySemiBold { - id: hfcBalanceTextLabel; - text: "HFC Balance:"; - // Anchors - anchors.top: parent.top; - anchors.left: parent.left; - width: paintedWidth; - // Text size - size: 20; - // Style - color: hifi.colors.lightGrayText; - // Alignment - horizontalAlignment: Text.AlignHLeft; - verticalAlignment: Text.AlignVCenter; - } - RalewayRegular { - id: hfcBalanceText; - text: "--"; - // Text size - size: hfcBalanceTextLabel.size; - // Anchors - anchors.top: parent.top; - anchors.left: hfcBalanceTextLabel.right; - anchors.leftMargin: 16; - width: paintedWidth; - // Style - color: hifi.colors.lightGrayText; - // Alignment - horizontalAlignment: Text.AlignHLeft; - verticalAlignment: Text.AlignVCenter; - } - } - // - // HFC BALANCE END - // - - // - // INVENTORY CONTENTS START - // - Item { - id: inventoryContentsContainer; - // Anchors - anchors.left: parent.left; - anchors.leftMargin: 16; - anchors.right: parent.right; - anchors.rightMargin: 16; - anchors.top: hfcBalanceContainer.bottom; - anchors.topMargin: 8; - anchors.bottom: actionButtonsContainer.top; - anchors.bottomMargin: 8; - - RalewaySemiBold { - id: inventoryContentsLabel; - text: "Inventory:"; - // Anchors - anchors.top: parent.top; - anchors.left: parent.left; - width: paintedWidth; - // Text size - size: 24; - // Style - color: hifi.colors.lightGrayText; - // Alignment - horizontalAlignment: Text.AlignHLeft; - verticalAlignment: Text.AlignVCenter; - } - ListView { - id: inventoryContentsList; - clip: true; - // Anchors - anchors.top: inventoryContentsLabel.bottom; - anchors.topMargin: 8; - anchors.left: parent.left; - anchors.bottom: parent.bottom; - width: parent.width; - delegate: Item { - width: parent.width; - height: 30; - RalewayRegular { - id: thisItemId; - // Text size - size: 20; - // Style - color: hifi.colors.blueAccent; - text: modelData.title; - // Alignment - horizontalAlignment: Text.AlignHLeft; - } - MouseArea { - anchors.fill: parent; - hoverEnabled: enabled; - onClicked: { - sendToScript({method: 'inventory_itemClicked', itemId: modelData.id}); - } - onEntered: { - thisItemId.color = hifi.colors.blueHighlight; - } - onExited: { - thisItemId.color = hifi.colors.blueAccent; - } - } - } - } - } - // - // INVENTORY CONTENTS END - // - - // - // ACTION BUTTONS START - // - Item { - id: actionButtonsContainer; - // Size - width: inventoryRoot.width; - height: 40; - // Anchors - anchors.left: parent.left; - anchors.bottom: parent.bottom; - anchors.bottomMargin: 8; - - // "Back" button - HifiControlsUit.Button { - id: backButton; - 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: "Back" - onClicked: { - sendToScript({method: 'inventory_backClicked', referrerURL: referrerURL}); - } - } - } - // - // ACTION BUTTONS END - // - - // - // FUNCTION DEFINITIONS START - // - // - // 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) { - switch (message.method) { - case 'updateInventory': - referrerURL = message.referrerURL; - commerce.balance(); - commerce.inventory(); - break; - default: - console.log('Unrecognized message from marketplaces.js:', JSON.stringify(message)); - } - } - signal sendToScript(var message); - - // - // FUNCTION DEFINITIONS END - // -} diff --git a/interface/resources/qml/hifi/commerce/checkout/Checkout.qml b/interface/resources/qml/hifi/commerce/checkout/Checkout.qml new file mode 100644 index 0000000000..109e357206 --- /dev/null +++ b/interface/resources/qml/hifi/commerce/checkout/Checkout.qml @@ -0,0 +1,944 @@ +// +// Checkout.qml +// qml/hifi/commerce/checkout +// +// Checkout +// +// Created by Zach Fox on 2017-08-25 +// 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 +import "../wallet" as HifiWallet + +// references XXX from root context + +Rectangle { + HifiConstants { id: hifi; } + + id: root; + property string activeView: "initialize"; + property bool purchasesReceived: false; + property bool balanceReceived: false; + property bool securityImageResultReceived: false; + property bool keyFilePathIfExistsResultReceived: false; + property string itemId: ""; + property string itemHref: ""; + property double balanceAfterPurchase: 0; + property bool alreadyOwned: false; + property int itemPriceFull: 0; + property bool itemIsJson: true; + // Style + color: hifi.colors.baseGray; + Hifi.QmlCommerce { + id: commerce; + + onLoginStatusResult: { + if (!isLoggedIn && root.activeView !== "needsLogIn") { + root.activeView = "needsLogIn"; + } else if (isLoggedIn) { + root.activeView = "initialize"; + commerce.getSecurityImage(); + commerce.getKeyFilePathIfExists(); + commerce.balance(); + commerce.inventory(); + } + } + + onSecurityImageResult: { + securityImageResultReceived = true; + if (!exists && root.activeView !== "notSetUp") { // "If security image is not set up" + root.activeView = "notSetUp"; + } else if (root.securityImageResultReceived && exists && root.keyFilePathIfExistsResultReceived && root.activeView === "initialize") { + root.activeView = "checkoutMain"; + } else if (exists) { + // just set the source again (to be sure the change was noticed) + securityImage.source = ""; + securityImage.source = "image://security/securityImage"; + } + } + + onKeyFilePathIfExistsResult: { + keyFilePathIfExistsResultReceived = true; + if (path === "" && root.activeView !== "notSetUp") { + root.activeView = "notSetUp"; + } else if (root.securityImageResultReceived && root.keyFilePathIfExistsResultReceived && path !== "" && root.activeView === "initialize") { + root.activeView = "checkoutMain"; + } + } + + onBuyResult: { + if (result.status !== 'success') { + failureErrorText.text = "Here's some more info about the error:

" + (result.message); + root.activeView = "checkoutFailure"; + } else { + root.activeView = "checkoutSuccess"; + } + } + + onBalanceResult: { + if (result.status !== 'success') { + console.log("Failed to get balance", result.data.message); + } else { + root.balanceReceived = true; + hfcBalanceText.text = (parseFloat(result.data.balance/100).toFixed(2)) + " HFC"; + balanceAfterPurchase = parseFloat(result.data.balance/100) - root.itemPriceFull/100; + root.setBuyText(); + } + } + + onInventoryResult: { + if (result.status !== 'success') { + console.log("Failed to get purchases", result.data.message); + } else { + root.purchasesReceived = true; + if (purchasesContains(result.data.assets, itemId)) { + root.alreadyOwned = true; + } else { + root.alreadyOwned = false; + } + root.setBuyText(); + } + } + } + + HifiWallet.SecurityImageModel { + id: securityImageModel; + } + + // + // TITLE BAR START + // + Item { + id: titleBarContainer; + visible: !needsLogIn.visible; + // Size + width: parent.width; + height: 50; + // Anchors + anchors.left: parent.left; + anchors.top: parent.top; + + // Title Bar text + RalewaySemiBold { + id: titleBarText; + text: "MARKETPLACE"; + // Text size + size: hifi.fontSizes.overlayTitle; + // Anchors + anchors.top: parent.top; + anchors.left: parent.left; + anchors.leftMargin: 16; + anchors.bottom: parent.bottom; + width: paintedWidth; + // Style + color: hifi.colors.faintGray; + // Alignment + horizontalAlignment: Text.AlignHLeft; + verticalAlignment: Text.AlignVCenter; + } + + // Security Image (TEMPORARY!) + Image { + id: securityImage; + // Anchors + anchors.top: parent.top; + anchors.right: parent.right; + anchors.verticalCenter: parent.verticalCenter; + height: parent.height - 10; + width: height; + fillMode: Image.PreserveAspectFit; + mipmap: true; + cache: false; + source: "image://security/securityImage"; + } + Image { + id: securityImageOverlay; + source: "../wallet/images/lockOverlay.png"; + width: securityImage.width * 0.45; + height: securityImage.height * 0.45; + anchors.bottom: securityImage.bottom; + anchors.right: securityImage.right; + mipmap: true; + opacity: 0.9; + } + + // Separator + HifiControlsUit.Separator { + anchors.left: parent.left; + anchors.right: parent.right; + anchors.bottom: parent.bottom; + } + } + // + // TITLE BAR END + // + + Rectangle { + id: initialize; + visible: root.activeView === "initialize"; + anchors.top: titleBarContainer.bottom; + anchors.bottom: parent.top; + anchors.left: parent.left; + anchors.right: parent.right; + color: hifi.colors.baseGray; + + Component.onCompleted: { + securityImageResultReceived = false; + purchasesReceived = false; + balanceReceived = false; + keyFilePathIfExistsResultReceived = false; + commerce.getLoginStatus(); + } + } + + HifiWallet.NeedsLogIn { + id: needsLogIn; + visible: root.activeView === "needsLogIn"; + anchors.top: parent.top; + anchors.bottom: parent.bottom; + anchors.left: parent.left; + anchors.right: parent.right; + + Connections { + onSendSignalToWallet: { + sendToScript(msg); + } + } + } + Connections { + target: GlobalServices + onMyUsernameChanged: { + commerce.getLoginStatus(); + } + } + + + + // + // "WALLET NOT SET UP" START + // + Item { + id: notSetUp; + visible: root.activeView === "notSetUp"; + anchors.top: titleBarContainer.bottom; + anchors.bottom: parent.bottom; + anchors.left: parent.left; + anchors.right: parent.right; + + RalewayRegular { + id: notSetUpText; + text: "Your wallet isn't set up.

Set up your Wallet (no credit card necessary) to claim your free HFC " + + "and get items from the Marketplace."; + // Text size + size: 24; + // Anchors + anchors.top: parent.top; + anchors.bottom: notSetUpActionButtonsContainer.top; + anchors.left: parent.left; + anchors.leftMargin: 16; + anchors.right: parent.right; + anchors.rightMargin: 16; + // Style + color: hifi.colors.faintGray; + wrapMode: Text.WordWrap; + // Alignment + horizontalAlignment: Text.AlignHCenter; + verticalAlignment: Text.AlignVCenter; + } + + Item { + id: notSetUpActionButtonsContainer; + // Size + width: root.width; + height: 70; + // Anchors + anchors.left: parent.left; + anchors.bottom: parent.bottom; + anchors.bottomMargin: 24; + + // "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: { + sendToScript({method: 'checkout_cancelClicked', params: itemId}); + } + } + + // "Set Up" button + HifiControlsUit.Button { + id: setUpButton; + color: hifi.buttons.blue; + 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: "Set Up Wallet" + onClicked: { + sendToScript({method: 'checkout_setUpClicked'}); + } + } + } + } + // + // "WALLET NOT SET UP" END + // + + // + // CHECKOUT CONTENTS START + // + Item { + id: checkoutContents; + visible: root.activeView === "checkoutMain"; + anchors.top: titleBarContainer.bottom; + anchors.bottom: parent.bottom; + anchors.left: parent.left; + anchors.right: parent.right; + + // + // ITEM DESCRIPTION START + // + Item { + id: itemDescriptionContainer; + // Anchors + anchors.left: parent.left; + anchors.leftMargin: 32; + anchors.right: parent.right; + anchors.rightMargin: 32; + anchors.top: parent.top; + anchors.bottom: checkoutActionButtonsContainer.top; + + // HFC Balance text + Item { + id: hfcBalanceContainer; + // Anchors + anchors.top: parent.top; + anchors.topMargin: 30; + anchors.left: parent.left; + anchors.leftMargin: 16; + anchors.right: parent.right; + anchors.rightMargin: 16; + height: childrenRect.height; + + RalewaySemiBold { + id: hfcBalanceTextLabel; + text: "Balance:"; + // Anchors + anchors.top: parent.top; + anchors.left: parent.left; + width: paintedWidth; + height: paintedHeight; + // Text size + size: 30; + // Style + color: hifi.colors.lightGrayText; + // Alignment + horizontalAlignment: Text.AlignLeft; + verticalAlignment: Text.AlignVCenter; + } + RalewayRegular { + id: hfcBalanceText; + text: "-- HFC"; + // Text size + size: hfcBalanceTextLabel.size; + // Anchors + anchors.top: parent.top; + anchors.left: hfcBalanceTextLabel.right; + anchors.leftMargin: 16; + anchors.right: parent.right; + anchors.rightMargin: 16; + height: paintedHeight; + // Style + color: hifi.colors.lightGrayText; + // Alignment + horizontalAlignment: Text.AlignRight; + verticalAlignment: Text.AlignVCenter; + } + } + + // Item Name text + Item { + id: itemNameContainer; + // Anchors + anchors.top: hfcBalanceContainer.bottom; + anchors.topMargin: 32; + anchors.left: parent.left; + anchors.leftMargin: 16; + anchors.right: parent.right; + anchors.rightMargin: 16; + height: childrenRect.height; + + RalewaySemiBold { + id: itemNameTextLabel; + text: "Item:"; + // Anchors + anchors.top: parent.top; + anchors.left: parent.left; + width: paintedWidth; + height: paintedHeight; + // Text size + size: 20; + // Style + color: hifi.colors.lightGrayText; + // Alignment + horizontalAlignment: Text.AlignLeft; + verticalAlignment: Text.AlignVCenter; + } + RalewayRegular { + id: itemNameText; + // Text size + size: itemNameTextLabel.size; + // Anchors + anchors.top: parent.top; + anchors.left: itemNameTextLabel.right; + anchors.leftMargin: 16; + anchors.right: parent.right; + anchors.rightMargin: 16; + height: paintedHeight; + // Style + color: hifi.colors.lightGrayText; + elide: Text.ElideRight; + // Alignment + horizontalAlignment: Text.AlignRight; + verticalAlignment: Text.AlignVCenter; + } + } + + + // Item Author text + Item { + id: itemAuthorContainer; + // Anchors + anchors.top: itemNameContainer.bottom; + anchors.topMargin: 4; + anchors.left: parent.left; + anchors.leftMargin: 16; + anchors.right: parent.right; + anchors.rightMargin: 16; + height: childrenRect.height; + + RalewaySemiBold { + id: itemAuthorTextLabel; + text: "Author:"; + // Anchors + anchors.top: parent.top; + anchors.left: parent.left; + width: paintedWidth; + height: paintedHeight; + // Text size + size: 20; + // Style + color: hifi.colors.lightGrayText; + // Alignment + horizontalAlignment: Text.AlignLeft; + verticalAlignment: Text.AlignVCenter; + } + RalewayRegular { + id: itemAuthorText; + // Text size + size: itemAuthorTextLabel.size; + // Anchors + anchors.top: parent.top; + anchors.left: itemAuthorTextLabel.right; + anchors.leftMargin: 16; + anchors.right: parent.right; + anchors.rightMargin: 16; + height: paintedHeight; + // Style + color: hifi.colors.lightGrayText; + elide: Text.ElideRight; + // Alignment + horizontalAlignment: Text.AlignRight; + verticalAlignment: Text.AlignVCenter; + } + } + + // "Item Price" container + Item { + id: itemPriceContainer; + // Anchors + anchors.top: itemAuthorContainer.bottom; + anchors.topMargin: 32; + anchors.left: parent.left; + anchors.leftMargin: 16; + anchors.right: parent.right; + anchors.rightMargin: 16; + height: childrenRect.height; + + RalewaySemiBold { + id: itemPriceTextLabel; + text: "Item Price:"; + // Anchors + anchors.top: parent.top; + anchors.left: parent.left; + width: paintedWidth; + height: paintedHeight; + // Text size + size: 30; + // Style + color: hifi.colors.lightGrayText; + // Alignment + horizontalAlignment: Text.AlignLeft; + verticalAlignment: Text.AlignVCenter; + } + RalewayRegular { + id: itemPriceText; + text: "-- HFC"; + // Text size + size: itemPriceTextLabel.size; + // Anchors + anchors.top: parent.top; + anchors.left: itemPriceTextLabel.right; + anchors.leftMargin: 16; + anchors.right: parent.right; + anchors.rightMargin: 16; + height: paintedHeight; + // Style + color: (balanceAfterPurchase >= 0) ? hifi.colors.lightGrayText : hifi.colors.redHighlight; + // Alignment + horizontalAlignment: Text.AlignRight; + verticalAlignment: Text.AlignVCenter; + } + } + + // "Balance After Purchase" container + Item { + id: balanceAfterPurchaseContainer; + // Anchors + anchors.top: itemPriceContainer.bottom; + anchors.topMargin: 16; + anchors.left: parent.left; + anchors.leftMargin: 16; + anchors.right: parent.right; + anchors.rightMargin: 16; + height: childrenRect.height; + + RalewaySemiBold { + id: balanceAfterPurchaseTextLabel; + text: "Balance After Purchase:"; + // Anchors + anchors.top: parent.top; + anchors.left: parent.left; + width: paintedWidth; + height: paintedHeight; + // Text size + size: 20; + // Style + color: hifi.colors.lightGrayText; + // Alignment + horizontalAlignment: Text.AlignLeft; + verticalAlignment: Text.AlignVCenter; + } + RalewayRegular { + id: balanceAfterPurchaseText; + text: balanceAfterPurchase.toFixed(2) + " HFC"; + // Text size + size: balanceAfterPurchaseTextLabel.size; + // Anchors + anchors.top: parent.top; + anchors.left: balanceAfterPurchaseTextLabel.right; + anchors.leftMargin: 16; + anchors.right: parent.right; + anchors.rightMargin: 16; + height: paintedHeight; + // Style + color: (balanceAfterPurchase >= 0) ? hifi.colors.lightGrayText : hifi.colors.redHighlight; + // Alignment + horizontalAlignment: Text.AlignRight; + verticalAlignment: Text.AlignVCenter; + } + } + } + // + // ITEM DESCRIPTION END + // + + + // + // ACTION BUTTONS AND TEXT START + // + Item { + id: checkoutActionButtonsContainer; + // Size + width: root.width; + height: 200; + // Anchors + anchors.left: parent.left; + anchors.bottom: parent.bottom; + anchors.bottomMargin: 8; + + // "Cancel" button + HifiControlsUit.Button { + id: cancelPurchaseButton; + color: hifi.buttons.black; + colorScheme: hifi.colorSchemes.dark; + anchors.top: parent.top; + anchors.topMargin: 3; + height: 40; + anchors.left: parent.left; + anchors.leftMargin: 20; + width: parent.width/2 - anchors.leftMargin*2; + text: "Cancel" + onClicked: { + sendToScript({method: 'checkout_cancelClicked', params: itemId}); + } + } + + // "Buy" button + HifiControlsUit.Button { + id: buyButton; + enabled: (balanceAfterPurchase >= 0 && purchasesReceived && balanceReceived) || !itemIsJson; + color: hifi.buttons.blue; + colorScheme: hifi.colorSchemes.dark; + anchors.top: parent.top; + anchors.topMargin: 3; + height: 40; + anchors.right: parent.right; + anchors.rightMargin: 20; + width: parent.width/2 - anchors.rightMargin*2; + text: (itemIsJson ? ((purchasesReceived && balanceReceived) ? (root.alreadyOwned ? "Buy Another" : "Buy"): "--") : "Get Item"); + onClicked: { + if (itemIsJson) { + buyButton.enabled = false; + commerce.buy(itemId, itemPriceFull); + } else { + if (urlHandler.canHandleUrl(itemHref)) { + urlHandler.handleUrl(itemHref); + } + } + } + } + + // "Purchases" button + HifiControlsUit.Button { + id: goToPurchasesButton; + color: hifi.buttons.black; + colorScheme: hifi.colorSchemes.dark; + anchors.top: buyButton.bottom; + anchors.topMargin: 20; + anchors.bottomMargin: 7; + height: 40; + anchors.left: parent.left; + anchors.leftMargin: 20; + width: parent.width - anchors.leftMargin*2; + text: "View Purchases" + onClicked: { + sendToScript({method: 'checkout_goToPurchases'}); + } + } + + RalewayRegular { + id: buyText; + // Text size + size: 20; + // Anchors + anchors.bottom: parent.bottom; + anchors.bottomMargin: 10; + height: paintedHeight; + anchors.left: parent.left; + anchors.leftMargin: 10; + anchors.right: parent.right; + anchors.rightMargin: 10; + // Style + color: hifi.colors.faintGray; + wrapMode: Text.WordWrap; + // Alignment + horizontalAlignment: Text.AlignHCenter; + verticalAlignment: Text.AlignVCenter; + } + } + // + // ACTION BUTTONS END + // + } + // + // CHECKOUT CONTENTS END + // + + // + // CHECKOUT SUCCESS START + // + Item { + id: checkoutSuccess; + visible: root.activeView === "checkoutSuccess"; + anchors.top: titleBarContainer.bottom; + anchors.bottom: root.bottom; + anchors.left: parent.left; + anchors.right: parent.right; + + RalewayRegular { + id: completeText; + text: "Purchase Complete!

You bought " + (itemNameText.text) + " by " + (itemAuthorText.text) + ""; + // Text size + size: 24; + // Anchors + anchors.top: parent.top; + anchors.topMargin: 40; + height: paintedHeight; + anchors.left: parent.left; + anchors.right: parent.right; + // Style + color: hifi.colors.faintGray; + wrapMode: Text.WordWrap; + // Alignment + horizontalAlignment: Text.AlignHCenter; + verticalAlignment: Text.AlignVCenter; + } + + Item { + id: checkoutSuccessActionButtonsContainer; + // Size + width: root.width; + height: 70; + // Anchors + anchors.top: completeText.bottom; + anchors.topMargin: 30; + anchors.left: parent.left; + anchors.right: parent.right; + + // "Purchases" button + HifiControlsUit.Button { + id: purchasesButton; + 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: "View Purchases"; + onClicked: { + sendToScript({method: 'checkout_goToPurchases'}); + } + } + + // "Rez Now!" button + HifiControlsUit.Button { + id: rezNowButton; + color: hifi.buttons.blue; + 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: "Rez Now!" + onClicked: { + if (urlHandler.canHandleUrl(itemHref)) { + urlHandler.handleUrl(itemHref); + } + } + } + } + + Item { + id: continueShoppingButtonContainer; + // Size + width: root.width; + height: 70; + // Anchors + anchors.left: parent.left; + anchors.bottom: parent.bottom; + anchors.bottomMargin: 8; + // "Continue Shopping" button + HifiControlsUit.Button { + id: continueShoppingButton; + 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.rightMargin*2; + text: "Continue Shopping"; + onClicked: { + sendToScript({method: 'checkout_continueShopping', itemId: itemId}); + } + } + } + } + // + // CHECKOUT SUCCESS END + // + + // + // CHECKOUT FAILURE START + // + Item { + id: checkoutFailure; + visible: root.activeView === "checkoutFailure"; + anchors.top: titleBarContainer.bottom; + anchors.bottom: root.bottom; + anchors.left: parent.left; + anchors.right: parent.right; + + RalewayRegular { + id: failureHeaderText; + text: "Purchase Failed.
Your Purchases and HFC balance haven't changed."; + // Text size + size: 24; + // Anchors + anchors.top: parent.top; + anchors.topMargin: 80; + height: paintedHeight; + anchors.left: parent.left; + anchors.right: parent.right; + // Style + color: hifi.colors.faintGray; + wrapMode: Text.WordWrap; + // Alignment + horizontalAlignment: Text.AlignHCenter; + verticalAlignment: Text.AlignVCenter; + } + + RalewayRegular { + id: failureErrorText; + // Text size + size: 16; + // Anchors + anchors.top: failureHeaderText.bottom; + anchors.topMargin: 35; + height: paintedHeight; + anchors.left: parent.left; + anchors.right: parent.right; + // Style + color: hifi.colors.faintGray; + wrapMode: Text.WordWrap; + // Alignment + horizontalAlignment: Text.AlignHCenter; + verticalAlignment: Text.AlignVCenter; + } + + Item { + id: backToMarketplaceButtonContainer; + // Size + width: root.width; + height: 130; + // Anchors + anchors.left: parent.left; + anchors.bottom: parent.bottom; + anchors.bottomMargin: 8; + // "Back to Marketplace" button + HifiControlsUit.Button { + id: backToMarketplaceButton; + 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: "Back to Marketplace"; + onClicked: { + sendToScript({method: 'checkout_continueShopping', itemId: itemId}); + } + } + } + } + // + // CHECKOUT FAILURE END + // + + // + // FUNCTION DEFINITIONS START + // + // + // 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) { + switch (message.method) { + case 'updateCheckoutQML': + itemId = message.params.itemId; + itemNameText.text = message.params.itemName; + itemAuthorText.text = message.params.itemAuthor; + root.itemPriceFull = message.params.itemPrice; + itemPriceText.text = root.itemPriceFull === 0 ? "Free" : "" + (parseFloat(root.itemPriceFull/100).toFixed(2)) + " HFC"; + itemHref = message.params.itemHref; + if (itemHref.indexOf('.json') === -1) { + root.itemIsJson = false; + } + setBuyText(); + break; + default: + console.log('Unrecognized message from marketplaces.js:', JSON.stringify(message)); + } + } + signal sendToScript(var message); + + function purchasesContains(purchasesJson, id) { + for (var idx = 0; idx < purchasesJson.length; idx++) { + if(purchasesJson[idx].id === id) { + return true; + } + } + return false; + } + + function setBuyText() { + if (root.itemIsJson) { + if (root.purchasesReceived && root.balanceReceived) { + if (root.balanceAfterPurchase < 0) { + if (root.alreadyOwned) { + buyText.text = "You do not have enough HFC to purchase this item again. Go to your Purchases to view the copy you own."; + } else { + buyText.text = "You do not have enough HFC to purchase this item."; + } + } else { + if (root.alreadyOwned) { + buyText.text = "You already own this item. If you buy it again, you'll be able to use multiple copies of it at once."; + } else { + buyText.text = "This item will be added to your Purchases, which can be accessed from Marketplace."; + } + } + } else { + buyText.text = ""; + } + } else { + buyText.text = "This Marketplace item isn't an entity. It will not be added to your Purchases."; + } + } + + // + // FUNCTION DEFINITIONS END + // +} diff --git a/interface/resources/qml/hifi/commerce/images/02car.jpg b/interface/resources/qml/hifi/commerce/images/02car.jpg deleted file mode 100644 index 5dd8091e57..0000000000 Binary files a/interface/resources/qml/hifi/commerce/images/02car.jpg and /dev/null differ diff --git a/interface/resources/qml/hifi/commerce/images/04stars.jpg b/interface/resources/qml/hifi/commerce/images/04stars.jpg deleted file mode 100644 index 8f2bf62f83..0000000000 Binary files a/interface/resources/qml/hifi/commerce/images/04stars.jpg and /dev/null differ diff --git a/interface/resources/qml/hifi/commerce/images/05plane.jpg b/interface/resources/qml/hifi/commerce/images/05plane.jpg deleted file mode 100644 index 6504459d8b..0000000000 Binary files a/interface/resources/qml/hifi/commerce/images/05plane.jpg and /dev/null differ diff --git a/interface/resources/qml/hifi/commerce/purchases/PurchasedItem.qml b/interface/resources/qml/hifi/commerce/purchases/PurchasedItem.qml new file mode 100644 index 0000000000..af32f5cfb7 --- /dev/null +++ b/interface/resources/qml/hifi/commerce/purchases/PurchasedItem.qml @@ -0,0 +1,143 @@ +// +// PurchasedItem.qml +// qml/hifi/commerce/purchases +// +// PurchasedItem +// +// Created by Zach Fox on 2017-08-25 +// 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 +import "../wallet" as HifiWallet + +// references XXX from root context + +Rectangle { + HifiConstants { id: hifi; } + + id: root; + property string itemName: ""; + property string itemId: ""; + property string itemPreviewImageUrl: ""; + property string itemHref: ""; + // Style + color: hifi.colors.white; + // Size + width: parent.width; + height: 120; + + Image { + id: itemPreviewImage; + source: root.itemPreviewImageUrl; + anchors.left: parent.left; + anchors.leftMargin: 8; + anchors.top: parent.top; + anchors.topMargin: 8; + anchors.bottom: parent.bottom; + anchors.bottomMargin: 8; + width: 180; + fillMode: Image.PreserveAspectFit; + + MouseArea { + anchors.fill: parent; + onClicked: { + sendToPurchases({method: 'purchases_itemInfoClicked', itemId: root.itemId}); + } + } + } + + + RalewayRegular { + id: itemName; + anchors.top: itemPreviewImage.top; + anchors.left: itemPreviewImage.right; + anchors.leftMargin: 8; + anchors.right: parent.right; + anchors.rightMargin: 8; + height: 30; + // Text size + size: 20; + // Style + color: hifi.colors.blueAccent; + text: root.itemName; + elide: Text.ElideRight; + // Alignment + horizontalAlignment: Text.AlignLeft; + verticalAlignment: Text.AlignVCenter; + + MouseArea { + anchors.fill: parent; + hoverEnabled: enabled; + onClicked: { + sendToPurchases({method: 'purchases_itemInfoClicked', itemId: root.itemId}); + } + onEntered: { + itemName.color = hifi.colors.blueHighlight; + } + onExited: { + itemName.color = hifi.colors.blueAccent; + } + } + } + + Item { + id: buttonContainer; + anchors.top: itemName.bottom; + anchors.topMargin: 8; + anchors.bottom: parent.bottom; + anchors.bottomMargin: 8; + anchors.left: itemPreviewImage.right; + anchors.leftMargin: 8; + anchors.right: parent.right; + anchors.rightMargin: 8; + + // "Rez" button + HifiControlsUit.Button { + id: rezButton; + color: hifi.buttons.blue; + colorScheme: hifi.colorSchemes.dark; + anchors.top: parent.top; + anchors.left: parent.left; + anchors.right: parent.right; + height: parent.height/2 - 4; + text: "Rez Item" + onClicked: { + if (urlHandler.canHandleUrl(root.itemHref)) { + urlHandler.handleUrl(root.itemHref); + } + } + } + + // "More Info" button + HifiControlsUit.Button { + id: moreInfoButton; + color: hifi.buttons.black; + colorScheme: hifi.colorSchemes.dark; + anchors.bottom: parent.bottom; + anchors.left: parent.left; + anchors.right: parent.right; + height: parent.height/2 - 4; + text: "More Info" + onClicked: { + sendToPurchases({method: 'purchases_itemInfoClicked', itemId: root.itemId}); + } + } + } + + // + // FUNCTION DEFINITIONS START + // + signal sendToPurchases(var msg); + // + // FUNCTION DEFINITIONS END + // +} diff --git a/interface/resources/qml/hifi/commerce/purchases/Purchases.qml b/interface/resources/qml/hifi/commerce/purchases/Purchases.qml new file mode 100644 index 0000000000..bc843a140d --- /dev/null +++ b/interface/resources/qml/hifi/commerce/purchases/Purchases.qml @@ -0,0 +1,505 @@ +// +// Purchases.qml +// qml/hifi/commerce/purchases +// +// Purchases +// +// Created by Zach Fox on 2017-08-25 +// 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 +import "../wallet" as HifiWallet + +// references XXX from root context + +Rectangle { + HifiConstants { id: hifi; } + + id: root; + property string activeView: "initialize"; + property string referrerURL: ""; + property bool securityImageResultReceived: false; + property bool keyFilePathIfExistsResultReceived: false; + property bool purchasesReceived: false; + property bool punctuationMode: false; + // Style + color: hifi.colors.baseGray; + Hifi.QmlCommerce { + id: commerce; + + onLoginStatusResult: { + if (!isLoggedIn && root.activeView !== "needsLogIn") { + root.activeView = "needsLogIn"; + } else if (isLoggedIn) { + root.activeView = "initialize"; + commerce.getSecurityImage(); + commerce.getKeyFilePathIfExists(); + commerce.inventory(); + } + } + + onSecurityImageResult: { + securityImageResultReceived = true; + if (!exists && root.activeView !== "notSetUp") { // "If security image is not set up" + root.activeView = "notSetUp"; + } else if (root.securityImageResultReceived && exists && root.keyFilePathIfExistsResultReceived && root.activeView === "initialize") { + root.activeView = "purchasesMain"; + } else if (exists) { + // just set the source again (to be sure the change was noticed) + securityImage.source = ""; + securityImage.source = "image://security/securityImage"; + } + } + + onKeyFilePathIfExistsResult: { + keyFilePathIfExistsResultReceived = true; + if (path === "" && root.activeView !== "notSetUp") { + root.activeView = "notSetUp"; + } else if (root.securityImageResultReceived && root.keyFilePathIfExistsResultReceived && path !== "" && root.activeView === "initialize") { + root.activeView = "purchasesMain"; + } + } + + onInventoryResult: { + purchasesReceived = true; + if (result.status !== 'success') { + console.log("Failed to get purchases", result.message); + } else { + purchasesModel.clear(); + purchasesModel.append(result.data.assets); + filteredPurchasesModel.clear(); + filteredPurchasesModel.append(result.data.assets); + } + } + } + + HifiWallet.SecurityImageModel { + id: securityImageModel; + } + + // + // TITLE BAR START + // + Item { + id: titleBarContainer; + visible: !needsLogIn.visible; + // Size + height: 50; + // Anchors + anchors.left: parent.left; + anchors.right: parent.right; + anchors.top: parent.top; + + // Title Bar text + RalewaySemiBold { + id: titleBarText; + text: "PURCHASES"; + // Text size + size: hifi.fontSizes.overlayTitle; + // Anchors + anchors.top: parent.top; + anchors.left: parent.left; + anchors.leftMargin: 16; + anchors.bottom: parent.bottom; + width: paintedWidth; + // Style + color: hifi.colors.faintGray; + // Alignment + horizontalAlignment: Text.AlignHLeft; + verticalAlignment: Text.AlignVCenter; + } + + // Security Image (TEMPORARY!) + Image { + id: securityImage; + // Anchors + anchors.top: parent.top; + anchors.right: parent.right; + anchors.verticalCenter: parent.verticalCenter; + height: parent.height - 10; + width: height; + fillMode: Image.PreserveAspectFit; + mipmap: true; + cache: false; + source: "image://security/securityImage"; + } + Image { + id: securityImageOverlay; + source: "../wallet/images/lockOverlay.png"; + width: securityImage.width * 0.45; + height: securityImage.height * 0.45; + anchors.bottom: securityImage.bottom; + anchors.right: securityImage.right; + mipmap: true; + opacity: 0.9; + } + + // Separator + HifiControlsUit.Separator { + anchors.left: parent.left; + anchors.right: parent.right; + anchors.bottom: parent.bottom; + } + } + // + // TITLE BAR END + // + + Rectangle { + id: initialize; + visible: root.activeView === "initialize"; + anchors.top: titleBarContainer.bottom; + anchors.bottom: parent.bottom; + anchors.left: parent.left; + anchors.right: parent.right; + color: hifi.colors.baseGray; + + Component.onCompleted: { + securityImageResultReceived = false; + purchasesReceived = false; + keyFilePathIfExistsResultReceived = false; + commerce.getLoginStatus(); + } + } + + HifiWallet.NeedsLogIn { + id: needsLogIn; + visible: root.activeView === "needsLogIn"; + anchors.top: parent.top; + anchors.bottom: parent.bottom; + anchors.left: parent.left; + anchors.right: parent.right; + + Connections { + onSendSignalToWallet: { + sendToScript(msg); + } + } + } + Connections { + target: GlobalServices + onMyUsernameChanged: { + commerce.getLoginStatus(); + } + } + + // + // "WALLET NOT SET UP" START + // + Item { + id: notSetUp; + visible: root.activeView === "notSetUp"; + anchors.top: titleBarContainer.bottom; + anchors.bottom: parent.bottom; + anchors.left: parent.left; + anchors.right: parent.right; + + RalewayRegular { + id: notSetUpText; + text: "Your wallet isn't set up.

Set up your Wallet (no credit card necessary) to claim your free HFC " + + "and get items from the Marketplace."; + // Text size + size: 24; + // Anchors + anchors.top: parent.top; + anchors.bottom: notSetUpActionButtonsContainer.top; + anchors.left: parent.left; + anchors.leftMargin: 16; + anchors.right: parent.right; + anchors.rightMargin: 16; + // Style + color: hifi.colors.faintGray; + wrapMode: Text.WordWrap; + // Alignment + horizontalAlignment: Text.AlignHCenter; + verticalAlignment: Text.AlignVCenter; + } + + Item { + id: notSetUpActionButtonsContainer; + // Size + width: root.width; + height: 70; + // Anchors + anchors.left: parent.left; + anchors.bottom: parent.bottom; + anchors.bottomMargin: 24; + + // "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: { + sendToScript({method: 'purchases_backClicked', referrerURL: referrerURL}); + } + } + + // "Set Up" button + HifiControlsUit.Button { + id: setUpButton; + color: hifi.buttons.blue; + 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: "Set Up Wallet" + onClicked: { + sendToScript({method: 'checkout_setUpClicked'}); + } + } + } + } + // + // "WALLET NOT SET UP" END + // + + // + // PURCHASES CONTENTS START + // + Item { + id: purchasesContentsContainer; + visible: root.activeView === "purchasesMain"; + // Anchors + anchors.left: parent.left; + anchors.leftMargin: 4; + anchors.right: parent.right; + anchors.rightMargin: 4; + anchors.top: titleBarContainer.bottom; + anchors.topMargin: 8; + anchors.bottom: actionButtonsContainer.top; + anchors.bottomMargin: 8; + + // + // FILTER BAR START + // + Item { + id: filterBarContainer; + // Size + height: 40; + // Anchors + anchors.left: parent.left; + anchors.leftMargin: 8; + anchors.right: parent.right; + anchors.rightMargin: 8; + anchors.top: parent.top; + anchors.topMargin: 4; + + HifiControlsUit.TextField { + id: filterBar; + property int previousLength: 0; + anchors.fill: parent; + placeholderText: "Filter"; + + onTextChanged: { + if (filterBar.text.length < previousLength) { + filteredPurchasesModel.clear(); + + for (var i = 0; i < purchasesModel.count; i++) { + filteredPurchasesModel.append(purchasesModel.get(i)); + } + } + + for (var i = 0; i < filteredPurchasesModel.count; i++) { + if (filteredPurchasesModel.get(i).title.toLowerCase().indexOf(filterBar.text.toLowerCase()) === -1) { + filteredPurchasesModel.remove(i); + i--; + } + } + previousLength = filterBar.text.length; + } + + onAccepted: { + focus = false; + } + } + } + // + // FILTER BAR END + // + + ListModel { + id: purchasesModel; + } + ListModel { + id: filteredPurchasesModel; + } + + ListView { + id: purchasesContentsList; + visible: purchasesModel.count !== 0; + clip: true; + model: filteredPurchasesModel; + // Anchors + anchors.top: filterBarContainer.bottom; + anchors.topMargin: 12; + anchors.left: parent.left; + anchors.bottom: parent.bottom; + width: parent.width; + delegate: PurchasedItem { + itemName: title; + itemId: id; + itemPreviewImageUrl: preview; + itemHref: root_file_url; + anchors.topMargin: 12; + anchors.bottomMargin: 12; + + Connections { + onSendToPurchases: { + if (msg.method === 'purchases_itemInfoClicked') { + sendToScript({method: 'purchases_itemInfoClicked', itemId: itemId}); + } + } + } + } + } + + Item { + id: noPurchasesAlertContainer; + visible: !purchasesContentsList.visible && root.purchasesReceived; + anchors.top: filterBarContainer.bottom; + anchors.topMargin: 12; + anchors.left: parent.left; + anchors.bottom: parent.bottom; + width: parent.width; + + // Explanitory text + RalewayRegular { + id: haventPurchasedYet; + text: "You haven't purchased anything yet!

Get an item from Marketplace to add it to your Purchases."; + // Text size + size: 22; + // Anchors + anchors.top: parent.top; + anchors.topMargin: 150; + anchors.left: parent.left; + anchors.leftMargin: 24; + anchors.right: parent.right; + anchors.rightMargin: 24; + height: paintedHeight; + // Style + color: hifi.colors.faintGray; + wrapMode: Text.WordWrap; + // Alignment + horizontalAlignment: Text.AlignHCenter; + } + + // "Set Up" button + HifiControlsUit.Button { + color: hifi.buttons.blue; + colorScheme: hifi.colorSchemes.dark; + anchors.top: haventPurchasedYet.bottom; + anchors.topMargin: 20; + anchors.horizontalCenter: parent.horizontalCenter; + width: parent.width * 2 / 3; + height: 50; + text: "Visit Marketplace"; + onClicked: { + sendToScript({method: 'purchases_goToMarketplaceClicked'}); + } + } + } + } + // + // PURCHASES CONTENTS END + // + + // + // ACTION BUTTONS START + // + Item { + id: actionButtonsContainer; + visible: purchasesContentsContainer.visible; + // Size + width: parent.width; + height: 40; + // Anchors + anchors.left: parent.left; + anchors.bottom: keyboard.top; + anchors.bottomMargin: 8; + + // "Back" button + HifiControlsUit.Button { + id: backButton; + 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: "Back" + onClicked: { + sendToScript({method: 'purchases_backClicked', referrerURL: referrerURL}); + } + } + } + // + // ACTION BUTTONS END + // + + HifiControlsUit.Keyboard { + id: keyboard; + raised: HMD.mounted && filterBar.focus; + numeric: parent.punctuationMode; + anchors { + bottom: parent.bottom; + left: parent.left; + right: parent.right; + } + } + + // + // FUNCTION DEFINITIONS START + // + // + // 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) { + switch (message.method) { + case 'updatePurchases': + referrerURL = message.referrerURL; + break; + default: + console.log('Unrecognized message from marketplaces.js:', JSON.stringify(message)); + } + } + signal sendToScript(var message); + + // + // FUNCTION DEFINITIONS END + // +} diff --git a/interface/resources/qml/hifi/commerce/wallet/NeedsLogIn.qml b/interface/resources/qml/hifi/commerce/wallet/NeedsLogIn.qml new file mode 100644 index 0000000000..1e95aaa297 --- /dev/null +++ b/interface/resources/qml/hifi/commerce/wallet/NeedsLogIn.qml @@ -0,0 +1,188 @@ +// +// NeedsLogIn.qml +// qml/hifi/commerce/wallet +// +// NeedsLogIn +// +// Created by Zach Fox on 2017-08-18 +// 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 + +Item { + HifiConstants { id: hifi; } + + id: root; + Hifi.QmlCommerce { + id: commerce; + } + + // + // LOGIN PAGE START + // + Item { + id: loginPageContainer; + // Anchors + anchors.fill: parent; + + Item { + id: loginTitle; + // Size + width: parent.width; + height: 50; + // Anchors + anchors.left: parent.left; + anchors.top: parent.top; + + // Title Bar text + RalewaySemiBold { + text: "HIFI COMMERCE - LOGIN"; + // Text size + size: hifi.fontSizes.overlayTitle; + // Anchors + anchors.top: parent.top; + anchors.left: parent.left; + anchors.leftMargin: 16; + anchors.bottom: parent.bottom; + width: paintedWidth; + // Style + color: hifi.colors.faintGray; + // Alignment + horizontalAlignment: Text.AlignHLeft; + verticalAlignment: Text.AlignVCenter; + } + } + + // Text below title bar + RalewaySemiBold { + id: loginTitleHelper; + text: "Please Log In to High Fidelity"; + // Text size + size: 24; + // Anchors + anchors.top: loginTitle.bottom; + anchors.topMargin: 100; + anchors.left: parent.left; + anchors.leftMargin: 16; + anchors.right: parent.right; + anchors.rightMargin: 16; + height: 50; + // Style + color: hifi.colors.faintGray; + // Alignment + horizontalAlignment: Text.AlignHCenter; + verticalAlignment: Text.AlignVCenter; + } + + // Text below helper text + RalewayRegular { + id: loginDetailText; + text: "To buy/sell items on the Marketplace, or to use your Wallet, you must first log in to High Fidelity."; + // Text size + size: 18; + // Anchors + anchors.top: loginTitleHelper.bottom; + anchors.topMargin: 25; + anchors.left: parent.left; + anchors.leftMargin: 16; + anchors.right: parent.right; + anchors.rightMargin: 16; + height: 50; + // Style + color: hifi.colors.faintGray; + wrapMode: Text.WordWrap; + // Alignment + horizontalAlignment: Text.AlignHCenter; + verticalAlignment: Text.AlignVCenter; + } + + + + Item { + // Size + width: root.width; + height: 70; + // Anchors + anchors.top: loginDetailText.bottom; + anchors.topMargin: 40; + anchors.left: parent.left; + + // "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: { + sendToScript({method: 'needsLogIn_cancelClicked'}); + } + } + + // "Set Up" button + HifiControlsUit.Button { + id: setUpButton; + color: hifi.buttons.blue; + 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: "Log In" + onClicked: { + sendToScript({method: 'needsLogIn_loginClicked'}); + } + } + } + } + // + // LOGIN PAGE END + // + + // + // FUNCTION DEFINITIONS START + // + // + // Function Name: fromScript() + // + // Relevant Variables: + // None + // + // Arguments: + // message: The message sent from the JavaScript. + // Messages are in format "{method, params}", like json-rpc. + // + // Description: + // Called when a message is received from a script. + // + function fromScript(message) { + switch (message.method) { + default: + console.log('Unrecognized message from wallet.js:', JSON.stringify(message)); + } + } + signal sendSignalToWallet(var msg); + // + // FUNCTION DEFINITIONS END + // +} diff --git a/interface/resources/qml/hifi/commerce/wallet/NotSetUp.qml b/interface/resources/qml/hifi/commerce/wallet/NotSetUp.qml index 3efb592ba1..42b8526a8a 100644 --- a/interface/resources/qml/hifi/commerce/wallet/NotSetUp.qml +++ b/interface/resources/qml/hifi/commerce/wallet/NotSetUp.qml @@ -80,7 +80,7 @@ Item { // "Set Up" button HifiControlsUit.Button { - color: hifi.buttons.black; + color: hifi.buttons.blue; colorScheme: hifi.colorSchemes.dark; anchors.bottom: parent.bottom; anchors.bottomMargin: 150; diff --git a/interface/resources/qml/hifi/commerce/wallet/PassphraseSelection.qml b/interface/resources/qml/hifi/commerce/wallet/PassphraseSelection.qml index 89ef851b06..39d07315d5 100644 --- a/interface/resources/qml/hifi/commerce/wallet/PassphraseSelection.qml +++ b/interface/resources/qml/hifi/commerce/wallet/PassphraseSelection.qml @@ -45,9 +45,16 @@ Item { } } + // This will cause a bug -- if you bring up passphrase selection in HUD mode while + // in HMD while having HMD preview enabled, then move, then finish passphrase selection, + // HMD preview will stay off. + // TODO: Fix this unlikely bug onVisibleChanged: { if (visible) { passphraseField.focus = true; + sendMessageToLightbox({method: 'disableHmdPreview'}); + } else { + sendMessageToLightbox({method: 'maybeEnableHmdPreview'}); } } @@ -66,11 +73,25 @@ Item { echoMode: TextInput.Password; placeholderText: "passphrase"; - onVisibleChanged: { - if (visible) { - text = ""; + onFocusChanged: { + if (focus) { + sendMessageToLightbox({method: 'walletSetup_raiseKeyboard'}); + } else if (!passphraseFieldAgain.focus) { + sendMessageToLightbox({method: 'walletSetup_lowerKeyboard'}); } } + + MouseArea { + anchors.fill: parent; + onClicked: { + parent.focus = true; + sendMessageToLightbox({method: 'walletSetup_raiseKeyboard'}); + } + } + + onAccepted: { + passphraseFieldAgain.focus = true; + } } HifiControlsUit.TextField { id: passphraseFieldAgain; @@ -82,11 +103,25 @@ Item { echoMode: TextInput.Password; placeholderText: "re-enter passphrase"; - onVisibleChanged: { - if (visible) { - text = ""; + onFocusChanged: { + if (focus) { + sendMessageToLightbox({method: 'walletSetup_raiseKeyboard'}); + } else if (!passphraseField.focus) { + sendMessageToLightbox({method: 'walletSetup_lowerKeyboard'}); } } + + MouseArea { + anchors.fill: parent; + onClicked: { + parent.focus = true; + sendMessageToLightbox({method: 'walletSetup_raiseKeyboard'}); + } + } + + onAccepted: { + focus = false; + } } // Security Image @@ -111,9 +146,19 @@ Item { commerce.getSecurityImage(); } } - // "Security picture" text below pic + Image { + id: topSecurityImageOverlay; + source: "images/lockOverlay.png"; + width: passphrasePageSecurityImage.width * 0.45; + height: passphrasePageSecurityImage.height * 0.45; + anchors.bottom: passphrasePageSecurityImage.bottom; + anchors.right: passphrasePageSecurityImage.right; + mipmap: true; + opacity: 0.9; + } + // "Security image" text below pic RalewayRegular { - text: "security picture"; + text: "security image"; // Text size size: 12; // Anchors @@ -228,5 +273,11 @@ Item { errorText.text = text; } + function clearPassphraseFields() { + passphraseField.text = ""; + passphraseFieldAgain.text = ""; + setErrorText(""); + } + signal sendMessageToLightbox(var msg); } diff --git a/interface/resources/qml/hifi/commerce/wallet/PassphraseSelectionLightbox.qml b/interface/resources/qml/hifi/commerce/wallet/PassphraseSelectionLightbox.qml index 862d1894db..608cb229b0 100644 --- a/interface/resources/qml/hifi/commerce/wallet/PassphraseSelectionLightbox.qml +++ b/interface/resources/qml/hifi/commerce/wallet/PassphraseSelectionLightbox.qml @@ -27,29 +27,6 @@ Rectangle { // Style color: hifi.colors.baseGray; - onVisibleChanged: { - if (visible) { - root.resetSubmitButton(); - } - } - - Connections { - target: passphraseSelection; - onSendMessageToLightbox: { - if (msg.method === 'statusResult') { - if (msg.status) { - // Success submitting new passphrase - root.resetSubmitButton(); - root.visible = false; - } else { - // Error submitting new passphrase - root.resetSubmitButton(); - passphraseSelection.setErrorText("Backend error"); - } - } - } - } - // // SECURE PASSPHRASE SELECTION START // @@ -113,6 +90,24 @@ Rectangle { anchors.left: parent.left; anchors.right: parent.right; anchors.bottom: passphraseNavBar.top; + + Connections { + onSendMessageToLightbox: { + if (msg.method === 'statusResult') { + if (msg.status) { + // Success submitting new passphrase + root.resetSubmitButton(); + root.visible = false; + } else { + // Error submitting new passphrase + root.resetSubmitButton(); + passphraseSelection.setErrorText("Backend error"); + } + } else { + sendSignalToWallet(msg); + } + } + } } // Navigation Bar @@ -168,8 +163,14 @@ Rectangle { // SECURE PASSPHRASE SELECTION END // + signal sendSignalToWallet(var msg); + function resetSubmitButton() { passphraseSubmitButton.enabled = true; passphraseSubmitButton.text = "Submit"; } + + function clearPassphraseFields() { + passphraseSelection.clearPassphraseFields(); + } } diff --git a/interface/resources/qml/hifi/commerce/wallet/Security.qml b/interface/resources/qml/hifi/commerce/wallet/Security.qml index d0a96db3f2..b5d52d57e2 100644 --- a/interface/resources/qml/hifi/commerce/wallet/Security.qml +++ b/interface/resources/qml/hifi/commerce/wallet/Security.qml @@ -13,6 +13,7 @@ import Hifi 1.0 as Hifi import QtQuick 2.5 +import QtGraphicalEffects 1.0 import QtQuick.Controls 1.4 import "../../../styles-uit" import "../../../controls-uit" as HifiControlsUit @@ -35,8 +36,6 @@ Item { topSecurityImage.source = path; changeSecurityImageImage.source = ""; changeSecurityImageImage.source = path; - changePassphraseImage.source = ""; - changePassphraseImage.source = path; } } @@ -92,9 +91,19 @@ Item { source: "image://security/securityImage"; cache: false; } - // "Security picture" text below pic + Image { + id: topSecurityImageMask; + source: "images/lockOverlay.png"; + width: topSecurityImage.width * 0.45; + height: topSecurityImage.height * 0.45; + anchors.bottom: topSecurityImage.bottom; + anchors.right: topSecurityImage.right; + mipmap: true; + opacity: 0.9; + } + // "Security image" text below pic RalewayRegular { - text: "security picture"; + text: "security image"; // Text size size: 12; // Anchors @@ -148,10 +157,16 @@ Item { anchors.left: parent.left; height: parent.height; width: height; - source: "image://security/securityImage"; + source: "images/lockOverlay.png"; fillMode: Image.PreserveAspectFit; mipmap: true; cache: false; + visible: false; + } + ColorOverlay { + anchors.fill: changePassphraseImage; + source: changePassphraseImage; + color: "white" } // "Change Passphrase" button HifiControlsUit.Button { diff --git a/interface/resources/qml/hifi/commerce/wallet/SecurityImageModel.qml b/interface/resources/qml/hifi/commerce/wallet/SecurityImageModel.qml index b49f16857b..7b1434aa3c 100644 --- a/interface/resources/qml/hifi/commerce/wallet/SecurityImageModel.qml +++ b/interface/resources/qml/hifi/commerce/wallet/SecurityImageModel.qml @@ -16,27 +16,27 @@ import QtQuick 2.5 ListModel { id: root; ListElement{ - sourcePath: "images/01cat.jpg" + sourcePath: "images/01.jpg" securityImageEnumValue: 1; } ListElement{ - sourcePath: "images/02car.jpg" + sourcePath: "images/02.jpg" securityImageEnumValue: 2; } ListElement{ - sourcePath: "images/03dog.jpg" + sourcePath: "images/03.jpg" securityImageEnumValue: 3; } ListElement{ - sourcePath: "images/04stars.jpg" + sourcePath: "images/04.jpg" securityImageEnumValue: 4; } ListElement{ - sourcePath: "images/05plane.jpg" + sourcePath: "images/05.jpg" securityImageEnumValue: 5; } ListElement{ - sourcePath: "images/06gingerbread.jpg" + sourcePath: "images/06.jpg" securityImageEnumValue: 6; } diff --git a/interface/resources/qml/hifi/commerce/wallet/SecurityImageSelection.qml b/interface/resources/qml/hifi/commerce/wallet/SecurityImageSelection.qml index 7ab52b7551..5f5a3e8247 100644 --- a/interface/resources/qml/hifi/commerce/wallet/SecurityImageSelection.qml +++ b/interface/resources/qml/hifi/commerce/wallet/SecurityImageSelection.qml @@ -24,16 +24,16 @@ Item { HifiConstants { id: hifi; } id: root; - - Hifi.QmlCommerce { - id: commerce; - onSecurityImageResult: { - } - } - + + // This will cause a bug -- if you bring up security image selection in HUD mode while + // in HMD while having HMD preview enabled, then move, then finish passphrase selection, + // HMD preview will stay off. + // TODO: Fix this unlikely bug onVisibleChanged: { if (visible) { - commerce.getSecurityImage(); + sendSignalToWallet({method: 'disableHmdPreview'}); + } else { + sendSignalToWallet({method: 'maybeEnableHmdPreview'}); } } @@ -83,7 +83,7 @@ Item { // // FUNCTION DEFINITIONS START // - signal sendToScript(var message); + signal sendSignalToWallet(var msg); function getImagePathFromImageID(imageID) { return (imageID ? gridModel.getImagePathFromImageID(imageID) : ""); diff --git a/interface/resources/qml/hifi/commerce/wallet/SecurityImageSelectionLightbox.qml b/interface/resources/qml/hifi/commerce/wallet/SecurityImageSelectionLightbox.qml index d4b0b82ed3..ff7156dd6c 100644 --- a/interface/resources/qml/hifi/commerce/wallet/SecurityImageSelectionLightbox.qml +++ b/interface/resources/qml/hifi/commerce/wallet/SecurityImageSelectionLightbox.qml @@ -28,12 +28,6 @@ Rectangle { // Style color: hifi.colors.baseGray; - onVisibleChanged: { - if (visible) { - root.resetSubmitButton(); - } - } - Hifi.QmlCommerce { id: commerce; @@ -116,6 +110,12 @@ Rectangle { anchors.right: parent.right; anchors.rightMargin: 16; height: 280; + + Connections { + onSendSignalToWallet: { + sendSignalToWallet(msg); + } + } } // Text below security images @@ -193,6 +193,8 @@ Rectangle { // SECURITY IMAGE SELECTION END // + signal sendSignalToWallet(var msg); + function resetSubmitButton() { securityImageSubmitButton.enabled = true; securityImageSubmitButton.text = "Submit"; diff --git a/interface/resources/qml/hifi/commerce/wallet/Wallet.qml b/interface/resources/qml/hifi/commerce/wallet/Wallet.qml index ae2606c0f6..0d46077f2a 100644 --- a/interface/resources/qml/hifi/commerce/wallet/Wallet.qml +++ b/interface/resources/qml/hifi/commerce/wallet/Wallet.qml @@ -28,12 +28,23 @@ Rectangle { property string activeView: "initialize"; property bool securityImageResultReceived: false; property bool keyFilePathIfExistsResultReceived: false; + property bool keyboardRaised: false; // Style color: hifi.colors.baseGray; Hifi.QmlCommerce { id: commerce; + onLoginStatusResult: { + if (!isLoggedIn && root.activeView !== "needsLogIn") { + root.activeView = "needsLogIn"; + } else if (isLoggedIn) { + root.activeView = "initialize"; + commerce.getSecurityImage(); + commerce.getKeyFilePathIfExists(); + } + } + onSecurityImageResult: { securityImageResultReceived = true; if (!exists && root.activeView !== "notSetUp") { // "If security image is not set up" @@ -57,27 +68,6 @@ Rectangle { id: securityImageModel; } - Connections { - target: walletSetupLightbox; - onSendSignalToWallet: { - if (msg.method === 'walletSetup_cancelClicked') { - walletSetupLightbox.visible = false; - } else if (msg.method === 'walletSetup_finished') { - root.activeView = "walletHome"; - } else { - sendToScript(msg); - } - } - } - Connections { - target: notSetUp; - onSendSignalToWallet: { - if (msg.method === 'setUpClicked') { - walletSetupLightbox.visible = true; - } - } - } - Rectangle { id: walletSetupLightboxContainer; visible: walletSetupLightbox.visible || passphraseSelectionLightbox.visible || securityImageSelectionLightbox.visible; @@ -89,26 +79,60 @@ Rectangle { WalletSetupLightbox { id: walletSetupLightbox; visible: false; - z: 999; + z: 998; anchors.centerIn: walletSetupLightboxContainer; width: walletSetupLightboxContainer.width - 50; height: walletSetupLightboxContainer.height - 50; + + Connections { + onSendSignalToWallet: { + if (msg.method === 'walletSetup_cancelClicked') { + walletSetupLightbox.visible = false; + } else if (msg.method === 'walletSetup_finished') { + root.activeView = "walletHome"; + } else if (msg.method === 'walletSetup_raiseKeyboard') { + root.keyboardRaised = true; + } else if (msg.method === 'walletSetup_lowerKeyboard') { + root.keyboardRaised = false; + } else { + sendToScript(msg); + } + } + } } PassphraseSelectionLightbox { id: passphraseSelectionLightbox; visible: false; - z: 999; + z: 998; anchors.centerIn: walletSetupLightboxContainer; width: walletSetupLightboxContainer.width - 50; height: walletSetupLightboxContainer.height - 50; + + Connections { + onSendSignalToWallet: { + if (msg.method === 'walletSetup_raiseKeyboard') { + root.keyboardRaised = true; + } else if (msg.method === 'walletSetup_lowerKeyboard') { + root.keyboardRaised = false; + } else { + sendToScript(msg); + } + } + } } SecurityImageSelectionLightbox { id: securityImageSelectionLightbox; visible: false; - z: 999; + z: 998; anchors.centerIn: walletSetupLightboxContainer; width: walletSetupLightboxContainer.width - 50; height: walletSetupLightboxContainer.height - 50; + + Connections { + onSendSignalToWallet: { + sendToScript(msg); + } + } } @@ -117,6 +141,7 @@ Rectangle { // Item { id: titleBarContainer; + visible: !needsLogIn.visible; // Size width: parent.width; height: 50; @@ -168,8 +193,28 @@ Rectangle { color: hifi.colors.baseGray; Component.onCompleted: { - commerce.getSecurityImage(); - commerce.getKeyFilePathIfExists(); + commerce.getLoginStatus(); + } + } + + NeedsLogIn { + id: needsLogIn; + visible: root.activeView === "needsLogIn"; + anchors.top: parent.top; + anchors.bottom: parent.bottom; + anchors.left: parent.left; + anchors.right: parent.right; + + Connections { + onSendSignalToWallet: { + sendToScript(msg); + } + } + } + Connections { + target: GlobalServices + onMyUsernameChanged: { + commerce.getLoginStatus(); } } @@ -180,6 +225,14 @@ Rectangle { anchors.bottom: tabButtonsContainer.top; anchors.left: parent.left; anchors.right: parent.right; + + Connections { + onSendSignalToWallet: { + if (msg.method === 'setUpClicked') { + walletSetupLightbox.visible = true; + } + } + } } WalletHome { @@ -219,14 +272,15 @@ Rectangle { anchors.leftMargin: 16; anchors.right: parent.right; anchors.rightMargin: 16; - } - Connections { - target: security; - onSendSignalToWallet: { - if (msg.method === 'walletSecurity_changePassphrase') { - passphraseSelectionLightbox.visible = true; - } else if (msg.method === 'walletSecurity_changeSecurityImage') { - securityImageSelectionLightbox.visible = true; + + Connections { + onSendSignalToWallet: { + if (msg.method === 'walletSecurity_changePassphrase') { + passphraseSelectionLightbox.visible = true; + passphraseSelectionLightbox.clearPassphraseFields(); + } else if (msg.method === 'walletSecurity_changeSecurityImage') { + securityImageSelectionLightbox.visible = true; + } } } } @@ -254,6 +308,7 @@ Rectangle { // Item { id: tabButtonsContainer; + visible: !needsLogIn.visible; property int numTabs: 5; // Size width: root.width; @@ -455,7 +510,47 @@ Rectangle { } // // TAB BUTTONS END - // + // + + Item { + id: keyboardContainer; + z: 999; + visible: keyboard.raised; + property bool punctuationMode: false; + anchors { + bottom: parent.bottom; + left: parent.left; + right: parent.right; + } + + Image { + id: lowerKeyboardButton; + source: "images/lowerKeyboard.png"; + anchors.horizontalCenter: parent.horizontalCenter; + anchors.bottom: keyboard.top; + height: 30; + width: 120; + + MouseArea { + anchors.fill: parent; + + onClicked: { + root.keyboardRaised = false; + } + } + } + + HifiControlsUit.Keyboard { + id: keyboard; + raised: HMD.mounted && root.keyboardRaised; + numeric: parent.punctuationMode; + anchors { + bottom: parent.bottom; + left: parent.left; + right: parent.right; + } + } + } // // FUNCTION DEFINITIONS START diff --git a/interface/resources/qml/hifi/commerce/wallet/WalletHome.qml b/interface/resources/qml/hifi/commerce/wallet/WalletHome.qml index 88f939d393..b55f7f800a 100644 --- a/interface/resources/qml/hifi/commerce/wallet/WalletHome.qml +++ b/interface/resources/qml/hifi/commerce/wallet/WalletHome.qml @@ -24,6 +24,7 @@ Item { HifiConstants { id: hifi; } id: root; + property bool historyReceived: false; Hifi.QmlCommerce { id: commerce; @@ -39,6 +40,14 @@ Item { onBalanceResult : { balanceText.text = parseFloat(result.data.balance/100).toFixed(2); } + + onHistoryResult : { + historyReceived = true; + if (result.status === 'success') { + transactionHistoryModel.clear(); + transactionHistoryModel.append(result.data.history); + } + } } SecurityImageModel { @@ -104,7 +113,9 @@ Item { onVisibleChanged: { if (visible) { + historyReceived = false; commerce.balance(); + commerce.history(); } } } @@ -174,9 +185,19 @@ Item { cache: false; source: "image://security/securityImage"; } - // "Security picture" text below pic + Image { + id: securityImageOverlay; + source: "images/lockOverlay.png"; + width: securityImage.width * 0.45; + height: securityImage.height * 0.45; + anchors.bottom: securityImage.bottom; + anchors.right: securityImage.right; + mipmap: true; + opacity: 0.9; + } + // "Security image" text below pic RalewayRegular { - text: "security picture"; + text: "security image"; // Text size size: 12; // Anchors @@ -200,8 +221,7 @@ Item { anchors.topMargin: 8; anchors.left: parent.left; anchors.right: parent.right; - anchors.bottom: homeMessage.visible ? homeMessage.top : root.bottom; - anchors.bottomMargin: 10; + anchors.bottom: parent.bottom; RalewayRegular { id: recentActivityText; @@ -216,116 +236,68 @@ Item { // Style color: hifi.colors.faintGray; } - + ListModel { + id: transactionHistoryModel; + } Rectangle { - id: transactionHistory; anchors.top: recentActivityText.bottom; anchors.topMargin: 4; - anchors.bottom: toggleFullHistoryButton.top; - anchors.bottomMargin: 8; + anchors.bottom: parent.bottom; anchors.left: parent.left; anchors.right: parent.right; + color: "white"; - // some placeholder stuff + ListView { + id: transactionHistory; + anchors.centerIn: parent; + width: parent.width - 12; + height: parent.height - 12; + visible: transactionHistoryModel.count !== 0; + clip: true; + model: transactionHistoryModel; + delegate: Item { + width: parent.width; + height: transactionText.height + 30; + RalewayRegular { + id: transactionText; + text: model.text; + // Style + size: 18; + width: parent.width; + height: paintedHeight; + anchors.verticalCenter: parent.verticalCenter; + color: "black"; + wrapMode: Text.WordWrap; + // Alignment + horizontalAlignment: Text.AlignLeft; + verticalAlignment: Text.AlignVCenter; + } + + HifiControlsUit.Separator { + anchors.left: parent.left; + anchors.right: parent.right; + anchors.bottom: parent.bottom; + } + } + onAtYEndChanged: { + if (transactionHistory.atYEnd) { + console.log("User scrolled to the bottom of 'Recent Activity'."); + // Grab next page of results and append to model + } + } + } + + // This should never be visible (since you immediately get 100 HFC) RalewayRegular { - text: homeMessage.visible ? "you CANNOT scroll through this." : "you CAN scroll through this"; - // Text size - size: 16; - // Anchors + id: emptyTransationHistory; + size: 24; + visible: !transactionHistory.visible && root.historyReceived; + text: "Recent Activity Unavailable"; anchors.fill: parent; - // Style - color: hifi.colors.darkGray; - // Alignment horizontalAlignment: Text.AlignHCenter; verticalAlignment: Text.AlignVCenter; } } - - HifiControlsUit.Button { - id: toggleFullHistoryButton; - color: hifi.buttons.black; - colorScheme: hifi.colorSchemes.dark; - anchors.bottom: parent.bottom; - anchors.right: parent.right; - width: 250; - height: 40; - text: homeMessage.visible ? "See Full Transaction History" : "Collapse Transaction History"; - onClicked: { - if (homeMessage.visible) { - homeMessage.visible = false; - } else { - homeMessage.visible = true; - } - } - } - } - - // Item for "messages" - like "Welcome" - Item { - id: homeMessage; - anchors.bottom: parent.bottom; - anchors.left: parent.left; - anchors.leftMargin: 20; - anchors.right: parent.right; - anchors.rightMargin: 20; - height: childrenRect.height; - - RalewayRegular { - id: messageText; - text: "Welcome! Let's get you some spending money.

" + - "Now that your account is all set up, click the button below to request your starter money. " + - "A robot will promptly review your request and put money into your account."; - // Text size - size: 16; - // Anchors - anchors.top: parent.top; - anchors.left: parent.left; - anchors.right: parent.right; - height: 130; - // Style - color: hifi.colors.faintGray; - wrapMode: Text.WordWrap; - // Alignment - horizontalAlignment: Text.AlignHLeft; - verticalAlignment: Text.AlignVCenter; - } - - Item { - id: homeMessageButtons; - anchors.top: messageText.bottom; - anchors.topMargin: 4; - anchors.left: parent.left; - anchors.right: parent.right; - height: 40; - HifiControlsUit.Button { - id: noThanksButton; - color: hifi.buttons.black; - colorScheme: hifi.colorSchemes.dark; - anchors.top: parent.top; - anchors.bottom: parent.bottom; - anchors.left: parent.left; - width: 100; - text: "No Thanks" - onClicked: { - messageText.text = "Okay...weird. Who doesn't like free money? If you change your mind, too bad. Sorry." - homeMessageButtons.visible = false; - } - } - HifiControlsUit.Button { - id: freeMoneyButton; - color: hifi.buttons.black; - colorScheme: hifi.colorSchemes.dark; - anchors.top: parent.top; - anchors.bottom: parent.bottom; - anchors.right: parent.right; - width: 210; - text: "Free Money Please" - onClicked: { - messageText.text = "Go, MoneyRobots, Go!" - homeMessageButtons.visible = false; - } - } - } } // @@ -351,6 +323,7 @@ Item { } } signal sendSignalToWallet(var msg); + // // FUNCTION DEFINITIONS END // diff --git a/interface/resources/qml/hifi/commerce/wallet/WalletSetupLightbox.qml b/interface/resources/qml/hifi/commerce/wallet/WalletSetupLightbox.qml index bbeb77f6fa..4470ec7a75 100644 --- a/interface/resources/qml/hifi/commerce/wallet/WalletSetupLightbox.qml +++ b/interface/resources/qml/hifi/commerce/wallet/WalletSetupLightbox.qml @@ -24,21 +24,13 @@ Rectangle { HifiConstants { id: hifi; } id: root; - property string lastPage: "login"; + property string lastPage: "initialize"; // Style color: hifi.colors.baseGray; Hifi.QmlCommerce { id: commerce; - onLoginStatusResult: { - if (isLoggedIn) { - securityImageContainer.visible = true; - } else { - loginPageContainer.visible = true; - } - } - onSecurityImageResult: { if (!exists && root.lastPage === "securityImage") { // ERROR! Invalid security image. @@ -61,151 +53,14 @@ Rectangle { } } - // - // LOGIN PAGE START - // - Item { - id: loginPageContainer; - visible: false; - // Anchors - anchors.fill: parent; - - Component.onCompleted: { - commerce.getLoginStatus(); - } - - Item { - id: loginTitle; - // Size - width: parent.width; - height: 50; - // Anchors - anchors.left: parent.left; - anchors.top: parent.top; - - // Title Bar text - RalewaySemiBold { - text: "WALLET SETUP - LOGIN"; - // Text size - size: hifi.fontSizes.overlayTitle; - // Anchors - anchors.top: parent.top; - anchors.left: parent.left; - anchors.leftMargin: 16; - anchors.bottom: parent.bottom; - width: paintedWidth; - // Style - color: hifi.colors.faintGray; - // Alignment - horizontalAlignment: Text.AlignHLeft; - verticalAlignment: Text.AlignVCenter; - } - } - - // Text below title bar - RalewaySemiBold { - id: loginTitleHelper; - text: "Please Log In to High Fidelity"; - // Text size - size: 24; - // Anchors - anchors.top: loginTitle.bottom; - anchors.left: parent.left; - anchors.leftMargin: 16; - anchors.right: parent.right; - anchors.rightMargin: 16; - height: 50; - // Style - color: hifi.colors.faintGray; - // Alignment - horizontalAlignment: Text.AlignHLeft; - verticalAlignment: Text.AlignVCenter; - } - - // Text below helper text - RalewaySemiBold { - id: loginDetailText; - text: "To set up your wallet, you must first log in to High Fidelity."; - // Text size - size: 18; - // Anchors - anchors.top: loginTitleHelper.bottom; - anchors.topMargin: 25; - anchors.left: parent.left; - anchors.leftMargin: 16; - anchors.right: parent.right; - anchors.rightMargin: 16; - height: 50; - // Style - color: hifi.colors.faintGray; - wrapMode: Text.WordWrap; - // Alignment - horizontalAlignment: Text.AlignHLeft; - verticalAlignment: Text.AlignVCenter; - } - - // "Cancel" button - HifiControlsUit.Button { - color: hifi.buttons.black; - colorScheme: hifi.colorSchemes.dark; - anchors.top: loginDetailText.bottom; - anchors.topMargin: 25; - anchors.left: parent.left; - anchors.leftMargin: 16; - width: 150; - height: 50; - text: "Log In" - onClicked: { - sendSignalToWallet({method: 'walletSetup_loginClicked'}); - } - } - - // Navigation Bar - Item { - // Size - width: parent.width; - height: 100; - // Anchors: - anchors.left: parent.left; - anchors.bottom: parent.bottom; - - // "Cancel" button - HifiControlsUit.Button { - 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: 100; - text: "Cancel" - onClicked: { - sendSignalToWallet({method: 'walletSetup_cancelClicked'}); - } - } - } - } - // - // LOGIN PAGE END - // - // // SECURITY IMAGE SELECTION START // Item { id: securityImageContainer; - visible: false; // Anchors anchors.fill: parent; - onVisibleChanged: { - if (visible) { - commerce.getSecurityImage(); - } - } - Item { id: securityImageTitle; // Size @@ -262,6 +117,12 @@ Rectangle { anchors.right: parent.right; anchors.rightMargin: 16; height: 280; + + Connections { + onSendSignalToWallet: { + sendSignalToWallet(msg); + } + } } // Text below security images @@ -329,6 +190,7 @@ Rectangle { commerce.chooseSecurityImage(securityImagePath); securityImageContainer.visible = false; choosePassphraseContainer.visible = true; + passphraseSelection.clearPassphraseFields(); } } } @@ -407,6 +269,15 @@ Rectangle { anchors.left: parent.left; anchors.right: parent.right; anchors.bottom: passphraseNavBar.top; + + Connections { + onSendMessageToLightbox: { + if (msg.method === 'statusResult') { + } else { + sendSignalToWallet(msg); + } + } + } } // Navigation Bar diff --git a/interface/resources/qml/hifi/commerce/images/01cat.jpg b/interface/resources/qml/hifi/commerce/wallet/images/01.jpg similarity index 100% rename from interface/resources/qml/hifi/commerce/images/01cat.jpg rename to interface/resources/qml/hifi/commerce/wallet/images/01.jpg diff --git a/interface/resources/qml/hifi/commerce/wallet/images/01cat.jpg b/interface/resources/qml/hifi/commerce/wallet/images/01cat.jpg deleted file mode 100644 index 6e7897cb82..0000000000 Binary files a/interface/resources/qml/hifi/commerce/wallet/images/01cat.jpg and /dev/null differ diff --git a/interface/resources/qml/hifi/commerce/wallet/images/02.jpg b/interface/resources/qml/hifi/commerce/wallet/images/02.jpg new file mode 100644 index 0000000000..e210d0dc82 Binary files /dev/null and b/interface/resources/qml/hifi/commerce/wallet/images/02.jpg differ diff --git a/interface/resources/qml/hifi/commerce/wallet/images/02car.jpg b/interface/resources/qml/hifi/commerce/wallet/images/02car.jpg deleted file mode 100644 index 5dd8091e57..0000000000 Binary files a/interface/resources/qml/hifi/commerce/wallet/images/02car.jpg and /dev/null differ diff --git a/interface/resources/qml/hifi/commerce/images/03dog.jpg b/interface/resources/qml/hifi/commerce/wallet/images/03.jpg similarity index 100% rename from interface/resources/qml/hifi/commerce/images/03dog.jpg rename to interface/resources/qml/hifi/commerce/wallet/images/03.jpg diff --git a/interface/resources/qml/hifi/commerce/wallet/images/03dog.jpg b/interface/resources/qml/hifi/commerce/wallet/images/03dog.jpg deleted file mode 100644 index 4a85b80c0c..0000000000 Binary files a/interface/resources/qml/hifi/commerce/wallet/images/03dog.jpg and /dev/null differ diff --git a/interface/resources/qml/hifi/commerce/wallet/images/04.jpg b/interface/resources/qml/hifi/commerce/wallet/images/04.jpg new file mode 100644 index 0000000000..e2358b4dbc Binary files /dev/null and b/interface/resources/qml/hifi/commerce/wallet/images/04.jpg differ diff --git a/interface/resources/qml/hifi/commerce/wallet/images/04stars.jpg b/interface/resources/qml/hifi/commerce/wallet/images/04stars.jpg deleted file mode 100644 index 8f2bf62f83..0000000000 Binary files a/interface/resources/qml/hifi/commerce/wallet/images/04stars.jpg and /dev/null differ diff --git a/interface/resources/qml/hifi/commerce/wallet/images/05.jpg b/interface/resources/qml/hifi/commerce/wallet/images/05.jpg new file mode 100644 index 0000000000..796acac167 Binary files /dev/null and b/interface/resources/qml/hifi/commerce/wallet/images/05.jpg differ diff --git a/interface/resources/qml/hifi/commerce/wallet/images/05plane.jpg b/interface/resources/qml/hifi/commerce/wallet/images/05plane.jpg deleted file mode 100644 index 6504459d8b..0000000000 Binary files a/interface/resources/qml/hifi/commerce/wallet/images/05plane.jpg and /dev/null differ diff --git a/interface/resources/qml/hifi/commerce/images/06gingerbread.jpg b/interface/resources/qml/hifi/commerce/wallet/images/06.jpg similarity index 100% rename from interface/resources/qml/hifi/commerce/images/06gingerbread.jpg rename to interface/resources/qml/hifi/commerce/wallet/images/06.jpg diff --git a/interface/resources/qml/hifi/commerce/wallet/images/06gingerbread.jpg b/interface/resources/qml/hifi/commerce/wallet/images/06gingerbread.jpg deleted file mode 100644 index 54c37faa2f..0000000000 Binary files a/interface/resources/qml/hifi/commerce/wallet/images/06gingerbread.jpg and /dev/null differ diff --git a/interface/resources/qml/hifi/commerce/wallet/images/lockOverlay.png b/interface/resources/qml/hifi/commerce/wallet/images/lockOverlay.png new file mode 100644 index 0000000000..09b2011e58 Binary files /dev/null and b/interface/resources/qml/hifi/commerce/wallet/images/lockOverlay.png differ diff --git a/interface/resources/qml/hifi/commerce/wallet/images/lowerKeyboard.png b/interface/resources/qml/hifi/commerce/wallet/images/lowerKeyboard.png new file mode 100644 index 0000000000..9fc88262db Binary files /dev/null and b/interface/resources/qml/hifi/commerce/wallet/images/lowerKeyboard.png differ diff --git a/interface/resources/qml/hifi/tablet/TabletMenuItem.qml b/interface/resources/qml/hifi/tablet/TabletMenuItem.qml index 25f672e7a9..71e59e0d01 100644 --- a/interface/resources/qml/hifi/tablet/TabletMenuItem.qml +++ b/interface/resources/qml/hifi/tablet/TabletMenuItem.qml @@ -26,24 +26,48 @@ Item { visible: source.visible width: parent.width - CheckBox { + Item { id: check - // FIXME: Should use radio buttons if source.exclusiveGroup. + anchors { left: parent.left leftMargin: hifi.dimensions.menuPadding.x + 15 verticalCenter: label.verticalCenter } - width: 20 - visible: source.visible && source.type === 1 && source.checkable - checked: setChecked() - function setChecked() { - if (!source || source.type !== 1 || !source.checkable) { - return false; + + width: checkbox.visible ? checkbox.width : radiobutton.width + height: checkbox.visible ? checkbox.height : radiobutton.height + + CheckBox { + id: checkbox + // FIXME: Should use radio buttons if source.exclusiveGroup. + width: 20 + visible: source.visible && source.type === 1 && source.checkable && !source.exclusiveGroup + checked: setChecked() + function setChecked() { + if (!source || source.type !== 1 || !source.checkable) { + return false; + } + // FIXME this works for native QML menus but I don't think it will + // for proxied QML menus + return source.checked; + } + } + + RadioButton { + id: radiobutton + // FIXME: Should use radio buttons if source.exclusiveGroup. + width: 20 + visible: source.visible && source.type === 1 && source.checkable && source.exclusiveGroup + checked: setChecked() + function setChecked() { + if (!source || source.type !== 1 || !source.checkable) { + return false; + } + // FIXME this works for native QML menus but I don't think it will + // for proxied QML menus + return source.checked; } - // FIXME this works for native QML menus but I don't think it will - // for proxied QML menus - return source.checked; } } diff --git a/interface/resources/qml/hifi/tablet/TabletMenuStack.qml b/interface/resources/qml/hifi/tablet/TabletMenuStack.qml index 9076cd6c48..e7eefbc5e7 100644 --- a/interface/resources/qml/hifi/tablet/TabletMenuStack.qml +++ b/interface/resources/qml/hifi/tablet/TabletMenuStack.qml @@ -64,8 +64,10 @@ Item { d.pop(); } - function toModel(items) { + function toModel(items, newMenu) { var result = modelMaker.createObject(tabletMenu); + var exclusionGroups = {}; + for (var i = 0; i < items.length; ++i) { var item = items[i]; if (!item.visible) continue; @@ -77,6 +79,28 @@ Item { if (item.text !== "Users Online") { result.append({"name": item.text, "item": item}) } + + for(var j = 0; j < tabletMenu.rootMenu.exclusionGroupsByMenuItem.count; ++j) + { + var entry = tabletMenu.rootMenu.exclusionGroupsByMenuItem.get(j); + if(entry.menuItem == item.toString()) + { + var exclusionGroupId = entry.exclusionGroup; + console.debug('item exclusionGroupId: ', exclusionGroupId) + + if(!exclusionGroups[exclusionGroupId]) + { + exclusionGroups[exclusionGroupId] = exclusiveGroupMaker.createObject(newMenu); + console.debug('new exclusion group created: ', exclusionGroups[exclusionGroupId]) + } + + var exclusionGroup = exclusionGroups[exclusionGroupId]; + + item.exclusiveGroup = exclusionGroup + console.debug('item.exclusiveGroup: ', item.exclusiveGroup) + } + } + break; case MenuItemType.Separator: result.append({"name": "", "item": item}) @@ -133,10 +157,21 @@ Item { } } + property Component exclusiveGroupMaker: Component { + ExclusiveGroup { + } + } + function buildMenu(items) { - var model = toModel(items); // Menus must be childed to desktop for Z-ordering - var newMenu = menuViewMaker.createObject(tabletMenu, { model: model, isSubMenu: topMenu !== null }); + var newMenu = menuViewMaker.createObject(tabletMenu); + console.debug('newMenu created: ', newMenu) + + var model = toModel(items, newMenu); + + newMenu.model = model; + newMenu.isSubMenu = topMenu !== null; + pushMenu(newMenu); return newMenu; } diff --git a/interface/src/Application.cpp b/interface/src/Application.cpp index ba68e68123..88003bb10f 100644 --- a/interface/src/Application.cpp +++ b/interface/src/Application.cpp @@ -184,7 +184,6 @@ #include "ui/UpdateDialog.h" #include "ui/overlays/Overlays.h" #include "ui/DomainConnectionModel.h" -#include "ui/ImageProvider.h" #include "Util.h" #include "InterfaceParentFinder.h" #include "ui/OctreeStatsProvider.h" @@ -265,7 +264,7 @@ private: switch ((int)event->type()) { case ApplicationEvent::Render: render(); - // Ensure we never back up the render events. Each render should be triggered only in response + // Ensure we never back up the render events. Each render should be triggered only in response // to the NEXT render event after the last render occured QCoreApplication::removePostedEvents(this, ApplicationEvent::Render); return true; @@ -1028,6 +1027,9 @@ Application::Application(int& argc, char** argv, QElapsedTimer& startupTimer, bo _glWidget->setFocusPolicy(Qt::StrongFocus); _glWidget->setFocus(); + if (cmdOptionExists(argc, constArgv, "--system-cursor")) { + _preferredCursor.set(Cursor::Manager::getIconName(Cursor::Icon::SYSTEM)); + } showCursor(Cursor::Manager::lookupIcon(_preferredCursor.get())); // enable mouse tracking; otherwise, we only get drag events @@ -2245,8 +2247,6 @@ void Application::initializeUi() { qApp->quit(); }); - // register the pixmap image provider (used only for security image, for now) - engine->addImageProvider(ImageProvider::PROVIDER_NAME, new ImageProvider()); setupPreferences(); @@ -4550,10 +4550,13 @@ void Application::updateMyAvatarLookAtPosition() { } } else { AvatarSharedPointer lookingAt = myAvatar->getLookAtTargetAvatar().lock(); - if (lookingAt && myAvatar.get() != lookingAt.get()) { + bool haveLookAtCandidate = lookingAt && myAvatar.get() != lookingAt.get(); + auto avatar = static_pointer_cast(lookingAt); + bool mutualLookAtSnappingEnabled = avatar && avatar->getLookAtSnappingEnabled() && myAvatar->getLookAtSnappingEnabled(); + if (haveLookAtCandidate && mutualLookAtSnappingEnabled) { // If I am looking at someone else, look directly at one of their eyes isLookingAtSomeone = true; - auto lookingAtHead = static_pointer_cast(lookingAt)->getHead(); + auto lookingAtHead = avatar->getHead(); const float MAXIMUM_FACE_ANGLE = 65.0f * RADIANS_PER_DEGREE; glm::vec3 lookingAtFaceOrientation = lookingAtHead->getFinalOrientationInWorldFrame() * IDENTITY_FORWARD; @@ -5167,7 +5170,7 @@ void Application::update(float deltaTime) { } } else { // update the rendering without any simulation - getEntities()->update(false); + getEntities()->update(false); } // AvatarManager update diff --git a/interface/src/Menu.cpp b/interface/src/Menu.cpp index 2c4a515736..005d478411 100644 --- a/interface/src/Menu.cpp +++ b/interface/src/Menu.cpp @@ -56,6 +56,8 @@ Menu* Menu::getInstance() { return dynamic_cast(qApp->getWindow()->menuBar()); } +const char* exclusionGroupKey = "exclusionGroup"; + Menu::Menu() { auto dialogsManager = DependencyManager::get(); auto accountManager = DependencyManager::get(); @@ -222,32 +224,42 @@ Menu::Menu() { cameraModeGroup->setExclusive(true); // View > First Person - cameraModeGroup->addAction(addCheckableActionToQMenuAndActionHash( + auto firstPersonAction = cameraModeGroup->addAction(addCheckableActionToQMenuAndActionHash( viewMenu, MenuOption::FirstPerson, Qt::CTRL | Qt::Key_F, true, qApp, SLOT(cameraMenuChanged()))); + firstPersonAction->setProperty(exclusionGroupKey, QVariant::fromValue(cameraModeGroup)); + // View > Third Person - cameraModeGroup->addAction(addCheckableActionToQMenuAndActionHash( + auto thirdPersonAction = cameraModeGroup->addAction(addCheckableActionToQMenuAndActionHash( viewMenu, MenuOption::ThirdPerson, Qt::CTRL | Qt::Key_G, false, qApp, SLOT(cameraMenuChanged()))); + thirdPersonAction->setProperty(exclusionGroupKey, QVariant::fromValue(cameraModeGroup)); + // View > Mirror - cameraModeGroup->addAction(addCheckableActionToQMenuAndActionHash( + auto viewMirrorAction = cameraModeGroup->addAction(addCheckableActionToQMenuAndActionHash( viewMenu, MenuOption::FullscreenMirror, Qt::CTRL | Qt::Key_H, false, qApp, SLOT(cameraMenuChanged()))); + viewMirrorAction->setProperty(exclusionGroupKey, QVariant::fromValue(cameraModeGroup)); + // View > Independent [advanced] - cameraModeGroup->addAction(addCheckableActionToQMenuAndActionHash(viewMenu, + auto viewIndependentAction = cameraModeGroup->addAction(addCheckableActionToQMenuAndActionHash(viewMenu, MenuOption::IndependentMode, 0, false, qApp, SLOT(cameraMenuChanged()), UNSPECIFIED_POSITION, "Advanced")); + viewIndependentAction->setProperty(exclusionGroupKey, QVariant::fromValue(cameraModeGroup)); + // View > Entity Camera [advanced] - cameraModeGroup->addAction(addCheckableActionToQMenuAndActionHash(viewMenu, + auto viewEntityCameraAction = cameraModeGroup->addAction(addCheckableActionToQMenuAndActionHash(viewMenu, MenuOption::CameraEntityMode, 0, false, qApp, SLOT(cameraMenuChanged()), UNSPECIFIED_POSITION, "Advanced")); + viewEntityCameraAction->setProperty(exclusionGroupKey, QVariant::fromValue(cameraModeGroup)); + viewMenu->addSeparator(); // View > Center Player In View @@ -532,6 +544,11 @@ Menu::Menu() { action = addCheckableActionToQMenuAndActionHash(avatarDebugMenu, MenuOption::ShowOtherLookAtVectors, 0, false); connect(action, &QAction::triggered, [this]{ Avatar::setShowOtherLookAtVectors(isOptionChecked(MenuOption::ShowOtherLookAtVectors)); }); + action = addCheckableActionToQMenuAndActionHash(avatarDebugMenu, MenuOption::EnableLookAtSnapping, 0, true); + connect(action, &QAction::triggered, [this, avatar]{ + avatar->setProperty("lookAtSnappingEnabled", isOptionChecked(MenuOption::EnableLookAtSnapping)); + }); + addCheckableActionToQMenuAndActionHash(avatarDebugMenu, MenuOption::FixGaze, 0, false); addCheckableActionToQMenuAndActionHash(avatarDebugMenu, MenuOption::AnimDebugDrawDefaultPose, 0, false, avatar.get(), SLOT(setEnableDebugDrawDefaultPose(bool))); diff --git a/interface/src/Menu.h b/interface/src/Menu.h index 4e21cfa4ac..d77c87a6fc 100644 --- a/interface/src/Menu.h +++ b/interface/src/Menu.h @@ -177,6 +177,7 @@ namespace MenuOption { const QString ShowDSConnectTable = "Show Domain Connection Timing"; const QString ShowMyLookAtVectors = "Show My Eye Vectors"; const QString ShowOtherLookAtVectors = "Show Other Eye Vectors"; + const QString EnableLookAtSnapping = "Enable LookAt Snapping"; const QString ShowRealtimeEntityStats = "Show Realtime Entity Stats"; const QString StandingHMDSensorMode = "Standing HMD Sensor Mode"; const QString SimulateEyeTracking = "Simulate"; diff --git a/interface/src/avatar/MyAvatar.cpp b/interface/src/avatar/MyAvatar.cpp index 76535930bc..b69b8c79df 100755 --- a/interface/src/avatar/MyAvatar.cpp +++ b/interface/src/avatar/MyAvatar.cpp @@ -939,6 +939,9 @@ void MyAvatar::saveData() { settings.setValue("scale", _targetScale); + settings.setValue("yawSpeed", _yawSpeed); + settings.setValue("pitchSpeed", _pitchSpeed); + // only save the fullAvatarURL if it has not been overwritten on command line // (so the overrideURL is not valid), or it was overridden _and_ we specified // --replaceAvatarURL (so _saveAvatarOverrideUrl is true) @@ -1088,6 +1091,9 @@ void MyAvatar::loadData() { getHead()->setBasePitch(loadSetting(settings, "headPitch", 0.0f)); + _yawSpeed = loadSetting(settings, "yawSpeed", _yawSpeed); + _pitchSpeed = loadSetting(settings, "pitchSpeed", _pitchSpeed); + _prefOverrideAnimGraphUrl.set(QUrl(settings.value("animGraphURL", "").toString())); _fullAvatarURLFromPreferences = settings.value("fullAvatarURL", AvatarData::defaultFullAvatarModelUrl()).toUrl(); _fullAvatarModelName = settings.value("fullAvatarModelName", DEFAULT_FULL_AVATAR_MODEL_NAME).toString(); @@ -1210,6 +1216,15 @@ int MyAvatar::parseDataFromBuffer(const QByteArray& buffer) { return buffer.size(); } +ScriptAvatarData* MyAvatar::getTargetAvatar() const { + auto avatar = std::static_pointer_cast(_lookAtTargetAvatar.lock()); + if (avatar) { + return new ScriptAvatar(avatar); + } else { + return nullptr; + } +} + void MyAvatar::updateLookAtTargetAvatar() { // // Look at the avatar whose eyes are closest to the ray in direction of my avatar's head @@ -1238,9 +1253,8 @@ void MyAvatar::updateLookAtTargetAvatar() { if (angleTo < (smallestAngleTo * (isCurrentTarget ? KEEP_LOOKING_AT_CURRENT_ANGLE_FACTOR : 1.0f))) { _lookAtTargetAvatar = avatarPointer; _targetAvatarPosition = avatarPointer->getPosition(); - smallestAngleTo = angleTo; } - if (isLookingAtMe(avatar)) { + if (_lookAtSnappingEnabled && avatar->getLookAtSnappingEnabled() && isLookingAtMe(avatar)) { // Alter their gaze to look directly at my camera; this looks more natural than looking at my avatar's face. glm::vec3 lookAtPosition = avatar->getHead()->getLookAtPosition(); // A position, in world space, on my avatar. @@ -1257,14 +1271,19 @@ void MyAvatar::updateLookAtTargetAvatar() { ViewFrustum viewFrustum; qApp->copyViewFrustum(viewFrustum); + glm::vec3 viewPosition = viewFrustum.getPosition(); +#if DEBUG_ALWAYS_LOOKAT_EYES_NOT_CAMERA + viewPosition = (avatarLeftEye + avatarRightEye) / 2.0f; +#endif // scale gazeOffset by IPD, if wearing an HMD. if (qApp->isHMDMode()) { + glm::quat viewOrientation = viewFrustum.getOrientation(); glm::mat4 leftEye = qApp->getEyeOffset(Eye::Left); glm::mat4 rightEye = qApp->getEyeOffset(Eye::Right); glm::vec3 leftEyeHeadLocal = glm::vec3(leftEye[3]); glm::vec3 rightEyeHeadLocal = glm::vec3(rightEye[3]); - glm::vec3 humanLeftEye = viewFrustum.getPosition() + (viewFrustum.getOrientation() * leftEyeHeadLocal); - glm::vec3 humanRightEye = viewFrustum.getPosition() + (viewFrustum.getOrientation() * rightEyeHeadLocal); + glm::vec3 humanLeftEye = viewPosition + (viewOrientation * leftEyeHeadLocal); + glm::vec3 humanRightEye = viewPosition + (viewOrientation * rightEyeHeadLocal); auto hmdInterface = DependencyManager::get(); float ipdScale = hmdInterface->getIPDScale(); @@ -1278,7 +1297,7 @@ void MyAvatar::updateLookAtTargetAvatar() { } // And now we can finally add that offset to the camera. - glm::vec3 corrected = viewFrustum.getPosition() + gazeOffset; + glm::vec3 corrected = viewPosition + gazeOffset; avatar->getHead()->setCorrectedLookAtPosition(corrected); @@ -2526,6 +2545,7 @@ void MyAvatar::updateMotionBehaviorFromMenu() { _motionBehaviors &= ~AVATAR_MOTION_SCRIPTED_MOTOR_ENABLED; } setCollisionsEnabled(menu->isOptionChecked(MenuOption::EnableAvatarCollisions)); + setProperty("lookAtSnappingEnabled", menu->isOptionChecked(MenuOption::EnableLookAtSnapping)); } void MyAvatar::setFlyingEnabled(bool enabled) { diff --git a/interface/src/avatar/MyAvatar.h b/interface/src/avatar/MyAvatar.h index 148a946ab4..65dcc12e7d 100644 --- a/interface/src/avatar/MyAvatar.h +++ b/interface/src/avatar/MyAvatar.h @@ -23,6 +23,7 @@ #include #include #include +#include #include "AtRestDetector.h" #include "MyCharacterController.h" @@ -138,6 +139,9 @@ class MyAvatar : public Avatar { Q_PROPERTY(bool characterControllerEnabled READ getCharacterControllerEnabled WRITE setCharacterControllerEnabled) Q_PROPERTY(bool useAdvancedMovementControls READ useAdvancedMovementControls WRITE setUseAdvancedMovementControls) + Q_PROPERTY(float yawSpeed MEMBER _yawSpeed) + Q_PROPERTY(float pitchSpeed MEMBER _pitchSpeed) + Q_PROPERTY(bool hmdRollControlEnabled READ getHMDRollControlEnabled WRITE setHMDRollControlEnabled) Q_PROPERTY(float hmdRollControlDeadZone READ getHMDRollControlDeadZone WRITE setHMDRollControlDeadZone) Q_PROPERTY(float hmdRollControlRate READ getHMDRollControlRate WRITE setHMDRollControlRate) @@ -390,6 +394,7 @@ public: Q_INVOKABLE glm::vec3 getEyePosition() const { return getHead()->getEyePosition(); } Q_INVOKABLE glm::vec3 getTargetAvatarPosition() const { return _targetAvatarPosition; } + Q_INVOKABLE ScriptAvatarData* getTargetAvatar() const; Q_INVOKABLE glm::vec3 getLeftHandPosition() const; Q_INVOKABLE glm::vec3 getRightHandPosition() const; diff --git a/interface/src/commerce/Ledger.cpp b/interface/src/commerce/Ledger.cpp index 55ee0e16e9..dddfae6455 100644 --- a/interface/src/commerce/Ledger.cpp +++ b/interface/src/commerce/Ledger.cpp @@ -45,6 +45,7 @@ Handler(buy) Handler(receiveAt) Handler(balance) Handler(inventory) +Handler(history) void Ledger::send(const QString& endpoint, const QString& success, const QString& fail, QNetworkAccessManager::Operation method, QJsonObject request) { auto accountManager = DependencyManager::get(); @@ -107,3 +108,6 @@ void Ledger::inventory(const QStringList& keys) { keysQuery("inventory", "inventorySuccess", "inventoryFailure"); } +void Ledger::history(const QStringList& keys) { + keysQuery("history", "historySuccess", "historyFailure"); +} diff --git a/interface/src/commerce/Ledger.h b/interface/src/commerce/Ledger.h index e43b8453b9..7d3fdef0c0 100644 --- a/interface/src/commerce/Ledger.h +++ b/interface/src/commerce/Ledger.h @@ -28,12 +28,14 @@ public: bool receiveAt(const QString& hfc_key, const QString& old_key); void balance(const QStringList& keys); void inventory(const QStringList& keys); + void history(const QStringList& keys); signals: void buyResult(QJsonObject result); void receiveAtResult(QJsonObject result); void balanceResult(QJsonObject result); void inventoryResult(QJsonObject result); + void historyResult(QJsonObject result); public slots: void buySuccess(QNetworkReply& reply); @@ -44,6 +46,8 @@ public slots: void balanceFailure(QNetworkReply& reply); void inventorySuccess(QNetworkReply& reply); void inventoryFailure(QNetworkReply& reply); + void historySuccess(QNetworkReply& reply); + void historyFailure(QNetworkReply& reply); private: QJsonObject apiResponse(const QString& label, QNetworkReply& reply); diff --git a/interface/src/commerce/QmlCommerce.cpp b/interface/src/commerce/QmlCommerce.cpp index a5ae008a36..bf6bcc221c 100644 --- a/interface/src/commerce/QmlCommerce.cpp +++ b/interface/src/commerce/QmlCommerce.cpp @@ -25,6 +25,7 @@ QmlCommerce::QmlCommerce(QQuickItem* parent) : OffscreenQmlDialog(parent) { connect(ledger.data(), &Ledger::balanceResult, this, &QmlCommerce::balanceResult); connect(ledger.data(), &Ledger::inventoryResult, this, &QmlCommerce::inventoryResult); connect(wallet.data(), &Wallet::securityImageResult, this, &QmlCommerce::securityImageResult); + connect(ledger.data(), &Ledger::historyResult, this, &QmlCommerce::historyResult); connect(wallet.data(), &Wallet::keyFilePathIfExistsResult, this, &QmlCommerce::keyFilePathIfExistsResult); } @@ -46,26 +47,37 @@ void QmlCommerce::balance() { auto wallet = DependencyManager::get(); ledger->balance(wallet->listPublicKeys()); } + void QmlCommerce::inventory() { auto ledger = DependencyManager::get(); auto wallet = DependencyManager::get(); ledger->inventory(wallet->listPublicKeys()); } +void QmlCommerce::history() { + auto ledger = DependencyManager::get(); + auto wallet = DependencyManager::get(); + ledger->history(wallet->listPublicKeys()); +} + void QmlCommerce::chooseSecurityImage(const QString& imageFile) { auto wallet = DependencyManager::get(); wallet->chooseSecurityImage(imageFile); } + void QmlCommerce::getSecurityImage() { auto wallet = DependencyManager::get(); wallet->getSecurityImage(); } + void QmlCommerce::getLoginStatus() { emit loginStatusResult(DependencyManager::get()->isLoggedIn()); } + void QmlCommerce::setPassphrase(const QString& passphrase) { emit passphraseSetupStatusResult(true); } + void QmlCommerce::getPassphraseSetupStatus() { emit passphraseSetupStatusResult(false); } diff --git a/interface/src/commerce/QmlCommerce.h b/interface/src/commerce/QmlCommerce.h index 0c3ed64c81..fd913ae4b7 100644 --- a/interface/src/commerce/QmlCommerce.h +++ b/interface/src/commerce/QmlCommerce.h @@ -36,12 +36,14 @@ signals: void securityImageResult(bool exists); void loginStatusResult(bool isLoggedIn); void passphraseSetupStatusResult(bool passphraseIsSetup); + void historyResult(QJsonObject result); void keyFilePathIfExistsResult(const QString& path); protected: Q_INVOKABLE void buy(const QString& assetId, int cost, const QString& buyerUsername = ""); Q_INVOKABLE void balance(); Q_INVOKABLE void inventory(); + Q_INVOKABLE void history(); Q_INVOKABLE void chooseSecurityImage(const QString& imageFile); Q_INVOKABLE void getSecurityImage(); Q_INVOKABLE void getLoginStatus(); diff --git a/interface/src/commerce/Wallet.cpp b/interface/src/commerce/Wallet.cpp index 1d64733f49..4cbcb5f1d3 100644 --- a/interface/src/commerce/Wallet.cpp +++ b/interface/src/commerce/Wallet.cpp @@ -224,6 +224,12 @@ void initializeAESKeys(unsigned char* ivec, unsigned char* ckey, const QByteArra memcpy(ckey, hash.data(), 32); } +Wallet::~Wallet() { + if (_securityImage) { + delete _securityImage; + } +} + void Wallet::setPassphrase(const QString& passphrase) { if (_passphrase) { delete _passphrase; @@ -347,7 +353,7 @@ bool Wallet::createIfNeeded() { qCDebug(commerce) << "read private key"; RSA_free(key); // K -- add the public key since we have a legit private key associated with it - _publicKeys.push_back(QUrl::toPercentEncoding(publicKey.toBase64())); + _publicKeys.push_back(publicKey.toBase64()); return false; } } @@ -411,6 +417,13 @@ QString Wallet::signWithKey(const QByteArray& text, const QString& key) { return QString(); } +void Wallet::updateImageProvider() { + // inform the image provider. Note it doesn't matter which one you inform, as the + // images are statics + auto engine = DependencyManager::get()->getSurfaceContext()->engine(); + auto imageProvider = reinterpret_cast(engine->imageProvider(ImageProvider::PROVIDER_NAME)); + imageProvider->setSecurityImage(_securityImage); +} void Wallet::chooseSecurityImage(const QString& filename) { @@ -419,7 +432,7 @@ void Wallet::chooseSecurityImage(const QString& filename) { } // temporary... QString path = qApp->applicationDirPath(); - path.append("/resources/qml/hifi/commerce/"); + path.append("/resources/qml/hifi/commerce/wallet/"); path.append(filename); // now create a new security image pixmap _securityImage = new QPixmap(); @@ -431,10 +444,7 @@ void Wallet::chooseSecurityImage(const QString& filename) { if (encryptFile(path, imageFilePath())) { qCDebug(commerce) << "emitting pixmap"; - // inform the image provider - auto engine = DependencyManager::get()->getSurfaceContext()->engine(); - auto imageProvider = reinterpret_cast(engine->imageProvider(ImageProvider::PROVIDER_NAME)); - imageProvider->setSecurityImage(_securityImage); + updateImageProvider(); emit securityImageResult(true); } else { @@ -460,10 +470,7 @@ void Wallet::getSecurityImage() { _securityImage->loadFromData(data, dataLen, "jpg"); qCDebug(commerce) << "created pixmap from encrypted file"; - // inform the image provider - auto engine = DependencyManager::get()->getSurfaceContext()->engine(); - auto imageProvider = reinterpret_cast(engine->imageProvider(ImageProvider::PROVIDER_NAME)); - imageProvider->setSecurityImage(_securityImage); + updateImageProvider(); delete[] data; emit securityImageResult(true); diff --git a/interface/src/commerce/Wallet.h b/interface/src/commerce/Wallet.h index b13d4368d9..478a35625d 100644 --- a/interface/src/commerce/Wallet.h +++ b/interface/src/commerce/Wallet.h @@ -23,6 +23,8 @@ class Wallet : public QObject, public Dependency { SINGLETON_DEPENDENCY public: + + ~Wallet(); // These are currently blocking calls, although they might take a moment. bool createIfNeeded(); bool generateKeyPair(); @@ -42,23 +44,13 @@ signals: void securityImageResult(bool exists) ; void keyFilePathIfExistsResult(const QString& path); -protected: - enum SecurityImage { - NONE = 0, - Cat, - Car, - Dog, - Stars, - Plane, - Gingerbread - }; - private: QStringList _publicKeys{}; QPixmap* _securityImage { nullptr }; QByteArray _salt {"iamsalt!"}; QString* _passphrase { new QString("pwd") }; + void updateImageProvider(); bool encryptFile(const QString& inputFilePath, const QString& outputFilePath); bool decryptFile(const QString& inputFilePath, unsigned char** outputBufferPtr, int* outputBufferLen); }; diff --git a/interface/src/ui/overlays/ContextOverlayInterface.cpp b/interface/src/ui/overlays/ContextOverlayInterface.cpp index 898ce49e9e..34aae37175 100644 --- a/interface/src/ui/overlays/ContextOverlayInterface.cpp +++ b/interface/src/ui/overlays/ContextOverlayInterface.cpp @@ -13,6 +13,7 @@ #include "Application.h" #include +#include static const float CONTEXT_OVERLAY_TABLET_OFFSET = 30.0f; // Degrees static const float CONTEXT_OVERLAY_TABLET_ORIENTATION = 210.0f; // Degrees @@ -247,7 +248,7 @@ void ContextOverlayInterface::contextOverlays_hoverLeaveEntity(const EntityItemI } } -static const QString MARKETPLACE_BASE_URL = "https://metaverse.highfidelity.com/marketplace/items/"; +static const QString MARKETPLACE_BASE_URL = NetworkingConstants::METAVERSE_SERVER_URL.toString() + "/marketplace/items/"; void ContextOverlayInterface::openMarketplace() { // lets open the tablet and go to the current item in diff --git a/libraries/avatars-renderer/src/avatars-renderer/Avatar.cpp b/libraries/avatars-renderer/src/avatars-renderer/Avatar.cpp index 38ba60c977..7f4ebf39e1 100644 --- a/libraries/avatars-renderer/src/avatars-renderer/Avatar.cpp +++ b/libraries/avatars-renderer/src/avatars-renderer/Avatar.cpp @@ -557,8 +557,8 @@ void Avatar::postUpdate(float deltaTime) { if (isMyAvatar() ? showMyLookAtVectors : showOtherLookAtVectors) { const float EYE_RAY_LENGTH = 10.0; - const glm::vec4 BLUE(0.0f, 0.0f, 1.0f, 1.0f); - const glm::vec4 RED(1.0f, 0.0f, 0.0f, 1.0f); + const glm::vec4 BLUE(0.0f, 0.0f, _lookAtSnappingEnabled ? 1.0f : 0.25f, 1.0f); + const glm::vec4 RED(_lookAtSnappingEnabled ? 1.0f : 0.25f, 0.0f, 0.0f, 1.0f); int leftEyeJoint = getJointIndex("LeftEye"); glm::vec3 leftEyePosition; diff --git a/libraries/avatars/src/AvatarData.cpp b/libraries/avatars/src/AvatarData.cpp index 477755adf8..1baf649e64 100644 --- a/libraries/avatars/src/AvatarData.cpp +++ b/libraries/avatars/src/AvatarData.cpp @@ -91,6 +91,7 @@ AvatarData::AvatarData() : _targetVelocity(0.0f), _density(DEFAULT_AVATAR_DENSITY) { + connect(this, &AvatarData::lookAtSnappingChanged, this, &AvatarData::markIdentityDataChanged); } AvatarData::~AvatarData() { @@ -1446,7 +1447,9 @@ void AvatarData::processAvatarIdentity(const QByteArray& identityData, bool& ide >> identity.displayName >> identity.sessionDisplayName >> identity.isReplicated - >> identity.avatarEntityData; + >> identity.avatarEntityData + >> identity.lookAtSnappingEnabled + ; // set the store identity sequence number to match the incoming identity _identitySequenceNumber = incomingSequenceNumber; @@ -1488,6 +1491,11 @@ void AvatarData::processAvatarIdentity(const QByteArray& identityData, bool& ide identityChanged = true; } + if (identity.lookAtSnappingEnabled != _lookAtSnappingEnabled) { + setProperty("lookAtSnappingEnabled", identity.lookAtSnappingEnabled); + identityChanged = true; + } + #ifdef WANT_DEBUG qCDebug(avatars) << __FUNCTION__ << "identity.uuid:" << identity.uuid @@ -1519,7 +1527,9 @@ QByteArray AvatarData::identityByteArray(bool setIsReplicated) const { << _displayName << getSessionDisplayNameForTransport() // depends on _sessionDisplayName << (_isReplicated || setIsReplicated) - << _avatarEntityData; + << _avatarEntityData + << _lookAtSnappingEnabled + ; }); return identityData; diff --git a/libraries/avatars/src/AvatarData.h b/libraries/avatars/src/AvatarData.h index 78b1aa3633..b4c36dba70 100644 --- a/libraries/avatars/src/AvatarData.h +++ b/libraries/avatars/src/AvatarData.h @@ -353,6 +353,7 @@ class AvatarData : public QObject, public SpatiallyNestable { Q_PROPERTY(glm::vec3 position READ getPosition WRITE setPositionViaScript) Q_PROPERTY(float scale READ getTargetScale WRITE setTargetScale) + Q_PROPERTY(float density READ getDensity) Q_PROPERTY(glm::vec3 handPosition READ getHandPosition WRITE setHandPosition) Q_PROPERTY(float bodyYaw READ getBodyYaw WRITE setBodyYaw) Q_PROPERTY(float bodyPitch READ getBodyPitch WRITE setBodyPitch) @@ -374,6 +375,7 @@ class AvatarData : public QObject, public SpatiallyNestable { // sessionDisplayName is sanitized, defaulted version displayName that is defined by the AvatarMixer rather than by Interface clients. // The result is unique among all avatars present at the time. Q_PROPERTY(QString sessionDisplayName READ getSessionDisplayName WRITE setSessionDisplayName) + Q_PROPERTY(bool lookAtSnappingEnabled MEMBER _lookAtSnappingEnabled NOTIFY lookAtSnappingChanged) Q_PROPERTY(QString skeletonModelURL READ getSkeletonModelURLFromScript WRITE setSkeletonModelURLFromScript) Q_PROPERTY(QVector attachmentData READ getAttachmentData WRITE setAttachmentData) @@ -559,6 +561,7 @@ public: QString sessionDisplayName; bool isReplicated; AvatarEntityMap avatarEntityData; + bool lookAtSnappingEnabled; }; // identityChanged returns true if identity has changed, false otherwise. @@ -571,6 +574,7 @@ public: const QUrl& getSkeletonModelURL() const { return _skeletonModelURL; } const QString& getDisplayName() const { return _displayName; } const QString& getSessionDisplayName() const { return _sessionDisplayName; } + bool getLookAtSnappingEnabled() const { return _lookAtSnappingEnabled; } virtual void setSkeletonModelURL(const QUrl& skeletonModelURL); virtual void setDisplayName(const QString& displayName); @@ -662,6 +666,7 @@ public: signals: void displayNameChanged(); + void lookAtSnappingChanged(bool enabled); public slots: void sendAvatarDataPacket(); @@ -730,6 +735,7 @@ protected: QVector _attachmentData; QString _displayName; QString _sessionDisplayName { }; + bool _lookAtSnappingEnabled { true }; QHash _fstJointIndices; ///< 1-based, since zero is returned for missing keys QStringList _fstJointNames; ///< in order of depth-first traversal diff --git a/libraries/avatars/src/ScriptAvatarData.cpp b/libraries/avatars/src/ScriptAvatarData.cpp index 90ec7ec309..d0643d28f8 100644 --- a/libraries/avatars/src/ScriptAvatarData.cpp +++ b/libraries/avatars/src/ScriptAvatarData.cpp @@ -15,6 +15,7 @@ ScriptAvatarData::ScriptAvatarData(AvatarSharedPointer avatarData) : _avatarData(avatarData) { QObject::connect(avatarData.get(), &AvatarData::displayNameChanged, this, &ScriptAvatarData::displayNameChanged); + QObject::connect(avatarData.get(), &AvatarData::lookAtSnappingChanged, this, &ScriptAvatarData::lookAtSnappingChanged); } // @@ -161,6 +162,13 @@ bool ScriptAvatarData::getIsReplicated() const { } } +bool ScriptAvatarData::getLookAtSnappingEnabled() const { + if (AvatarSharedPointer sharedAvatarData = _avatarData.lock()) { + return sharedAvatarData->getLookAtSnappingEnabled(); + } else { + return false; + } +} // // IDENTIFIER PROPERTIES // END diff --git a/libraries/avatars/src/ScriptAvatarData.h b/libraries/avatars/src/ScriptAvatarData.h index 1b6944e01d..46dfb5325f 100644 --- a/libraries/avatars/src/ScriptAvatarData.h +++ b/libraries/avatars/src/ScriptAvatarData.h @@ -46,6 +46,7 @@ class ScriptAvatarData : public QObject { Q_PROPERTY(QString displayName READ getDisplayName NOTIFY displayNameChanged) Q_PROPERTY(QString sessionDisplayName READ getSessionDisplayName) Q_PROPERTY(bool isReplicated READ getIsReplicated) + Q_PROPERTY(bool lookAtSnappingEnabled READ getLookAtSnappingEnabled NOTIFY lookAtSnappingChanged) // // ATTACHMENT AND JOINT PROPERTIES @@ -97,6 +98,7 @@ public: QString getDisplayName() const; QString getSessionDisplayName() const; bool getIsReplicated() const; + bool getLookAtSnappingEnabled() const; // // ATTACHMENT AND JOINT PROPERTIES @@ -129,6 +131,7 @@ public: signals: void displayNameChanged(); + void lookAtSnappingChanged(bool enabled); public slots: glm::quat getAbsoluteJointRotationInObjectFrame(int index) const; diff --git a/libraries/display-plugins/src/display-plugins/CompositorHelper.cpp b/libraries/display-plugins/src/display-plugins/CompositorHelper.cpp index 4dc8d3378c..f09b3d7109 100644 --- a/libraries/display-plugins/src/display-plugins/CompositorHelper.cpp +++ b/libraries/display-plugins/src/display-plugins/CompositorHelper.cpp @@ -437,7 +437,7 @@ glm::mat4 CompositorHelper::getReticleTransform(const glm::mat4& eyePose, const mousePosition -= 1.0; mousePosition.y *= -1.0f; - vec2 mouseSize = CURSOR_PIXEL_SIZE / canvasSize; + vec2 mouseSize = CURSOR_PIXEL_SIZE * Cursor::Manager::instance().getScale() / canvasSize; result = glm::scale(glm::translate(glm::mat4(), vec3(mousePosition, 0.0f)), vec3(mouseSize, 1.0f)); } return result; @@ -451,3 +451,13 @@ QVariant ReticleInterface::getPosition() const { void ReticleInterface::setPosition(QVariant position) { _compositor->setReticlePosition(vec2FromVariant(position)); } + +float ReticleInterface::getScale() const { + auto& cursorManager = Cursor::Manager::instance(); + return cursorManager.getScale(); +} + +void ReticleInterface::setScale(float scale) { + auto& cursorManager = Cursor::Manager::instance(); + cursorManager.setScale(scale); +} diff --git a/libraries/display-plugins/src/display-plugins/CompositorHelper.h b/libraries/display-plugins/src/display-plugins/CompositorHelper.h index e6a32dcfb9..946229fc9b 100644 --- a/libraries/display-plugins/src/display-plugins/CompositorHelper.h +++ b/libraries/display-plugins/src/display-plugins/CompositorHelper.h @@ -176,6 +176,7 @@ class ReticleInterface : public QObject { Q_PROPERTY(QVariant position READ getPosition WRITE setPosition) Q_PROPERTY(bool visible READ getVisible WRITE setVisible) Q_PROPERTY(float depth READ getDepth WRITE setDepth) + Q_PROPERTY(float scale READ getScale WRITE setScale) Q_PROPERTY(glm::vec2 maximumPosition READ getMaximumPosition) Q_PROPERTY(bool mouseCaptured READ isMouseCaptured) Q_PROPERTY(bool allowMouseCapture READ getAllowMouseCapture WRITE setAllowMouseCapture) @@ -197,6 +198,9 @@ public: Q_INVOKABLE float getDepth() { return _compositor->getReticleDepth(); } Q_INVOKABLE void setDepth(float depth) { _compositor->setReticleDepth(depth); } + Q_INVOKABLE float getScale() const; + Q_INVOKABLE void setScale(float scale); + Q_INVOKABLE QVariant getPosition() const; Q_INVOKABLE void setPosition(QVariant position); diff --git a/libraries/entities-renderer/src/EntityTreeRenderer.cpp b/libraries/entities-renderer/src/EntityTreeRenderer.cpp index 113739d99b..9f4a9a3c4a 100644 --- a/libraries/entities-renderer/src/EntityTreeRenderer.cpp +++ b/libraries/entities-renderer/src/EntityTreeRenderer.cpp @@ -47,6 +47,11 @@ EntityTreeRenderer::EntityTreeRenderer(bool wantScripts, AbstractViewStateInterf _displayModelBounds(false), _layeredZones(this) { + setMouseRayPickResultOperator([](QUuid rayPickID) { + RayToEntityIntersectionResult entityResult; + return entityResult; + }); + setSetPrecisionPickingOperator([](QUuid rayPickID, bool value) {}); EntityRenderer::initEntityRenderers(); _currentHoverOverEntityID = UNKNOWN_ENTITY_ID; _currentClickingOnEntityID = UNKNOWN_ENTITY_ID; diff --git a/libraries/networking/src/udt/PacketHeaders.cpp b/libraries/networking/src/udt/PacketHeaders.cpp index 3c6147d6a6..c9961cc53a 100644 --- a/libraries/networking/src/udt/PacketHeaders.cpp +++ b/libraries/networking/src/udt/PacketHeaders.cpp @@ -37,7 +37,7 @@ PacketVersion versionForPacketType(PacketType packetType) { case PacketType::AvatarData: case PacketType::BulkAvatarData: case PacketType::KillAvatar: - return static_cast(AvatarMixerPacketVersion::IsReplicatedInAvatarIdentity); + return static_cast(AvatarMixerPacketVersion::AvatarIdentityLookAtSnapping); case PacketType::MessagesData: return static_cast(MessageDataVersion::TextOrBinaryData); case PacketType::ICEServerHeartbeat: diff --git a/libraries/networking/src/udt/PacketHeaders.h b/libraries/networking/src/udt/PacketHeaders.h index c1408307be..3ba2b96dfc 100644 --- a/libraries/networking/src/udt/PacketHeaders.h +++ b/libraries/networking/src/udt/PacketHeaders.h @@ -289,7 +289,8 @@ enum class AvatarMixerPacketVersion : PacketVersion { AvatarIdentitySequenceId, MannequinDefaultAvatar, AvatarIdentitySequenceFront, - IsReplicatedInAvatarIdentity + IsReplicatedInAvatarIdentity, + AvatarIdentityLookAtSnapping, }; enum class DomainConnectRequestVersion : PacketVersion { diff --git a/libraries/ui/src/VrMenu.cpp b/libraries/ui/src/VrMenu.cpp index 3959e950e9..70daff944a 100644 --- a/libraries/ui/src/VrMenu.cpp +++ b/libraries/ui/src/VrMenu.cpp @@ -26,12 +26,14 @@ static unsigned int USER_DATA_ID = 0; // and qml object and inject the pointer into both objects class MenuUserData : public QObjectUserData { public: - MenuUserData(QAction* action, QObject* qmlObject) { + MenuUserData(QAction* action, QObject* qmlObject, QObject* qmlParent) { if (!USER_DATA_ID) { USER_DATA_ID = DependencyManager::get()->getMenuUserDataId(); } _action = action; _qml = qmlObject; + _qmlParent = qmlParent; + action->setUserData(USER_DATA_ID, this); qmlObject->setUserData(USER_DATA_ID, this); qmlObject->setObjectName(uuid.toString()); @@ -43,6 +45,41 @@ public: _shutdownConnection = QObject::connect(qApp, &QCoreApplication::aboutToQuit, [=] { QObject::disconnect(_changedConnection); }); + + class ExclusionGroupSetter : public QObject { + public: + ExclusionGroupSetter(QObject* from, QObject* to, QObject* qmlParent) : QObject(from), _from(from), _to(to), _qmlParent(qmlParent) { + _from->installEventFilter(this); + } + + ~ExclusionGroupSetter() { + _from->removeEventFilter(this); + } + protected: + virtual bool eventFilter(QObject* o, QEvent* e) override { + if (e->type() == QEvent::DynamicPropertyChange) { + QDynamicPropertyChangeEvent* dpc = static_cast(e); + if (dpc->propertyName() == "exclusionGroup") + { + // unfortunately Qt doesn't support passing dynamic properties between C++ / QML, so we have to use this ugly helper function + QMetaObject::invokeMethod(_qmlParent, + "addExclusionGroup", + Qt::DirectConnection, + Q_ARG(QVariant, QVariant::fromValue(_to)), + Q_ARG(QVariant, _from->property(dpc->propertyName()))); + } + } + + return QObject::eventFilter(o, e); + } + + private: + QObject* _from; + QObject* _to; + QObject* _qmlParent; + }; + + new ExclusionGroupSetter(action, qmlObject, qmlParent); } ~MenuUserData() { @@ -110,6 +147,7 @@ private: QMetaObject::Connection _changedConnection; QAction* _action { nullptr }; QObject* _qml { nullptr }; + QObject* _qmlParent{ nullptr }; }; @@ -157,16 +195,16 @@ void VrMenu::addMenu(QMenu* menu) { } // Bind the QML and Widget together - new MenuUserData(menu->menuAction(), result); + new MenuUserData(menu->menuAction(), result, qmlParent); } -void bindActionToQmlAction(QObject* qmlAction, QAction* action) { +void bindActionToQmlAction(QObject* qmlAction, QAction* action, QObject* qmlParent) { auto text = action->text(); if (text == "Login") { qDebug(uiLogging) << "Login action " << action; } - new MenuUserData(action, qmlAction); + new MenuUserData(action, qmlAction, qmlParent); QObject::connect(action, &QAction::toggled, [=](bool checked) { qmlAction->setProperty("checked", checked); }); @@ -195,7 +233,7 @@ void VrMenu::addAction(QMenu* menu, QAction* action) { QObject* result = reinterpret_cast(returnedValue); // returnedValue.value(); Q_ASSERT(result); // Bind the QML and Widget together - bindActionToQmlAction(result, action); + bindActionToQmlAction(result, action, _rootMenu); } void VrMenu::addSeparator(QMenu* menu) { @@ -231,7 +269,7 @@ void VrMenu::insertAction(QAction* before, QAction* action) { Q_ASSERT(invokeResult); QObject* result = reinterpret_cast(returnedValue); // returnedValue.value(); Q_ASSERT(result); - bindActionToQmlAction(result, action); + bindActionToQmlAction(result, action, _rootMenu); } class QQuickMenuBase; diff --git a/interface/src/ui/ImageProvider.cpp b/libraries/ui/src/ui/ImageProvider.cpp similarity index 71% rename from interface/src/ui/ImageProvider.cpp rename to libraries/ui/src/ui/ImageProvider.cpp index 4925cdf1e9..6398e6fad2 100644 --- a/interface/src/ui/ImageProvider.cpp +++ b/libraries/ui/src/ui/ImageProvider.cpp @@ -10,13 +10,24 @@ // #include "ImageProvider.h" -#include + +#include +#include const QString ImageProvider::PROVIDER_NAME = "security"; +QReadWriteLock ImageProvider::_rwLock; +QPixmap* ImageProvider::_securityImage = nullptr; + +void ImageProvider::setSecurityImage(QPixmap* pixmap) { + // no need to delete old one, that is managed by the wallet + QWriteLocker lock(&_rwLock); + _securityImage = pixmap; +} QPixmap ImageProvider::requestPixmap(const QString& id, QSize* size, const QSize& requestedSize) { // adjust the internal pixmap to have the requested size + QReadLocker lock(&_rwLock); if (id == "securityImage" && _securityImage) { *size = _securityImage->size(); if (requestedSize.width() > 0 && requestedSize.height() > 0) { diff --git a/interface/src/ui/ImageProvider.h b/libraries/ui/src/ui/ImageProvider.h similarity index 82% rename from interface/src/ui/ImageProvider.h rename to libraries/ui/src/ui/ImageProvider.h index c0b620585a..5bd2e254e7 100644 --- a/interface/src/ui/ImageProvider.h +++ b/libraries/ui/src/ui/ImageProvider.h @@ -13,6 +13,7 @@ #define hifi_ImageProvider_h #include +#include class ImageProvider: public QQuickImageProvider { public: @@ -22,10 +23,10 @@ public: QPixmap requestPixmap(const QString& id, QSize* size, const QSize& requestedSize) override; - void setSecurityImage(QPixmap* pixmap) { _securityImage = pixmap; } - + void setSecurityImage(QPixmap* pixmap); protected: - QPixmap* _securityImage { nullptr }; + static QReadWriteLock _rwLock; + static QPixmap* _securityImage; }; diff --git a/libraries/ui/src/ui/OffscreenQmlSurface.cpp b/libraries/ui/src/ui/OffscreenQmlSurface.cpp index be9a15b416..84466f41b0 100644 --- a/libraries/ui/src/ui/OffscreenQmlSurface.cpp +++ b/libraries/ui/src/ui/OffscreenQmlSurface.cpp @@ -6,6 +6,7 @@ // See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html // #include "OffscreenQmlSurface.h" +#include "ImageProvider.h" // Has to come before Qt GL includes #include @@ -184,7 +185,7 @@ private: GLuint texture = textureAndFence.first; uvec2 size = _textureSizes[texture]; auto sizeKey = uvec2ToUint64(size); - // Textures can be returned after all surfaces of the given size have been destroyed, + // Textures can be returned after all surfaces of the given size have been destroyed, // in which case we just destroy the texture if (!_textures.count(sizeKey)) { destroy(textureAndFence); @@ -305,6 +306,9 @@ static size_t globalEngineRefCount{ 0 }; #endif void initializeQmlEngine(QQmlEngine* engine, QQuickWindow* window) { + // register the pixmap image provider (used only for security image, for now) + engine->addImageProvider(ImageProvider::PROVIDER_NAME, new ImageProvider()); + engine->setNetworkAccessManagerFactory(new QmlNetworkAccessManagerFactory); auto importList = engine->importPathList(); importList.insert(importList.begin(), PathUtils::resourcesPath()); @@ -464,8 +468,8 @@ std::function OffscreenQmlSurface::getDiscardLambda() { } bool OffscreenQmlSurface::allowNewFrame(uint8_t fps) { - // If we already have a pending texture, don't render another one - // i.e. don't render faster than the consumer context, since it wastes + // If we already have a pending texture, don't render another one + // i.e. don't render faster than the consumer context, since it wastes // GPU cycles on producing output that will never be seen if (0 != _latestTextureAndFence.first) { return false; @@ -526,6 +530,7 @@ void OffscreenQmlSurface::create() { // Create a QML engine. auto qmlEngine = acquireEngine(_quickWindow); + _qmlContext = new QQmlContext(qmlEngine->rootContext()); _qmlContext->setContextProperty("offscreenWindow", QVariant::fromValue(getWindow())); diff --git a/scripts/system/commerce/wallet.js b/scripts/system/commerce/wallet.js index 4b758f0add..34c602513e 100644 --- a/scripts/system/commerce/wallet.js +++ b/scripts/system/commerce/wallet.js @@ -14,7 +14,7 @@ /*global XXX */ (function () { // BEGIN LOCAL_SCOPE - + Script.include("/~/system/libraries/accountUtils.js"); // Function Name: onButtonClicked() // @@ -53,19 +53,22 @@ // Description: // -Called when a message is received from SpectatorCamera.qml. The "message" argument is what is sent from the QML // in the format "{method, params}", like json-rpc. See also sendToQml(). + var isHmdPreviewDisabled = true; function fromQml(message) { switch (message.method) { case 'walletSetup_cancelClicked': + case 'needsLogIn_cancelClicked': tablet.gotoHomeScreen(); break; - case 'walletSetup_loginClicked': - if ((HMD.active && Settings.getValue("hmdTabletBecomesToolbar", false)) - || (!HMD.active && Settings.getValue("desktopTabletBecomesToolbar", true))) { - Menu.triggerOption("Login / Sign Up"); - tablet.gotoHomeScreen(); - } else { - tablet.loadQMLOnTop("../../../dialogs/TabletLoginDialog.qml"); - } + case 'needsLogIn_loginClicked': + openLoginWindow(); + break; + case 'disableHmdPreview': + isHmdPreviewDisabled = Menu.isOptionChecked("Disable Preview"); + Menu.setIsOptionChecked("Disable Preview", true); + break; + case 'maybeEnableHmdPreview': + Menu.setIsOptionChecked("Disable Preview", isHmdPreviewDisabled); break; default: print('Unrecognized message from QML:', JSON.stringify(message)); diff --git a/scripts/system/html/js/marketplacesInject.js b/scripts/system/html/js/marketplacesInject.js index 2889a1514a..2675bb97b6 100644 --- a/scripts/system/html/js/marketplacesInject.js +++ b/scripts/system/html/js/marketplacesInject.js @@ -90,20 +90,20 @@ }); } - function addInventoryButton() { + function addPurchasesButton() { // Why isn't this an id?! This really shouldn't be a class on the website, but it is. var navbarBrandElement = document.getElementsByClassName('navbar-brand')[0]; - var inventoryElement = document.createElement('a'); - inventoryElement.classList.add("btn"); - inventoryElement.classList.add("btn-default"); - inventoryElement.id = "inventoryButton"; - inventoryElement.setAttribute('href', "#"); - inventoryElement.innerHTML = "INVENTORY"; - inventoryElement.style = "height:100%;margin-top:0;padding:15px 15px;"; - navbarBrandElement.parentNode.insertAdjacentElement('beforeend', inventoryElement); - $('#inventoryButton').on('click', function () { + var purchasesElement = document.createElement('a'); + purchasesElement.classList.add("btn"); + purchasesElement.classList.add("btn-default"); + purchasesElement.id = "purchasesButton"; + purchasesElement.setAttribute('href', "#"); + purchasesElement.innerHTML = "PURCHASES"; + purchasesElement.style = "height:100%;margin-top:0;padding:15px 15px;"; + navbarBrandElement.parentNode.insertAdjacentElement('beforeend', purchasesElement); + $('#purchasesButton').on('click', function () { EventBridge.emitWebEvent(JSON.stringify({ - type: "INVENTORY", + type: "PURCHASES", referrerURL: window.location.href })); }); @@ -121,11 +121,25 @@ } function injectBuyButtonOnMainPage() { + var cost; + $('.grid-item').find('#price-or-edit').find('a').each(function() { $(this).attr('data-href', $(this).attr('href')); $(this).attr('href', '#'); - }); - $('.grid-item').find('#price-or-edit').find('.price').text("BUY"); + cost = $(this).closest('.col-xs-3').find('.item-cost').text(); + + $(this).closest('.col-xs-3').prev().attr("class", 'col-xs-6'); + $(this).closest('.col-xs-3').attr("class", 'col-xs-6'); + + if (parseInt(cost) > 0) { + var priceElement = $(this).find('.price') + priceElement.css({ "width": "auto", "padding": "3px 5px", "height": "26px" }); + priceElement.text(parseFloat(cost / 100).toFixed(2) + ' HFC'); + priceElement.css({ "min-width": priceElement.width() + 10 }); + } + }); + + $('.grid-item').find('#price-or-edit').find('a').on('click', function () { buyButtonClicked($(this).closest('.grid-item').attr('data-item-id'), $(this).closest('.grid-item').find('.item-title').text(), @@ -152,8 +166,8 @@ // Try this here in case it works (it will if the user just pressed the "back" button, // since that doesn't trigger another AJAX request. - injectBuyButtonOnMainPage; - addInventoryButton(); + injectBuyButtonOnMainPage(); + addPurchasesButton(); } } @@ -161,15 +175,21 @@ if (confirmAllPurchases) { var href = $('#side-info').find('.btn').attr('href'); $('#side-info').find('.btn').attr('href', '#'); - $('#side-info').find('.btn').html('Buy Item '); + + var cost = $('.item-cost').text(); + + if (parseInt(cost) > 0 && $('#side-info').find('#buyItemButton').size() === 0) { + $('#side-info').find('.btn').html('Own Item: ' + (parseFloat(cost / 100).toFixed(2)) + ' HFC'); + } + $('#side-info').find('.btn').on('click', function () { buyButtonClicked(window.location.pathname.split("/")[3], $('#top-center').find('h1').text(), $('#creator').find('.value').text(), - $('.item-cost').text(), + cost, href); }); - addInventoryButton(); + addPurchasesButton(); } } diff --git a/scripts/system/libraries/accountUtils.js b/scripts/system/libraries/accountUtils.js new file mode 100644 index 0000000000..6df0aa3a87 --- /dev/null +++ b/scripts/system/libraries/accountUtils.js @@ -0,0 +1,16 @@ +// +// accountUtils.js +// scripts/system/libraries/libraries +// +// Copyright 2017 High Fidelity, Inc. +// + +openLoginWindow = function openLoginWindow() { + if ((HMD.active && Settings.getValue("hmdTabletBecomesToolbar", false)) + || (!HMD.active && Settings.getValue("desktopTabletBecomesToolbar", true))) { + Menu.triggerOption("Login / Sign Up"); + } else { + tablet.loadQMLOnTop("../../dialogs/TabletLoginDialog.qml"); + HMD.openTablet(); + } +}; diff --git a/scripts/system/marketplaces/marketplaces.js b/scripts/system/marketplaces/marketplaces.js index 84d7d44689..7d1aaee157 100644 --- a/scripts/system/marketplaces/marketplaces.js +++ b/scripts/system/marketplaces/marketplaces.js @@ -19,9 +19,9 @@ var MARKETPLACE_URL_INITIAL = MARKETPLACE_URL + "?"; // Append "?" to signal injected script that it's the initial page. var MARKETPLACES_URL = Script.resolvePath("../html/marketplaces.html"); 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 MARKETPLACE_CHECKOUT_QML_PATH = Script.resourcesPath() + "qml/hifi/commerce/checkout/Checkout.qml"; + var MARKETPLACE_PURCHASES_QML_PATH = Script.resourcesPath() + "qml/hifi/commerce/purchases/Purchases.qml"; + var MARKETPLACE_WALLET_QML_PATH = Script.resourcesPath() + "qml/hifi/commerce/wallet/Wallet.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"; @@ -88,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 || url === MARKETPLACE_SECURITY_QML_PATH)); + wireEventBridge(type === "QML" && (url === MARKETPLACE_CHECKOUT_QML_PATH || url === MARKETPLACE_PURCHASES_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) { @@ -141,10 +141,10 @@ action: "inspectionModeSetting", data: Settings.getValue("inspectionMode", false) })); - } else if (parsedJsonMessage.type === "INVENTORY") { - tablet.pushOntoStack(MARKETPLACE_INVENTORY_QML_PATH); + } else if (parsedJsonMessage.type === "PURCHASES") { + tablet.pushOntoStack(MARKETPLACE_PURCHASES_QML_PATH); tablet.sendToQml({ - method: 'updateInventory', + method: 'updatePurchases', referrerURL: parsedJsonMessage.referrerURL }); } @@ -199,30 +199,46 @@ // in the format "{method, params}", like json-rpc. function fromQml(message) { switch (message.method) { + case 'checkout_setUpClicked': + tablet.pushOntoStack(MARKETPLACE_WALLET_QML_PATH); + break; case 'checkout_cancelClicked': tablet.gotoWebScreen(MARKETPLACE_URL + '/items/' + message.params, MARKETPLACES_INJECT_SCRIPT_URL); // TODO: Make Marketplace a QML app that's a WebView wrapper so we can use the app stack. // I don't think this is trivial to do since we also want to inject some JS into the DOM. //tablet.popFromStack(); break; - case 'checkout_buySuccess': + case 'checkout_goToPurchases': + tablet.pushOntoStack(MARKETPLACE_PURCHASES_QML_PATH); + tablet.sendToQml({ + method: 'updatePurchases', + referrerURL: MARKETPLACE_URL_INITIAL + }); + break; + case 'checkout_continueShopping': tablet.gotoWebScreen(MARKETPLACE_URL + '/items/' + message.itemId, MARKETPLACES_INJECT_SCRIPT_URL); //tablet.popFromStack(); break; - case 'inventory_itemClicked': + case 'purchases_itemInfoClicked': var itemId = message.itemId; if (itemId && itemId !== "") { tablet.gotoWebScreen(MARKETPLACE_URL + '/items/' + itemId, MARKETPLACES_INJECT_SCRIPT_URL); } break; - case 'inventory_backClicked': + case 'purchases_backClicked': tablet.gotoWebScreen(message.referrerURL, MARKETPLACES_INJECT_SCRIPT_URL); break; - case 'securityImageSelection_cancelClicked': - tablet.gotoWebScreen(message.referrerURL, MARKETPLACES_INJECT_SCRIPT_URL); + case 'purchases_goToMarketplaceClicked': + tablet.gotoWebScreen(MARKETPLACE_URL_INITIAL, MARKETPLACES_INJECT_SCRIPT_URL); + break; + case 'needsLogIn_cancelClicked': + tablet.gotoWebScreen(MARKETPLACE_URL_INITIAL, MARKETPLACES_INJECT_SCRIPT_URL); + break; + case 'needsLogIn_loginClicked': + openLoginWindow(); break; default: - print('Unrecognized message from Checkout.qml, Inventory.qml, or SecurityImageSelection.qml: ' + JSON.stringify(message)); + print('Unrecognized message from Checkout.qml or Purchases.qml: ' + JSON.stringify(message)); } } diff --git a/scripts/system/snapshot.js b/scripts/system/snapshot.js index df5ed45fed..e5c60af77b 100644 --- a/scripts/system/snapshot.js +++ b/scripts/system/snapshot.js @@ -10,7 +10,8 @@ /* globals Tablet, Script, HMD, Settings, DialogsManager, Menu, Reticle, OverlayWebWindow, Desktop, Account, MyAvatar, Snapshot */ /* eslint indent: ["error", 4, { "outerIIFEBody": 0 }] */ -(function() { // BEGIN LOCAL_SCOPE +(function () { // BEGIN LOCAL_SCOPE +Script.include("/~/system/libraries/accountUtils.js"); var SNAPSHOT_DELAY = 500; // 500ms var FINISH_SOUND_DELAY = 350; @@ -52,15 +53,7 @@ try { print('Failed to resolve request api, error: ' + err); } -function openLoginWindow() { - if ((HMD.active && Settings.getValue("hmdTabletBecomesToolbar", false)) - || (!HMD.active && Settings.getValue("desktopTabletBecomesToolbar", true))) { - Menu.triggerOption("Login / Sign Up"); - } else { - tablet.loadQMLOnTop("../../dialogs/TabletLoginDialog.qml"); - HMD.openTablet(); - } -} + function removeFromStoryIDsToMaybeDelete(story_id) { storyIDsToMaybeDelete.splice(storyIDsToMaybeDelete.indexOf(story_id), 1);