diff --git a/assignment-client/src/audio/AudioMixer.cpp b/assignment-client/src/audio/AudioMixer.cpp index 7f088d8183..8af4eec934 100644 --- a/assignment-client/src/audio/AudioMixer.cpp +++ b/assignment-client/src/audio/AudioMixer.cpp @@ -9,6 +9,8 @@ // See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html // +#include "AudioMixer.h" + #include #include @@ -36,8 +38,6 @@ #include "AvatarAudioStream.h" #include "InjectedAudioStream.h" -#include "AudioMixer.h" - static const float DEFAULT_ATTENUATION_PER_DOUBLING_IN_DISTANCE = 0.5f; // attenuation = -6dB * log2(distance) static const int DISABLE_STATIC_JITTER_FRAMES = -1; static const float DEFAULT_NOISE_MUTING_THRESHOLD = 1.0f; diff --git a/assignment-client/src/audio/AudioMixer.h b/assignment-client/src/audio/AudioMixer.h index ed3a5b0541..8c47893aa3 100644 --- a/assignment-client/src/audio/AudioMixer.h +++ b/assignment-client/src/audio/AudioMixer.h @@ -18,6 +18,8 @@ #include #include +#include + #include "AudioMixerStats.h" #include "AudioMixerSlavePool.h" diff --git a/assignment-client/src/audio/AudioMixerClientData.cpp b/assignment-client/src/audio/AudioMixerClientData.cpp index 2560f43337..d4d4f847ee 100644 --- a/assignment-client/src/audio/AudioMixerClientData.cpp +++ b/assignment-client/src/audio/AudioMixerClientData.cpp @@ -9,6 +9,8 @@ // See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html // +#include "AudioMixerClientData.h" + #include #include @@ -22,8 +24,6 @@ #include "AudioLogging.h" #include "AudioHelpers.h" #include "AudioMixer.h" -#include "AudioMixerClientData.h" - AudioMixerClientData::AudioMixerClientData(const QUuid& nodeID) : NodeData(nodeID), diff --git a/assignment-client/src/audio/AudioMixerClientData.h b/assignment-client/src/audio/AudioMixerClientData.h index c3a31715ea..514e1c9756 100644 --- a/assignment-client/src/audio/AudioMixerClientData.h +++ b/assignment-client/src/audio/AudioMixerClientData.h @@ -21,6 +21,7 @@ #include #include +#include #include #include "PositionalAudioStream.h" diff --git a/assignment-client/src/audio/AudioMixerSlave.cpp b/assignment-client/src/audio/AudioMixerSlave.cpp index abc2f7220d..b975ba8bfe 100644 --- a/assignment-client/src/audio/AudioMixerSlave.cpp +++ b/assignment-client/src/audio/AudioMixerSlave.cpp @@ -9,6 +9,8 @@ // See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html // +#include "AudioMixerSlave.h" + #include #include @@ -34,8 +36,6 @@ #include "InjectedAudioStream.h" #include "AudioHelpers.h" -#include "AudioMixerSlave.h" - using AudioStreamMap = AudioMixerClientData::AudioStreamMap; // packet helpers diff --git a/interface/resources/fonts/hifi-glyphs.ttf b/interface/resources/fonts/hifi-glyphs.ttf index 8907cf7858..8db0377f88 100644 Binary files a/interface/resources/fonts/hifi-glyphs.ttf and b/interface/resources/fonts/hifi-glyphs.ttf differ diff --git a/interface/resources/qml/controls-uit/Separator.qml b/interface/resources/qml/controls-uit/Separator.qml index 5e2d278454..3350764ae9 100644 --- a/interface/resources/qml/controls-uit/Separator.qml +++ b/interface/resources/qml/controls-uit/Separator.qml @@ -14,8 +14,8 @@ import "../styles-uit" Item { property int colorScheme: 0; - readonly property var topColor: [ hifi.colors.baseGrayShadow, hifi.colors.faintGray ]; - readonly property var bottomColor: [ hifi.colors.baseGrayHighlight, hifi.colors.faintGray ]; + readonly property var topColor: [ hifi.colors.baseGrayShadow, hifi.colors.faintGray, "#89858C" ]; + readonly property var bottomColor: [ hifi.colors.baseGrayHighlight, hifi.colors.faintGray, "#89858C" ]; // Size height: colorScheme === 0 ? 2 : 1; diff --git a/interface/resources/qml/controls-uit/TextField.qml b/interface/resources/qml/controls-uit/TextField.qml index a21d1f8efd..3fc5d83129 100644 --- a/interface/resources/qml/controls-uit/TextField.qml +++ b/interface/resources/qml/controls-uit/TextField.qml @@ -34,11 +34,10 @@ TextField { placeholderText: textField.placeholderText - FontLoader { id: firaSansSemiBold; source: "../../fonts/FiraSans-SemiBold.ttf"; } + FontLoader { id: firaSansRegular; source: "../../fonts/FiraSans-Regular.ttf"; } FontLoader { id: hifiGlyphs; source: "../../fonts/hifi-glyphs.ttf"; } - font.family: firaSansSemiBold.name + font.family: firaSansRegular.name font.pixelSize: hifi.fontSizes.textFieldInput - font.italic: textField.text == "" height: implicitHeight + 3 // Make surrounding box higher so that highlight is vertically centered. property alias textFieldLabel: textFieldLabel diff --git a/interface/resources/qml/hifi/commerce/checkout/Checkout.qml b/interface/resources/qml/hifi/commerce/checkout/Checkout.qml index 809f48361d..ab47bb28ad 100644 --- a/interface/resources/qml/hifi/commerce/checkout/Checkout.qml +++ b/interface/resources/qml/hifi/commerce/checkout/Checkout.qml @@ -28,7 +28,7 @@ Rectangle { id: root; objectName: "checkout" property string activeView: "initialize"; - property bool purchasesReceived: false; + property bool ownershipStatusReceived: false; property bool balanceReceived: false; property string itemName; property string itemId; @@ -37,11 +37,16 @@ Rectangle { property double balanceAfterPurchase; property bool alreadyOwned: false; property int itemPrice: -1; - property bool itemIsJson: true; + property bool isCertified; + property string itemType; + property var itemTypesArray: ["entity", "wearable", "contentSet", "app", "avatar"]; + property var itemTypesText: ["entity", "wearable", "content set", "app", "avatar"]; + property var buttonTextNormal: ["REZ", "WEAR", "REPLACE CONTENT SET", "INSTALL", "WEAR"]; + property var buttonTextClicked: ["REZZED!", "WORN!", "CONTENT SET REPLACED!", "INSTALLED!", "AVATAR CHANGED!"] + property var buttonGlyph: [hifi.glyphs.wand, hifi.glyphs.hat, hifi.glyphs.globe, hifi.glyphs.install, hifi.glyphs.avatar]; property bool shouldBuyWithControlledFailure: false; property bool debugCheckoutSuccess: false; property bool canRezCertifiedItems: Entities.canRezCertified() || Entities.canRezTmpCertified(); - property bool isWearable; property string referrer; // Style color: hifi.colors.white; @@ -85,7 +90,9 @@ Rectangle { UserActivityLogger.commercePurchaseFailure(root.itemId, root.itemAuthor, root.itemPrice, !root.alreadyOwned, result.message); } else { root.itemHref = result.data.download_url; - root.isWearable = result.data.categories.indexOf("Wearables") > -1; + if (result.data.categories.indexOf("Wearables") > -1) { + root.itemType = "wearable"; + } root.activeView = "checkoutSuccess"; UserActivityLogger.commercePurchaseSuccess(root.itemId, root.itemAuthor, root.itemPrice, !root.alreadyOwned); } @@ -97,32 +104,53 @@ Rectangle { } else { root.balanceReceived = true; root.balanceAfterPurchase = result.data.balance - root.itemPrice; - root.setBuyText(); + root.refreshBuyUI(); } } - onInventoryResult: { + onAlreadyOwnedResult: { if (result.status !== 'success') { - console.log("Failed to get purchases", result.data.message); + console.log("Failed to get Already Owned status", result.data.message); } else { - root.purchasesReceived = true; - if (purchasesContains(result.data.assets, itemId)) { - root.alreadyOwned = true; + root.ownershipStatusReceived = true; + if (result.data.marketplace_item_id === root.itemId) { + root.alreadyOwned = result.data.already_owned; } else { + console.log("WARNING - Received 'Already Owned' status about different Marketplace ID!"); root.alreadyOwned = false; } - root.setBuyText(); + root.refreshBuyUI(); } } } onItemIdChanged: { - Commerce.inventory(); + root.ownershipStatusReceived = false; + Commerce.alreadyOwned(root.itemId); itemPreviewImage.source = "https://hifi-metaverse.s3-us-west-1.amazonaws.com/marketplace/previews/" + itemId + "/thumbnail/hifi-mp-" + itemId + ".jpg"; } onItemHrefChanged: { - itemIsJson = root.itemHref.endsWith('.json'); + if (root.itemHref.indexOf(".fst") > -1) { + root.itemType = "avatar"; + } else if (root.itemHref.indexOf('.json.gz') > -1) { + root.itemType = "contentSet"; + } else if (root.itemHref.indexOf('.app.json') > -1) { + root.itemType = "app"; + } else if (root.itemHref.indexOf('.json') > -1) { + root.itemType = "entity"; // "wearable" type handled later + } else { + console.log("WARNING - Item type is UNKNOWN!"); + root.itemType = "entity"; + } + } + + onItemTypeChanged: { + if (root.itemType === "entity" || root.itemType === "wearable" || root.itemType === "contentSet" || root.itemType === "avatar") { + root.isCertified = true; + } else { + root.isCertified = false; + } } onItemPriceChanged: { @@ -203,7 +231,7 @@ Rectangle { color: hifi.colors.white; Component.onCompleted: { - purchasesReceived = false; + ownershipStatusReceived = false; balanceReceived = false; Commerce.getWalletStatus(); } @@ -278,6 +306,31 @@ Rectangle { anchors.left: parent.left; anchors.right: parent.right; + Rectangle { + id: loading; + z: 997; + visible: !root.ownershipStatusReceived || !root.balanceReceived; + anchors.fill: parent; + color: Qt.rgba(0.0, 0.0, 0.0, 0.7); + + // This object is always used in a popup. + // This MouseArea is used to prevent a user from being + // able to click on a button/mouseArea underneath the popup/section. + MouseArea { + anchors.fill: parent; + hoverEnabled: true; + propagateComposedEvents: false; + } + + AnimatedImage { + source: "../common/images/loader.gif" + width: 96; + height: width; + anchors.verticalCenter: parent.verticalCenter; + anchors.horizontalCenter: parent.horizontalCenter; + } + } + RalewayRegular { id: confirmPurchaseText; anchors.top: parent.top; @@ -286,8 +339,8 @@ Rectangle { anchors.leftMargin: 16; width: paintedWidth; height: paintedHeight; - text: "Confirm Purchase:"; - color: hifi.colors.baseGray; + text: "Review Purchase:"; + color: hifi.colors.black; size: 28; } @@ -400,7 +453,7 @@ Rectangle { width: root.width; // Anchors anchors.top: separator2.bottom; - anchors.topMargin: 16; + anchors.topMargin: 0; anchors.left: parent.left; anchors.leftMargin: 16; anchors.right: parent.right; @@ -411,8 +464,8 @@ Rectangle { Rectangle { id: buyTextContainer; visible: buyText.text !== ""; - anchors.top: cancelPurchaseButton.bottom; - anchors.topMargin: 16; + anchors.top: parent.top; + anchors.topMargin: 10; anchors.left: parent.left; anchors.right: parent.right; height: buyText.height + 30; @@ -454,32 +507,63 @@ Rectangle { // Alignment horizontalAlignment: Text.AlignLeft; verticalAlignment: Text.AlignVCenter; + } + } - onLinkActivated: { - sendToScript({method: 'checkout_goToPurchases', filterText: root.itemName}); - } + // "View in My Purchases" button + HifiControlsUit.Button { + id: viewInMyPurchasesButton; + visible: false; + color: hifi.buttons.blue; + colorScheme: hifi.colorSchemes.light; + anchors.top: buyTextContainer.visible ? buyTextContainer.bottom : checkoutActionButtonsContainer.top; + anchors.topMargin: 10; + height: 50; + anchors.left: parent.left; + anchors.right: parent.right; + text: "VIEW THIS ITEM IN MY PURCHASES"; + onClicked: { + sendToScript({method: 'checkout_goToPurchases', filterText: root.itemName}); } } // "Buy" button HifiControlsUit.Button { id: buyButton; - enabled: (root.balanceAfterPurchase >= 0 && purchasesReceived && balanceReceived) || !itemIsJson; - color: hifi.buttons.blue; + visible: !((root.itemType === "avatar" || root.itemType === "app") && viewInMyPurchasesButton.visible) + enabled: (root.balanceAfterPurchase >= 0 && ownershipStatusReceived && balanceReceived) || (!root.isCertified); + color: viewInMyPurchasesButton.visible ? hifi.buttons.white : hifi.buttons.blue; colorScheme: hifi.colorSchemes.light; - anchors.top: checkoutActionButtonsContainer.top; - anchors.topMargin: 16; - height: 40; + anchors.top: viewInMyPurchasesButton.visible ? viewInMyPurchasesButton.bottom : + (buyTextContainer.visible ? buyTextContainer.bottom : checkoutActionButtonsContainer.top); + anchors.topMargin: 10; + height: 50; anchors.left: parent.left; anchors.right: parent.right; - text: (itemIsJson ? ((purchasesReceived && balanceReceived) ? "Confirm Purchase" : "--") : "Get Item"); + text: ((root.isCertified) ? ((ownershipStatusReceived && balanceReceived) ? + (viewInMyPurchasesButton.visible ? "Buy It Again" : "Confirm Purchase") : "--") : "Get Item"); onClicked: { - if (itemIsJson) { - buyButton.enabled = false; + if (root.isCertified) { if (!root.shouldBuyWithControlledFailure) { - Commerce.buy(itemId, itemPrice); + if (root.itemType === "contentSet" && !Entities.canReplaceContent()) { + lightboxPopup.titleText = "Purchase Content Set"; + lightboxPopup.bodyText = "You will not be able to replace this domain's content with " + root.itemName + + " until the server owner gives you 'Replace Content' permissions.

Are you sure you want to purchase this content set?"; + lightboxPopup.button1text = "CANCEL"; + lightboxPopup.button1method = "root.visible = false;" + lightboxPopup.button2text = "CONFIRM"; + lightboxPopup.button2method = "Commerce.buy('" + root.itemId + "', " + root.itemPrice + ");" + + "root.visible = false; buyButton.enabled = false; loading.visible = true;"; + lightboxPopup.visible = true; + } else { + buyButton.enabled = false; + loading.visible = true; + Commerce.buy(root.itemId, root.itemPrice); + } } else { - Commerce.buy(itemId, itemPrice, true); + buyButton.enabled = false; + loading.visible = true; + Commerce.buy(root.itemId, root.itemPrice, true); } } else { if (urlHandler.canHandleUrl(itemHref)) { @@ -494,9 +578,9 @@ Rectangle { id: cancelPurchaseButton; color: hifi.buttons.noneBorderlessGray; colorScheme: hifi.colorSchemes.light; - anchors.top: buyButton.bottom; - anchors.topMargin: 16; - height: 40; + anchors.top: buyButton.visible ? buyButton.bottom : viewInMyPurchasesButton.bottom; + anchors.topMargin: 10; + height: 50; anchors.left: parent.left; anchors.right: parent.right; text: "Cancel" @@ -522,31 +606,32 @@ Rectangle { anchors.top: titleBarContainer.bottom; anchors.bottom: root.bottom; anchors.left: parent.left; - anchors.leftMargin: 16; + anchors.leftMargin: 20; anchors.right: parent.right; - anchors.rightMargin: 16; + anchors.rightMargin: 20; RalewayRegular { id: completeText; anchors.top: parent.top; - anchors.topMargin: 30; + anchors.topMargin: 18; anchors.left: parent.left; width: paintedWidth; height: paintedHeight; text: "Thank you for your order!"; color: hifi.colors.baseGray; - size: 28; + size: 36; } RalewaySemiBold { id: completeText2; - text: "The item " + '' + root.itemName + '' + - " has been added to your Purchases and a receipt will appear in your Wallet's transaction history."; + text: "The " + (root.itemTypesText)[itemTypesArray.indexOf(root.itemType)] + + ' ' + root.itemName + '' + + " has been added to your Purchases and a receipt will appear in your Wallet's transaction history."; // Text size - size: 20; + size: 18; // Anchors anchors.top: completeText.bottom; - anchors.topMargin: 10; + anchors.topMargin: 15; height: paintedHeight; anchors.left: parent.left; anchors.right: parent.right; @@ -576,7 +661,7 @@ Rectangle { RalewayBold { anchors.fill: parent; - text: "REZZED"; + text: (root.buttonTextClicked)[itemTypesArray.indexOf(root.itemType)]; size: 18; color: hifi.colors.white; verticalAlignment: Text.AlignVCenter; @@ -592,26 +677,52 @@ Rectangle { // "Rez" button HifiControlsUit.Button { id: rezNowButton; - enabled: root.canRezCertifiedItems || root.isWearable; - buttonGlyph: hifi.glyphs.lightning; + enabled: (root.itemType === "entity" && root.canRezCertifiedItems) || + (root.itemType === "contentSet" && Entities.canReplaceContent()) || + root.itemType === "wearable" || root.itemType === "avatar"; + buttonGlyph: (root.buttonGlyph)[itemTypesArray.indexOf(root.itemType)]; color: hifi.buttons.red; colorScheme: hifi.colorSchemes.light; anchors.top: completeText2.bottom; - anchors.topMargin: 30; + anchors.topMargin: 27; height: 50; anchors.left: parent.left; anchors.right: parent.right; - text: root.isWearable ? "Wear It" : "Rez It" + text: (root.buttonTextNormal)[itemTypesArray.indexOf(root.itemType)]; onClicked: { - sendToScript({method: 'checkout_rezClicked', itemHref: root.itemHref, isWearable: root.isWearable}); - rezzedNotifContainer.visible = true; - rezzedNotifContainerTimer.start(); - UserActivityLogger.commerceEntityRezzed(root.itemId, "checkout", root.isWearable ? "rez" : "wear"); + if (root.itemType === "contentSet") { + lightboxPopup.titleText = "Replace Content"; + lightboxPopup.bodyText = "Rezzing this content set will replace the existing environment and all of the items in this domain. " + + "If you want to save the state of the content in this domain, create a backup before proceeding.

" + + "For more information about backing up and restoring content, " + + "" + + "click here to open info on your desktop browser."; + lightboxPopup.button1text = "CANCEL"; + lightboxPopup.button1method = "root.visible = false;" + lightboxPopup.button2text = "CONFIRM"; + lightboxPopup.button2method = "Commerce.replaceContentSet('" + root.itemHref + "');" + + "root.visible = false;rezzedNotifContainer.visible = true; rezzedNotifContainerTimer.start();" + + "UserActivityLogger.commerceEntityRezzed('" + root.itemId + "', 'checkout', '" + root.itemType + "');"; + lightboxPopup.visible = true; + } else if (root.itemType === "avatar") { + lightboxPopup.titleText = "Change Avatar"; + lightboxPopup.bodyText = "This will change your current avatar to " + root.itemName + " while retaining your wearables."; + lightboxPopup.button1text = "CANCEL"; + lightboxPopup.button1method = "root.visible = false;" + lightboxPopup.button2text = "CONFIRM"; + lightboxPopup.button2method = "MyAvatar.useFullAvatarURL('" + root.itemHref + "'); root.visible = false;"; + lightboxPopup.visible = true; + } else { + sendToScript({method: 'checkout_rezClicked', itemHref: root.itemHref, itemType: root.itemType}); + rezzedNotifContainer.visible = true; + rezzedNotifContainerTimer.start(); + UserActivityLogger.commerceEntityRezzed(root.itemId, "checkout", root.itemType); + } } } RalewaySemiBold { id: noPermissionText; - visible: !root.canRezCertifiedItems && !root.isWearable; + visible: !root.canRezCertifiedItems && root.itemType === "entity"; text: 'You do not have Certified Rez permissions in this domain.' // Text size size: 16; @@ -640,7 +751,7 @@ Rectangle { } RalewaySemiBold { id: explainRezText; - visible: !root.isWearable; + visible: root.itemType === "entity"; text: 'What does "Rez" mean?' // Text size size: 16; @@ -663,9 +774,9 @@ Rectangle { RalewaySemiBold { id: myPurchasesLink; - text: 'View this item in My Purchases'; + text: 'View this item in My Purchases'; // Text size - size: 20; + size: 18; // Anchors anchors.top: explainRezText.visible ? explainRezText.bottom : (noPermissionText.visible ? noPermissionText.bottom : rezNowButton.bottom); anchors.topMargin: 40; @@ -685,12 +796,12 @@ Rectangle { RalewaySemiBold { id: walletLink; - text: 'View receipt in Wallet'; + text: 'View receipt in Wallet'; // Text size - size: 20; + size: 18; // Anchors anchors.top: myPurchasesLink.bottom; - anchors.topMargin: 20; + anchors.topMargin: 16; height: paintedHeight; anchors.left: parent.left; anchors.right: parent.right; @@ -708,12 +819,12 @@ Rectangle { RalewayRegular { id: pendingText; text: 'Your item is marked "pending" while your purchase is being confirmed. ' + - 'Learn More'; + 'Learn More'; // Text size - size: 20; + size: 18; // Anchors anchors.top: walletLink.bottom; - anchors.topMargin: 60; + anchors.topMargin: 32; height: paintedHeight; anchors.left: parent.left; anchors.right: parent.right; @@ -739,11 +850,10 @@ Rectangle { color: hifi.buttons.noneBorderlessGray; colorScheme: hifi.colorSchemes.light; anchors.bottom: parent.bottom; - anchors.bottomMargin: 20; + anchors.bottomMargin: 54; anchors.right: parent.right; - anchors.rightMargin: 14; - width: parent.width/2 - anchors.rightMargin; - height: 60; + width: 193; + height: 44; text: "Continue Shopping"; onClicked: { sendToScript({method: 'checkout_continueShopping', itemId: itemId}); @@ -851,7 +961,7 @@ Rectangle { buyButton.color = hifi.buttons.red; root.shouldBuyWithControlledFailure = true; } else { - buyButton.text = (itemIsJson ? ((purchasesReceived && balanceReceived) ? (root.alreadyOwned ? "Buy Another" : "Buy"): "--") : "Get Item"); + buyButton.text = (root.isCertified ? ((ownershipStatusReceived && balanceReceived) ? (root.alreadyOwned ? "Buy Another" : "Buy"): "--") : "Get Item"); buyButton.color = hifi.buttons.blue; root.shouldBuyWithControlledFailure = false; } @@ -883,7 +993,7 @@ Rectangle { itemHref = message.params.itemHref; referrer = message.params.referrer; itemAuthor = message.params.itemAuthor; - setBuyText(); + refreshBuyUI(); break; default: console.log('Unrecognized message from marketplaces.js:', JSON.stringify(message)); @@ -891,22 +1001,13 @@ Rectangle { } 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) { + function refreshBuyUI() { + if (root.isCertified) { + if (root.ownershipStatusReceived && root.balanceReceived) { if (root.balanceAfterPurchase < 0) { if (root.alreadyOwned) { - buyText.text = "Your Wallet does not have sufficient funds to purchase this item again.
" + - 'View the copy you own in My Purchases
'; + buyText.text = "Your Wallet does not have sufficient funds to purchase this item again."; + viewInMyPurchasesButton.visible = true; } else { buyText.text = "Your Wallet does not have sufficient funds to purchase this item."; } @@ -916,15 +1017,19 @@ Rectangle { buyGlyph.size = 54; } else { if (root.alreadyOwned) { - buyText.text = 'You already own this item.
Purchasing it will buy another copy.
View this item in My Purchases
'; - buyTextContainer.color = "#FFD6AD"; - buyTextContainer.border.color = "#FAC07D"; - buyGlyph.text = hifi.glyphs.alert; - buyGlyph.size = 46; + viewInMyPurchasesButton.visible = true; } else { buyText.text = ""; } + + if (root.itemType === "contentSet" && !Entities.canReplaceContent()) { + buyText.text = "The domain owner must enable 'Replace Content' permissions for you in this " + + "domain's server settings before you can replace this domain's content with " + root.itemName + ""; + buyTextContainer.color = "#FFC3CD"; + buyTextContainer.border.color = "#F3808F"; + buyGlyph.text = hifi.glyphs.alert; + buyGlyph.size = 54; + } } } else { buyText.text = ""; @@ -945,8 +1050,8 @@ Rectangle { root.activeView = "checkoutSuccess"; } root.balanceReceived = false; - root.purchasesReceived = false; - Commerce.inventory(); + root.ownershipStatusReceived = false; + Commerce.alreadyOwned(root.itemId); Commerce.balance(); } diff --git a/interface/resources/qml/hifi/commerce/common/CommerceLightbox.qml b/interface/resources/qml/hifi/commerce/common/CommerceLightbox.qml index 8b0ec17232..b24af716ad 100644 --- a/interface/resources/qml/hifi/commerce/common/CommerceLightbox.qml +++ b/interface/resources/qml/hifi/commerce/common/CommerceLightbox.qml @@ -100,6 +100,10 @@ Rectangle { size: 20; verticalAlignment: Text.AlignTop; wrapMode: Text.WordWrap; + + onLinkActivated: { + sendToParent({ method: 'commerceLightboxLinkClicked', linkUrl: link }); + } } Item { diff --git a/interface/resources/qml/hifi/commerce/common/FirstUseTutorial.qml b/interface/resources/qml/hifi/commerce/common/FirstUseTutorial.qml index bcc641acd5..9944838e03 100644 --- a/interface/resources/qml/hifi/commerce/common/FirstUseTutorial.qml +++ b/interface/resources/qml/hifi/commerce/common/FirstUseTutorial.qml @@ -90,11 +90,11 @@ Rectangle { id: introText2; text: "My Purchases"; // Text size - size: 28; + size: 22; // Anchors anchors.top: introText1.bottom; anchors.left: parent.left; - anchors.leftMargin: 12; + anchors.leftMargin: 24; anchors.right: parent.right; height: paintedHeight; // Style diff --git a/interface/resources/qml/hifi/commerce/inspectionCertificate/InspectionCertificate.qml b/interface/resources/qml/hifi/commerce/inspectionCertificate/InspectionCertificate.qml index f493747c5e..a622349d00 100644 --- a/interface/resources/qml/hifi/commerce/inspectionCertificate/InspectionCertificate.qml +++ b/interface/resources/qml/hifi/commerce/inspectionCertificate/InspectionCertificate.qml @@ -208,6 +208,7 @@ Rectangle { // able to click on a button/mouseArea underneath the popup/section. MouseArea { anchors.fill: parent; + hoverEnabled: true; propagateComposedEvents: false; } diff --git a/interface/resources/qml/hifi/commerce/purchases/PurchasedItem.qml b/interface/resources/qml/hifi/commerce/purchases/PurchasedItem.qml index 05e280b839..cc2bcd69aa 100644 --- a/interface/resources/qml/hifi/commerce/purchases/PurchasedItem.qml +++ b/interface/resources/qml/hifi/commerce/purchases/PurchasedItem.qml @@ -20,6 +20,7 @@ import "../../../styles-uit" import "../../../controls-uit" as HifiControlsUit import "../../../controls" as HifiControls import "../wallet" as HifiWallet +import TabletScriptingInterface 1.0 // references XXX from root context @@ -29,7 +30,6 @@ Item { id: root; property string purchaseStatus; property bool purchaseStatusChanged; - property bool canRezCertifiedItems: false; property string itemName; property string itemId; property string itemPreviewImageUrl; @@ -39,7 +39,14 @@ Item { property int itemEdition; property int numberSold; property int limitedRun; - property bool isWearable; + property string itemType; + property var itemTypesArray: ["entity", "wearable", "contentSet", "app", "avatar"]; + property var buttonTextNormal: ["REZ", "WEAR", "REPLACE", "INSTALL", "WEAR"]; + property var buttonTextClicked: ["REZZED", "WORN", "REPLACED", "INSTALLED", "WORN"] + property var buttonGlyph: [hifi.glyphs.wand, hifi.glyphs.hat, hifi.glyphs.globe, hifi.glyphs.install, hifi.glyphs.avatar]; + property bool showConfirmation: false; + property bool hasPermissionToRezThis; + property bool permissionExplanationCardVisible; property string originalStatusText; property string originalStatusColor; @@ -47,6 +54,35 @@ Item { height: 110; width: parent.width; + Connections { + target: Commerce; + + onContentSetChanged: { + if (contentSetHref === root.itemHref) { + showConfirmation = true; + } + } + } + + Connections { + target: MyAvatar; + + onSkeletonModelURLChanged: { + if (skeletonModelURL === root.itemHref) { + showConfirmation = true; + } + } + } + + onItemTypeChanged: { + if ((itemType === "entity" && (!Entities.canRezCertified() && !Entities.canRezTmpCertified())) || + (itemType === "contentSet" && !Entities.canReplaceContent())) { + root.hasPermissionToRezThis = false; + } else { + root.hasPermissionToRezThis = true; + } + } + onPurchaseStatusChangedChanged: { if (root.purchaseStatusChanged === true && root.purchaseStatus === "confirmed") { root.originalStatusText = statusText.text; @@ -57,6 +93,15 @@ Item { } } + onShowConfirmationChanged: { + if (root.showConfirmation) { + rezzedNotifContainer.visible = true; + rezzedNotifContainerTimer.start(); + UserActivityLogger.commerceEntityRezzed(root.itemId, "purchases", root.itemType); + root.showConfirmation = false; + } + } + Timer { id: confirmedTimer; interval: 3000; @@ -73,10 +118,10 @@ Item { color: hifi.colors.white; // Size anchors.left: parent.left; - anchors.leftMargin: 8; + anchors.leftMargin: 16; anchors.right: parent.right; - anchors.rightMargin: 8; - anchors.top: parent.top; + anchors.rightMargin: 16; + anchors.verticalCenter: parent.verticalCenter; height: root.height - 10; Image { @@ -96,15 +141,20 @@ Item { } } - + TextMetrics { + id: itemNameTextMetrics; + font: itemName.font; + text: itemName.text; + } RalewaySemiBold { id: itemName; anchors.top: itemPreviewImage.top; anchors.topMargin: 4; anchors.left: itemPreviewImage.right; anchors.leftMargin: 8; - anchors.right: buttonContainer.left; - anchors.rightMargin: 8; + width: !noPermissionGlyph.visible ? (buttonContainer.x - itemPreviewImage.x - itemPreviewImage.width - anchors.leftMargin) : + Math.min(itemNameTextMetrics.tightBoundingRect.width + 2, + buttonContainer.x - itemPreviewImage.x - itemPreviewImage.width - anchors.leftMargin - noPermissionGlyph.width + 2); height: paintedHeight; // Text size size: 24; @@ -130,6 +180,93 @@ Item { } } } + HiFiGlyphs { + id: noPermissionGlyph; + visible: !root.hasPermissionToRezThis; + anchors.verticalCenter: itemName.verticalCenter; + anchors.left: itemName.right; + anchors.leftMargin: itemName.truncated ? -10 : -2; + text: hifi.glyphs.info; + // Size + size: 40; + width: 32; + // Style + color: hifi.colors.redAccent; + + MouseArea { + anchors.fill: parent; + hoverEnabled: true; + + onEntered: { + noPermissionGlyph.color = hifi.colors.redHighlight; + } + onExited: { + noPermissionGlyph.color = hifi.colors.redAccent; + } + onClicked: { + root.sendToPurchases({ method: 'openPermissionExplanationCard' }); + } + } + } + Rectangle { + id: permissionExplanationCard; + z: 995; + visible: root.permissionExplanationCardVisible; + anchors.fill: parent; + color: hifi.colors.white; + + RalewayRegular { + id: permissionExplanationText; + text: { + if (root.itemType === "contentSet") { + "You do not have 'Replace Content' permissions in this domain. Learn more"; + } else if (root.itemType === "entity") { + "You do not have 'Rez Certified' permissions in this domain. Learn more"; + } else { + "" + } + } + size: 16; + anchors.left: parent.left; + anchors.leftMargin: 30; + anchors.top: parent.top; + anchors.bottom: parent.bottom; + anchors.right: permissionExplanationGlyph.left; + color: hifi.colors.baseGray; + wrapMode: Text.WordWrap; + verticalAlignment: Text.AlignVCenter; + + onLinkActivated: { + sendToPurchases({method: 'showPermissionsExplanation', itemType: root.itemType}); + } + } + // "Close" button + HiFiGlyphs { + id: permissionExplanationGlyph; + text: hifi.glyphs.close; + color: hifi.colors.baseGray; + size: 26; + anchors.top: parent.top; + anchors.bottom: parent.bottom; + anchors.right: parent.right; + width: 77; + horizontalAlignment: Text.AlignHCenter; + verticalAlignment: Text.AlignVCenter; + MouseArea { + anchors.fill: parent; + hoverEnabled: true; + onEntered: { + parent.text = hifi.glyphs.closeInverted; + } + onExited: { + parent.text = hifi.glyphs.close; + } + onClicked: { + root.sendToPurchases({ method: 'openPermissionExplanationCard', closeAll: true }); + } + } + } + } Item { id: certificateContainer; @@ -151,19 +288,19 @@ Item { anchors.bottom: parent.bottom; width: 32; // Style - color: hifi.colors.lightGray; + color: hifi.colors.black; } RalewayRegular { id: viewCertificateText; text: "VIEW CERTIFICATE"; - size: 14; + size: 13; anchors.left: certificateIcon.right; anchors.leftMargin: 4; anchors.top: parent.top; anchors.bottom: parent.bottom; anchors.right: parent.right; - color: hifi.colors.lightGray; + color: hifi.colors.black; } MouseArea { @@ -173,13 +310,13 @@ Item { sendToPurchases({method: 'purchases_itemCertificateClicked', itemCertificateId: root.certificateId}); } onEntered: { - certificateIcon.color = hifi.colors.black; - viewCertificateText.color = hifi.colors.black; - } - onExited: { certificateIcon.color = hifi.colors.lightGray; viewCertificateText.color = hifi.colors.lightGray; } + onExited: { + certificateIcon.color = hifi.colors.black; + viewCertificateText.color = hifi.colors.black; + } } } @@ -193,14 +330,14 @@ Item { anchors.right: buttonContainer.left; anchors.rightMargin: 2; - FiraSansRegular { + RalewayRegular { anchors.left: parent.left; anchors.top: parent.top; anchors.bottom: parent.bottom; width: paintedWidth; text: "#" + root.itemEdition; - size: 15; - color: "#cc6a6a6a"; + size: 13; + color: hifi.colors.black; verticalAlignment: Text.AlignTop; } } @@ -311,7 +448,7 @@ Item { id: rezzedNotifContainer; z: 998; visible: false; - color: hifi.colors.blueHighlight; + color: "#1FC6A6"; anchors.fill: buttonContainer; MouseArea { anchors.fill: parent; @@ -321,8 +458,8 @@ Item { RalewayBold { anchors.fill: parent; - text: "REZZED"; - size: 18; + text: (root.buttonTextClicked)[itemTypesArray.indexOf(root.itemType)]; + size: 15; color: hifi.colors.white; verticalAlignment: Text.AlignVCenter; horizontalAlignment: Text.AlignHCenter; @@ -337,25 +474,47 @@ Item { Button { id: buttonContainer; - property int color: hifi.buttons.red; + property int color: hifi.buttons.blue; property int colorScheme: hifi.colorSchemes.light; anchors.top: parent.top; + anchors.topMargin: 4; anchors.bottom: parent.bottom; + anchors.bottomMargin: 4; anchors.right: parent.right; + anchors.rightMargin: 4; width: height; - enabled: (root.canRezCertifiedItems || root.isWearable) && root.purchaseStatus !== "invalidated"; + enabled: root.hasPermissionToRezThis && + root.purchaseStatus !== "invalidated" && + MyAvatar.skeletonModelURL !== root.itemHref; + + onHoveredChanged: { + if (hovered) { + Tablet.playSound(TabletEnums.ButtonHover); + } + } + + onFocusChanged: { + if (focus) { + Tablet.playSound(TabletEnums.ButtonHover); + } + } onClicked: { - sendToPurchases({method: 'purchases_rezClicked', itemHref: root.itemHref, isWearable: root.isWearable}); - rezzedNotifContainer.visible = true; - rezzedNotifContainerTimer.start(); - UserActivityLogger.commerceEntityRezzed(root.itemId, "purchases", root.isWearable ? "rez" : "wear"); + Tablet.playSound(TabletEnums.ButtonClick); + if (root.itemType === "contentSet") { + sendToPurchases({method: 'showReplaceContentLightbox', itemHref: root.itemHref}); + } else if (root.itemType === "avatar") { + sendToPurchases({method: 'showChangeAvatarLightbox', itemName: root.itemName, itemHref: root.itemHref}); + } else { + sendToPurchases({method: 'purchases_rezClicked', itemHref: root.itemHref, itemType: root.itemType}); + root.showConfirmation = true; + } } style: ButtonStyle { - background: Rectangle { + radius: 4; gradient: Gradient { GradientStop { position: 0.2 @@ -390,13 +549,13 @@ Item { label: Item { HiFiGlyphs { - id: lightningIcon; - text: hifi.glyphs.lightning; + id: rezIcon; + text: (root.buttonGlyph)[itemTypesArray.indexOf(root.itemType)]; // Size - size: 32; + size: 60; // Anchors anchors.top: parent.top; - anchors.topMargin: 12; + anchors.topMargin: 0; anchors.left: parent.left; anchors.right: parent.right; horizontalAlignment: Text.AlignHCenter; @@ -405,18 +564,19 @@ Item { : hifi.buttons.disabledTextColor[control.colorScheme] } RalewayBold { - anchors.top: lightningIcon.bottom; - anchors.topMargin: -20; + id: rezIconLabel; + anchors.top: rezIcon.bottom; + anchors.topMargin: -4; anchors.right: parent.right; anchors.left: parent.left; anchors.bottom: parent.bottom; font.capitalization: Font.AllUppercase color: enabled ? hifi.buttons.textColor[control.color] : hifi.buttons.disabledTextColor[control.colorScheme] - size: 16; + size: 15; verticalAlignment: Text.AlignVCenter horizontalAlignment: Text.AlignHCenter - text: root.isWearable ? "Wear It" : "Rez It" + text: MyAvatar.skeletonModelURL === root.itemHref ? "CURRENT" : (root.buttonTextNormal)[itemTypesArray.indexOf(root.itemType)]; } } } @@ -425,11 +585,11 @@ Item { DropShadow { anchors.fill: mainContainer; - horizontalOffset: 3; - verticalOffset: 3; - radius: 8.0; - samples: 17; - color: "#80000000"; + horizontalOffset: 0; + verticalOffset: 4; + radius: 4.0; + samples: 9 + color: Qt.rgba(0, 0, 0, 0.25); source: mainContainer; } diff --git a/interface/resources/qml/hifi/commerce/purchases/Purchases.qml b/interface/resources/qml/hifi/commerce/purchases/Purchases.qml index 2743677683..9b333a60cd 100644 --- a/interface/resources/qml/hifi/commerce/purchases/Purchases.qml +++ b/interface/resources/qml/hifi/commerce/purchases/Purchases.qml @@ -32,7 +32,6 @@ Rectangle { property bool securityImageResultReceived: false; property bool purchasesReceived: false; property bool punctuationMode: false; - property bool canRezCertifiedItems: Entities.canRezCertified() || Entities.canRezTmpCertified(); property bool pendingInventoryReply: true; property bool isShowingMyItems: false; property bool isDebuggingFirstUseTutorial: false; @@ -148,7 +147,11 @@ Rectangle { Connections { onSendToParent: { - sendToScript(msg); + if (msg.method === 'commerceLightboxLinkClicked') { + Qt.openUrlExternally(msg.linkUrl); + } else { + sendToScript(msg); + } } } } @@ -297,7 +300,7 @@ Rectangle { anchors.left: parent.left; anchors.leftMargin: 8; anchors.right: parent.right; - anchors.rightMargin: 12; + anchors.rightMargin: 16; anchors.top: parent.top; anchors.topMargin: 4; @@ -308,11 +311,11 @@ Rectangle { anchors.bottom: parent.bottom; anchors.bottomMargin: 10; anchors.left: parent.left; - anchors.leftMargin: 4; + anchors.leftMargin: 16; width: paintedWidth; text: isShowingMyItems ? "My Items" : "My Purchases"; - color: hifi.colors.baseGray; - size: 28; + color: hifi.colors.black; + size: 22; } HifiControlsUit.TextField { @@ -323,8 +326,8 @@ Rectangle { hasRoundedBorder: true; anchors.left: myText.right; anchors.leftMargin: 16; - anchors.top: parent.top; - anchors.bottom: parent.bottom; + height: 39; + anchors.verticalCenter: parent.verticalCenter; anchors.right: parent.right; placeholderText: "filter items"; @@ -345,7 +348,7 @@ Rectangle { HifiControlsUit.Separator { id: separator; - colorScheme: 1; + colorScheme: 2; anchors.left: parent.left; anchors.right: parent.right; anchors.top: filterBarContainer.bottom; @@ -365,69 +368,6 @@ Rectangle { id: filteredPurchasesModel; } - Rectangle { - id: cantRezCertified; - visible: !root.canRezCertifiedItems; - color: "#FFC3CD"; - radius: 4; - border.color: hifi.colors.redAccent; - border.width: 1; - anchors.top: separator.bottom; - anchors.topMargin: 12; - anchors.left: parent.left; - anchors.leftMargin: 16; - anchors.right: parent.right; - anchors.rightMargin: 16; - height: 80; - - HiFiGlyphs { - id: lightningIcon; - text: hifi.glyphs.lightning; - // Size - size: 36; - // Anchors - anchors.top: parent.top; - anchors.topMargin: 18; - anchors.left: parent.left; - anchors.leftMargin: 12; - horizontalAlignment: Text.AlignHCenter; - // Style - color: hifi.colors.lightGray; - } - - RalewayRegular { - text: "You don't have permission to rez certified items in this domain. " + - 'Learn More'; - // Text size - size: 18; - // Anchors - anchors.top: parent.top; - anchors.topMargin: 4; - anchors.left: lightningIcon.right; - anchors.leftMargin: 8; - anchors.right: parent.right; - anchors.rightMargin: 8; - anchors.bottom: parent.bottom; - anchors.bottomMargin: 4; - // Style - color: hifi.colors.baseGray; - wrapMode: Text.WordWrap; - // Alignment - verticalAlignment: Text.AlignVCenter; - - onLinkActivated: { - lightboxPopup.titleText = "Rez Permission Required"; - lightboxPopup.bodyText = "You don't have permission to rez certified items in this domain.

" + - "Use the GOTO app to visit another domain or go to your own sandbox."; - lightboxPopup.button1text = "CLOSE"; - lightboxPopup.button1method = "root.visible = false;" - lightboxPopup.button2text = "OPEN GOTO"; - lightboxPopup.button2method = "sendToParent({method: 'purchases_openGoTo'});"; - lightboxPopup.visible = true; - } - } - } - ListView { id: purchasesContentsList; visible: (root.isShowingMyItems && filteredPurchasesModel.count !== 0) || (!root.isShowingMyItems && filteredPurchasesModel.count !== 0); @@ -436,13 +376,12 @@ Rectangle { snapMode: ListView.SnapToItem; highlightRangeMode: ListView.StrictlyEnforceRange; // Anchors - anchors.top: root.canRezCertifiedItems ? separator.bottom : cantRezCertified.bottom; + anchors.top: separator.bottom; anchors.topMargin: 12; anchors.left: parent.left; anchors.bottom: parent.bottom; width: parent.width; delegate: PurchasedItem { - canRezCertifiedItems: root.canRezCertifiedItems; itemName: title; itemId: id; itemPreviewImageUrl: preview; @@ -454,16 +393,31 @@ Rectangle { numberSold: model.number_sold; limitedRun: model.limited_run; displayedItemCount: model.displayedItemCount; - isWearable: model.categories.indexOf("Wearables") > -1; - anchors.topMargin: 12; - anchors.bottomMargin: 12; + permissionExplanationCardVisible: model.permissionExplanationCardVisible; + itemType: { + if (model.root_file_url.indexOf(".fst") > -1) { + "avatar"; + } else if (model.categories.indexOf("Wearables") > -1) { + "wearable"; + } else if (model.root_file_url.endsWith('.json.gz')) { + "contentSet"; + } else if (model.root_file_url.endsWith('.app.json')) { + "app"; + } else if (model.root_file_url.endsWith('.json')) { + "entity"; + } else { + "unknown"; + } + } + anchors.topMargin: 10; + anchors.bottomMargin: 10; Connections { onSendToPurchases: { if (msg.method === 'purchases_itemInfoClicked') { sendToScript({method: 'purchases_itemInfoClicked', itemId: itemId}); } else if (msg.method === "purchases_rezClicked") { - sendToScript({method: 'purchases_rezClicked', itemHref: itemHref, isWearable: isWearable}); + sendToScript({method: 'purchases_rezClicked', itemHref: itemHref, itemType: itemType}); } else if (msg.method === 'purchases_itemCertificateClicked') { inspectionCertificate.visible = true; inspectionCertificate.isLightbox = true; @@ -482,8 +436,51 @@ Rectangle { lightboxPopup.button1text = "CLOSE"; lightboxPopup.button1method = "root.visible = false;" lightboxPopup.visible = true; + } else if (msg.method === "showReplaceContentLightbox") { + lightboxPopup.titleText = "Replace Content"; + lightboxPopup.bodyText = "Rezzing this content set will replace the existing environment and all of the items in this domain. " + + "If you want to save the state of the content in this domain, create a backup before proceeding.

" + + "For more information about backing up and restoring content, " + + "" + + "click here to open info on your desktop browser."; + lightboxPopup.button1text = "CANCEL"; + lightboxPopup.button1method = "root.visible = false;" + lightboxPopup.button2text = "CONFIRM"; + lightboxPopup.button2method = "Commerce.replaceContentSet('" + msg.itemHref + "'); root.visible = false;"; + lightboxPopup.visible = true; + } else if (msg.method === "showChangeAvatarLightbox") { + lightboxPopup.titleText = "Change Avatar"; + lightboxPopup.bodyText = "This will change your current avatar to " + msg.itemName + " while retaining your wearables."; + lightboxPopup.button1text = "CANCEL"; + lightboxPopup.button1method = "root.visible = false;" + lightboxPopup.button2text = "CONFIRM"; + lightboxPopup.button2method = "MyAvatar.useFullAvatarURL('" + msg.itemHref + "'); root.visible = false;"; + lightboxPopup.visible = true; + } else if (msg.method === "showPermissionsExplanation") { + if (msg.itemType === "entity") { + lightboxPopup.titleText = "Rez Certified Permission"; + lightboxPopup.bodyText = "You don't have permission to rez certified items in this domain.

" + + "Use the GOTO app to visit another domain or go to your own sandbox."; + lightboxPopup.button2text = "OPEN GOTO"; + lightboxPopup.button2method = "sendToParent({method: 'purchases_openGoTo'});"; + } else if (msg.itemType === "contentSet") { + lightboxPopup.titleText = "Replace Content Permission"; + lightboxPopup.bodyText = "You do not have the permission 'Replace Content' in this domain's server settings. The domain owner " + + "must enable it for you before you can replace content sets in this domain."; + } + lightboxPopup.button1text = "CLOSE"; + lightboxPopup.button1method = "root.visible = false;" + lightboxPopup.visible = true; } else if (msg.method === "setFilterText") { filterBar.text = msg.filterText; + } else if (msg.method === "openPermissionExplanationCard") { + for (var i = 0; i < filteredPurchasesModel.count; i++) { + if (i !== index || msg.closeAll) { + filteredPurchasesModel.setProperty(i, "permissionExplanationCardVisible", false); + } else { + filteredPurchasesModel.setProperty(i, "permissionExplanationCardVisible", true); + } + } } } } @@ -685,6 +682,7 @@ Rectangle { filteredPurchasesModel.clear(); for (var i = 0; i < tempPurchasesModel.count; i++) { filteredPurchasesModel.append(tempPurchasesModel.get(i)); + filteredPurchasesModel.setProperty(i, 'permissionExplanationCardVisible', false); } populateDisplayedItemCounts(); diff --git a/interface/resources/qml/styles-uit/HifiConstants.qml b/interface/resources/qml/styles-uit/HifiConstants.qml index 5da587ea57..43de8333af 100644 --- a/interface/resources/qml/styles-uit/HifiConstants.qml +++ b/interface/resources/qml/styles-uit/HifiConstants.qml @@ -218,7 +218,7 @@ Item { readonly property var colorStart: [ colors.white, colors.primaryHighlight, "#d42043", "#343434", Qt.rgba(0, 0, 0, 0), Qt.rgba(0, 0, 0, 0), Qt.rgba(0, 0, 0, 0), Qt.rgba(0, 0, 0, 0) ] readonly property var colorFinish: [ colors.lightGrayText, colors.blueAccent, "#94132e", colors.black, Qt.rgba(0, 0, 0, 0), Qt.rgba(0, 0, 0, 0), Qt.rgba(0, 0, 0, 0), Qt.rgba(0, 0, 0, 0) ] readonly property var hoveredColor: [ colorStart[white], colorStart[blue], colorStart[red], colorFinish[black], colorStart[none], colorStart[noneBorderless], colorStart[noneBorderlessWhite], colorStart[noneBorderlessGray] ] - readonly property var pressedColor: [ colorFinish[white], colorFinish[blue], colorFinish[red], colorStart[black], colorStart[none], colorStart[noneBorderless], colorStart[noneBorderlessWhite], colorStart[noneBorderlessGray] ] + readonly property var pressedColor: [ colorFinish[white], colorFinish[blue], colorFinish[red], colorStart[black], colorStart[none], colorStart[noneBorderless], colorStart[noneBorderlessWhite], colors.lightGrayText ] readonly property var focusedColor: [ colors.lightGray50, colors.blueAccent, colors.redAccent, colors.darkGray, colorStart[none], colorStart[noneBorderless], colorStart[noneBorderlessWhite], colorStart[noneBorderlessGray] ] readonly property var disabledColorStart: [ colorStart[white], colors.baseGrayHighlight] readonly property var disabledColorFinish: [ colorFinish[white], colors.baseGrayShadow] @@ -354,5 +354,9 @@ Item { readonly property string wallet: "\ue027" readonly property string paperPlane: "\ue028" readonly property string passphrase: "\ue029" + readonly property string globe: "\ue02c" + readonly property string wand: "\ue02d" + readonly property string hat: "\ue02e" + readonly property string install: "\ue02f" } } diff --git a/interface/src/Application.cpp b/interface/src/Application.cpp index 2e1df62c9f..8465fda0a8 100644 --- a/interface/src/Application.cpp +++ b/interface/src/Application.cpp @@ -6370,6 +6370,24 @@ bool Application::askToWearAvatarAttachmentUrl(const QString& url) { return true; } +void Application::replaceDomainContent(const QString& url) { + qCDebug(interfaceapp) << "Attempting to replace domain content: " << url; + QByteArray urlData(url.toUtf8()); + auto limitedNodeList = DependencyManager::get(); + limitedNodeList->eachMatchingNode([](const SharedNodePointer& node) { + return node->getType() == NodeType::EntityServer && node->getActiveSocket(); + }, [&urlData, limitedNodeList](const SharedNodePointer& octreeNode) { + auto octreeFilePacket = NLPacket::create(PacketType::OctreeFileReplacementFromUrl, urlData.size(), true); + octreeFilePacket->write(urlData); + limitedNodeList->sendPacket(std::move(octreeFilePacket), *octreeNode); + }); + auto addressManager = DependencyManager::get(); + addressManager->handleLookupString(DOMAIN_SPAWNING_POINT); + QString newHomeAddress = addressManager->getHost() + DOMAIN_SPAWNING_POINT; + qCDebug(interfaceapp) << "Setting new home bookmark to: " << newHomeAddress; + DependencyManager::get()->setHomeLocationToAddress(newHomeAddress); +} + bool Application::askToReplaceDomainContent(const QString& url) { QString methodDetails; const int MAX_CHARACTERS_PER_LINE = 90; @@ -6389,21 +6407,7 @@ bool Application::askToReplaceDomainContent(const QString& url) { QString details; if (static_cast(answer.toInt()) == QMessageBox::Yes) { // Given confirmation, send request to domain server to replace content - qCDebug(interfaceapp) << "Attempting to replace domain content: " << url; - QByteArray urlData(url.toUtf8()); - auto limitedNodeList = DependencyManager::get(); - limitedNodeList->eachMatchingNode([](const SharedNodePointer& node) { - return node->getType() == NodeType::EntityServer && node->getActiveSocket(); - }, [&urlData, limitedNodeList](const SharedNodePointer& octreeNode) { - auto octreeFilePacket = NLPacket::create(PacketType::OctreeFileReplacementFromUrl, urlData.size(), true); - octreeFilePacket->write(urlData); - limitedNodeList->sendPacket(std::move(octreeFilePacket), *octreeNode); - }); - auto addressManager = DependencyManager::get(); - addressManager->handleLookupString(DOMAIN_SPAWNING_POINT); - QString newHomeAddress = addressManager->getHost() + DOMAIN_SPAWNING_POINT; - qCDebug(interfaceapp) << "Setting new home bookmark to: " << newHomeAddress; - DependencyManager::get()->setHomeLocationToAddress(newHomeAddress); + replaceDomainContent(url); details = "SuccessfulRequestToReplaceContent"; } else { details = "UserDeclinedToReplaceContent"; diff --git a/interface/src/Application.h b/interface/src/Application.h index f807a5f658..22a9c7774a 100644 --- a/interface/src/Application.h +++ b/interface/src/Application.h @@ -288,6 +288,8 @@ public: bool isServerlessMode() const { return !_serversEnabled; } + void replaceDomainContent(const QString& url); + signals: void svoImportRequested(const QString& url); diff --git a/interface/src/avatar/MyAvatar.cpp b/interface/src/avatar/MyAvatar.cpp index c25aaeeecd..50753724e8 100755 --- a/interface/src/avatar/MyAvatar.cpp +++ b/interface/src/avatar/MyAvatar.cpp @@ -1479,7 +1479,7 @@ void MyAvatar::setSkeletonModelURL(const QUrl& skeletonModelURL) { }); saveAvatarUrl(); emit skeletonChanged(); - + emit skeletonModelURLChanged(); } void MyAvatar::removeAvatarEntities() { diff --git a/interface/src/commerce/Ledger.cpp b/interface/src/commerce/Ledger.cpp index 10ddd4c110..dff441f840 100644 --- a/interface/src/commerce/Ledger.cpp +++ b/interface/src/commerce/Ledger.cpp @@ -49,6 +49,7 @@ Handler(balance) Handler(inventory) Handler(transferHfcToNode) Handler(transferHfcToUsername) +Handler(alreadyOwned) void Ledger::send(const QString& endpoint, const QString& success, const QString& fail, QNetworkAccessManager::Operation method, AccountManagerAuth::Type authType, QJsonObject request) { auto accountManager = DependencyManager::get(); @@ -336,3 +337,12 @@ void Ledger::transferHfcToUsername(const QString& hfc_key, const QString& userna auto transactionString = transactionDoc.toJson(QJsonDocument::Compact); signedSend("transaction", transactionString, hfc_key, "transfer_hfc_to_user", "transferHfcToUsernameSuccess", "transferHfcToUsernameFailure"); } + +void Ledger::alreadyOwned(const QString& marketplaceId) { + auto wallet = DependencyManager::get(); + QString endpoint = "already_owned"; + QJsonObject request; + request["public_keys"] = QJsonArray::fromStringList(wallet->listPublicKeys()); + request["marketplace_item_id"] = marketplaceId; + send(endpoint, "alreadyOwnedSuccess", "alreadyOwnedFailure", QNetworkAccessManager::PutOperation, AccountManagerAuth::Required, request); +} diff --git a/interface/src/commerce/Ledger.h b/interface/src/commerce/Ledger.h index 1b56c893ab..ac9fe950d9 100644 --- a/interface/src/commerce/Ledger.h +++ b/interface/src/commerce/Ledger.h @@ -35,6 +35,7 @@ public: void certificateInfo(const QString& certificateId); void transferHfcToNode(const QString& hfc_key, const QString& nodeID, const int& amount, const QString& optionalMessage); void transferHfcToUsername(const QString& hfc_key, const QString& username, const int& amount, const QString& optionalMessage); + void alreadyOwned(const QString& marketplaceId); enum CertificateStatus { CERTIFICATE_STATUS_UNKNOWN = 0, @@ -55,6 +56,7 @@ signals: void certificateInfoResult(QJsonObject result); void transferHfcToNodeResult(QJsonObject result); void transferHfcToUsernameResult(QJsonObject result); + void alreadyOwnedResult(QJsonObject result); void updateCertificateStatus(const QString& certID, uint certStatus); @@ -79,6 +81,8 @@ public slots: void transferHfcToNodeFailure(QNetworkReply& reply); void transferHfcToUsernameSuccess(QNetworkReply& reply); void transferHfcToUsernameFailure(QNetworkReply& reply); + void alreadyOwnedSuccess(QNetworkReply& reply); + void alreadyOwnedFailure(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 c9caa393ce..36c1e422c5 100644 --- a/interface/src/commerce/QmlCommerce.cpp +++ b/interface/src/commerce/QmlCommerce.cpp @@ -15,6 +15,8 @@ #include "Ledger.h" #include "Wallet.h" #include +#include +#include QmlCommerce::QmlCommerce() { auto ledger = DependencyManager::get(); @@ -28,9 +30,11 @@ QmlCommerce::QmlCommerce() { connect(ledger.data(), &Ledger::accountResult, this, &QmlCommerce::accountResult); connect(wallet.data(), &Wallet::walletStatusResult, this, &QmlCommerce::walletStatusResult); connect(ledger.data(), &Ledger::certificateInfoResult, this, &QmlCommerce::certificateInfoResult); + connect(ledger.data(), &Ledger::alreadyOwnedResult, this, &QmlCommerce::alreadyOwnedResult); connect(ledger.data(), &Ledger::updateCertificateStatus, this, &QmlCommerce::updateCertificateStatus); connect(ledger.data(), &Ledger::transferHfcToNodeResult, this, &QmlCommerce::transferHfcToNodeResult); connect(ledger.data(), &Ledger::transferHfcToUsernameResult, this, &QmlCommerce::transferHfcToUsernameResult); + connect(ledger.data(), &Ledger::transferHfcToUsernameResult, this, &QmlCommerce::transferHfcToUsernameResult); auto accountManager = DependencyManager::get(); connect(accountManager.data(), &AccountManager::usernameChanged, this, [&]() { @@ -163,3 +167,19 @@ void QmlCommerce::transferHfcToUsername(const QString& username, const int& amou QString key = keys[0]; ledger->transferHfcToUsername(key, username, amount, optionalMessage); } + +void QmlCommerce::replaceContentSet(const QString& itemHref) { + qApp->replaceDomainContent(itemHref); + QJsonObject messageProperties = { + { "status", "SuccessfulRequestToReplaceContent" }, + { "content_set_url", itemHref } + }; + UserActivityLogger::getInstance().logAction("replace_domain_content", messageProperties); + + emit contentSetChanged(itemHref); +} + +void QmlCommerce::alreadyOwned(const QString& marketplaceId) { + auto ledger = DependencyManager::get(); + ledger->alreadyOwned(marketplaceId); +} diff --git a/interface/src/commerce/QmlCommerce.h b/interface/src/commerce/QmlCommerce.h index b261ee6de6..b621608190 100644 --- a/interface/src/commerce/QmlCommerce.h +++ b/interface/src/commerce/QmlCommerce.h @@ -42,12 +42,15 @@ signals: void historyResult(QJsonObject result); void accountResult(QJsonObject result); void certificateInfoResult(QJsonObject result); + void alreadyOwnedResult(QJsonObject result); void updateCertificateStatus(const QString& certID, uint certStatus); void transferHfcToNodeResult(QJsonObject result); void transferHfcToUsernameResult(QJsonObject result); + void contentSetChanged(const QString& contentSetHref); + protected: Q_INVOKABLE void getWalletStatus(); @@ -68,9 +71,13 @@ protected: Q_INVOKABLE void account(); Q_INVOKABLE void certificateInfo(const QString& certificateId); + Q_INVOKABLE void alreadyOwned(const QString& marketplaceId); Q_INVOKABLE void transferHfcToNode(const QString& nodeID, const int& amount, const QString& optionalMessage); Q_INVOKABLE void transferHfcToUsername(const QString& username, const int& amount, const QString& optionalMessage); + + + Q_INVOKABLE void replaceContentSet(const QString& itemHref); }; #endif // hifi_QmlCommerce_h diff --git a/interface/src/scripting/AccountServicesScriptingInterface.h b/interface/src/scripting/AccountServicesScriptingInterface.h index 012c37305d..cfa51697af 100644 --- a/interface/src/scripting/AccountServicesScriptingInterface.h +++ b/interface/src/scripting/AccountServicesScriptingInterface.h @@ -38,7 +38,7 @@ class AccountServicesScriptingInterface : public QObject { Q_PROPERTY(QString username READ getUsername NOTIFY myUsernameChanged) Q_PROPERTY(bool loggedIn READ loggedIn NOTIFY loggedInChanged) Q_PROPERTY(QString findableBy READ getFindableBy WRITE setFindableBy NOTIFY findableByChanged) - Q_PROPERTY(QUrl metaverseServerURL READ getMetaverseServerURL) + Q_PROPERTY(QUrl metaverseServerURL READ getMetaverseServerURL CONSTANT) public: static AccountServicesScriptingInterface* getInstance(); diff --git a/libraries/avatars/src/AvatarData.h b/libraries/avatars/src/AvatarData.h index 033756bfd2..e927120b07 100644 --- a/libraries/avatars/src/AvatarData.h +++ b/libraries/avatars/src/AvatarData.h @@ -373,7 +373,7 @@ class AvatarData : public QObject, public SpatiallyNestable { // The result is unique among all avatars present at the time. Q_PROPERTY(QString sessionDisplayName READ getSessionDisplayName WRITE setSessionDisplayName NOTIFY sessionDisplayNameChanged) Q_PROPERTY(bool lookAtSnappingEnabled MEMBER _lookAtSnappingEnabled NOTIFY lookAtSnappingChanged) - Q_PROPERTY(QString skeletonModelURL READ getSkeletonModelURLFromScript WRITE setSkeletonModelURLFromScript) + Q_PROPERTY(QString skeletonModelURL READ getSkeletonModelURLFromScript WRITE setSkeletonModelURLFromScript NOTIFY skeletonModelURLChanged) Q_PROPERTY(QVector attachmentData READ getAttachmentData WRITE setAttachmentData) Q_PROPERTY(QStringList jointNames READ getJointNames) @@ -702,6 +702,7 @@ public: signals: void displayNameChanged(); void sessionDisplayNameChanged(); + void skeletonModelURLChanged(); void lookAtSnappingChanged(bool enabled); void sessionUUIDChanged(); diff --git a/libraries/avatars/src/ScriptAvatarData.cpp b/libraries/avatars/src/ScriptAvatarData.cpp index 1fd001e536..8491e5368b 100644 --- a/libraries/avatars/src/ScriptAvatarData.cpp +++ b/libraries/avatars/src/ScriptAvatarData.cpp @@ -16,6 +16,7 @@ ScriptAvatarData::ScriptAvatarData(AvatarSharedPointer avatarData) : { QObject::connect(avatarData.get(), &AvatarData::displayNameChanged, this, &ScriptAvatarData::displayNameChanged); QObject::connect(avatarData.get(), &AvatarData::sessionDisplayNameChanged, this, &ScriptAvatarData::sessionDisplayNameChanged); + QObject::connect(avatarData.get(), &AvatarData::skeletonModelURLChanged, this, &ScriptAvatarData::skeletonModelURLChanged); QObject::connect(avatarData.get(), &AvatarData::lookAtSnappingChanged, this, &ScriptAvatarData::lookAtSnappingChanged); } diff --git a/libraries/avatars/src/ScriptAvatarData.h b/libraries/avatars/src/ScriptAvatarData.h index 68074b838d..13713ff15f 100644 --- a/libraries/avatars/src/ScriptAvatarData.h +++ b/libraries/avatars/src/ScriptAvatarData.h @@ -51,7 +51,7 @@ class ScriptAvatarData : public QObject { // // ATTACHMENT AND JOINT PROPERTIES // - Q_PROPERTY(QString skeletonModelURL READ getSkeletonModelURLFromScript) + Q_PROPERTY(QString skeletonModelURL READ getSkeletonModelURLFromScript NOTIFY skeletonModelURLChanged) Q_PROPERTY(QVector attachmentData READ getAttachmentData) Q_PROPERTY(QStringList jointNames READ getJointNames) @@ -132,6 +132,7 @@ public: signals: void displayNameChanged(); void sessionDisplayNameChanged(); + void skeletonModelURLChanged(); void lookAtSnappingChanged(bool enabled); public slots: diff --git a/libraries/entities/src/EntityScriptingInterface.cpp b/libraries/entities/src/EntityScriptingInterface.cpp index 8bdf0252cf..2b44b47128 100644 --- a/libraries/entities/src/EntityScriptingInterface.cpp +++ b/libraries/entities/src/EntityScriptingInterface.cpp @@ -101,6 +101,11 @@ bool EntityScriptingInterface::canWriteAssets() { return nodeList->getThisNodeCanWriteAssets(); } +bool EntityScriptingInterface::canReplaceContent() { + auto nodeList = DependencyManager::get(); + return nodeList->getThisNodeCanReplaceContent(); +} + void EntityScriptingInterface::setEntityTree(EntityTreePointer elementTree) { if (_entityTree) { disconnect(_entityTree.get(), &EntityTree::addingEntity, this, &EntityScriptingInterface::addingEntity); diff --git a/libraries/entities/src/EntityScriptingInterface.h b/libraries/entities/src/EntityScriptingInterface.h index 919d0e3489..cdb784ffa8 100644 --- a/libraries/entities/src/EntityScriptingInterface.h +++ b/libraries/entities/src/EntityScriptingInterface.h @@ -144,6 +144,12 @@ public slots: */ Q_INVOKABLE bool canWriteAssets(); + /**jsdoc + * @function Entities.canReplaceContent + * @return {bool} `true` if the DomainServer will allow this Node/Avatar to replace the domain's content set + */ + Q_INVOKABLE bool canReplaceContent(); + /**jsdoc * Add a new entity with the specified properties. If `clientOnly` is true, the entity will * not be sent to the server and will only be visible/accessible on the local client. diff --git a/libraries/render-utils/src/Shadow.slh b/libraries/render-utils/src/Shadow.slh index abb04a4498..36eb35c757 100644 --- a/libraries/render-utils/src/Shadow.slh +++ b/libraries/render-utils/src/Shadow.slh @@ -89,7 +89,9 @@ float evalShadowCascadeAttenuation(int cascadeIndex, ShadowSampleOffsets offsets float evalShadowAttenuation(vec3 worldLightDir, vec4 worldPosition, float viewDepth, vec3 worldNormal) { ShadowSampleOffsets offsets = evalShadowFilterOffsets(worldPosition); - vec4 cascadeShadowCoords[2] = { vec4(0), vec4(0) }; + vec4 cascadeShadowCoords[2]; + cascadeShadowCoords[0] = vec4(0); + cascadeShadowCoords[1] = vec4(0); ivec2 cascadeIndices; float cascadeMix = determineShadowCascadesOnPixel(worldPosition, viewDepth, cascadeShadowCoords, cascadeIndices); diff --git a/libraries/shared/src/PathUtils.cpp b/libraries/shared/src/PathUtils.cpp index 3bd1f1e9e9..a5841b2f6c 100644 --- a/libraries/shared/src/PathUtils.cpp +++ b/libraries/shared/src/PathUtils.cpp @@ -129,6 +129,7 @@ QUrl PathUtils::expandToAppAbsolutePath(const QUrl& fileUrl) { } return url; } + const QString& PathUtils::qmlBaseUrl() { static const QString staticResourcePath = resourcesUrl() + "qml/"; return staticResourcePath; diff --git a/scripts/system/marketplaces/marketplaces.js b/scripts/system/marketplaces/marketplaces.js index 5ee9de766a..631b5e97ac 100644 --- a/scripts/system/marketplaces/marketplaces.js +++ b/scripts/system/marketplaces/marketplaces.js @@ -75,11 +75,11 @@ var selectionDisplay = null; // for gridTool.js to ignore tablet.pushOntoStack(MARKETPLACE_CHECKOUT_QML_PATH); tablet.sendToQml({ method: 'updateCheckoutQML', params: { - itemId: '0d90d21c-ce7a-4990-ad18-e9d2cf991027', - itemName: 'Test Flaregun', - itemPrice: (debugError ? 10 : 17), - itemHref: 'http://mpassets.highfidelity.com/0d90d21c-ce7a-4990-ad18-e9d2cf991027-v1/flaregun.json', - categories: ["Wearables", "Miscellaneous"] + itemId: '424611a2-73d0-4c03-9087-26a6a279257b', + itemName: '2018-02-15 Finnegon', + itemPrice: (debugError ? 10 : 3), + itemHref: 'http://devmpassets.highfidelity.com/424611a2-73d0-4c03-9087-26a6a279257b-v1/finnigon.fst', + categories: ["Miscellaneous"] } }); } @@ -233,13 +233,19 @@ var selectionDisplay = null; // for gridTool.js to ignore return position; } - function rezEntity(itemHref, isWearable) { + function rezEntity(itemHref, itemType) { + var isWearable = itemType === "wearable"; var success = Clipboard.importEntities(itemHref); var wearableLocalPosition = null; var wearableLocalRotation = null; var wearableLocalDimensions = null; var wearableDimensions = null; + if (itemType === "contentSet") { + console.log("Item is a content set; codepath shouldn't go here.") + return; + } + if (isWearable) { var wearableTransforms = Settings.getValue("io.highfidelity.avatarStore.checkOut.transforms"); if (!wearableTransforms) { @@ -545,7 +551,11 @@ var selectionDisplay = null; // for gridTool.js to ignore break; case 'checkout_rezClicked': case 'purchases_rezClicked': - rezEntity(message.itemHref, message.isWearable); + if (message.itemType === "app") { + console.log("How did you get here? You can't buy apps yet!"); + } else { + rezEntity(message.itemHref, message.itemType); + } break; case 'header_marketplaceImageClicked': case 'purchases_backClicked':