From f53aba2a3216d6bb5106dc5adda574134fe63b47 Mon Sep 17 00:00:00 2001 From: Zach Fox Date: Fri, 2 Mar 2018 15:53:06 -0800 Subject: [PATCH 01/32] Initial progress --- .../qml/hifi/commerce/checkout/Checkout.qml | 21 +- .../hifi/commerce/purchases/PurchasedItem.qml | 960 +++++++++--------- .../qml/hifi/commerce/purchases/Purchases.qml | 4 + scripts/system/html/js/marketplacesInject.js | 38 +- scripts/system/marketplaces/marketplaces.js | 6 +- 5 files changed, 566 insertions(+), 463 deletions(-) diff --git a/interface/resources/qml/hifi/commerce/checkout/Checkout.qml b/interface/resources/qml/hifi/commerce/checkout/Checkout.qml index 372fb3c774..328b662564 100644 --- a/interface/resources/qml/hifi/commerce/checkout/Checkout.qml +++ b/interface/resources/qml/hifi/commerce/checkout/Checkout.qml @@ -49,6 +49,7 @@ Rectangle { property bool canRezCertifiedItems: Entities.canRezCertified() || Entities.canRezTmpCertified(); property string referrer; property bool isInstalled; + property bool isUpdating; // Style color: hifi.colors.white; Connections { @@ -413,6 +414,7 @@ Rectangle { // "HFC" balance label HiFiGlyphs { id: itemPriceTextLabel; + visible: !root.isUpdating; text: hifi.glyphs.hfc; // Size size: 30; @@ -428,7 +430,7 @@ Rectangle { } FiraSansSemiBold { id: itemPriceText; - text: (root.itemPrice === -1) ? "--" : root.itemPrice; + text: root.isUpdating ? "FREE\nUPGRADE" : ((root.itemPrice === -1) ? "--" : root.itemPrice); // Text size size: 26; // Anchors @@ -549,8 +551,8 @@ Rectangle { height: 50; anchors.left: parent.left; anchors.right: parent.right; - text: ((root.isCertified) ? ((ownershipStatusReceived && balanceReceived) ? - (viewInMyPurchasesButton.visible ? "Buy It Again" : "Confirm Purchase") : "--") : "Get Item"); + text: root.isUpdating ? "CONFIRM UPDATE" : (((root.isCertified) ? ((ownershipStatusReceived && balanceReceived) ? + (viewInMyPurchasesButton.visible ? "Buy It Again" : "Confirm Purchase") : "--") : "Get Item")); onClicked: { if (root.isCertified) { if (!root.shouldBuyWithControlledFailure) { @@ -1002,6 +1004,7 @@ Rectangle { function fromScript(message) { switch (message.method) { case 'updateCheckoutQML': + root.isUpdating = message.params.isUpdating; itemId = message.params.itemId; itemName = message.params.itemName; root.itemPrice = message.params.itemPrice; @@ -1019,7 +1022,7 @@ Rectangle { function refreshBuyUI() { if (root.isCertified) { if (root.ownershipStatusReceived && root.balanceReceived) { - if (root.balanceAfterPurchase < 0) { + if (root.balanceAfterPurchase < 0 && !root.isUpdating) { if (root.alreadyOwned) { buyText.text = "Your Wallet does not have sufficient funds to purchase this item again."; viewInMyPurchasesButton.visible = true; @@ -1031,13 +1034,19 @@ Rectangle { buyGlyph.text = hifi.glyphs.alert; buyGlyph.size = 54; } else { - if (root.alreadyOwned) { + if (root.alreadyOwned && !root.isUpdating) { viewInMyPurchasesButton.visible = true; } else { buyText.text = ""; } - if (root.itemType === "contentSet" && !Entities.canReplaceContent()) { + if (root.isUpdating) { + buyText.text = "By agreeing to update, you agree to trade in your old item for the update that replaces it."; + buyTextContainer.color = "#FFC3CD"; + buyTextContainer.border.color = "#F3808F"; + buyGlyph.text = hifi.glyphs.alert; + buyGlyph.size = 54; + } else 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"; diff --git a/interface/resources/qml/hifi/commerce/purchases/PurchasedItem.qml b/interface/resources/qml/hifi/commerce/purchases/PurchasedItem.qml index fb8e509cde..d8e02b08e5 100644 --- a/interface/resources/qml/hifi/commerce/purchases/PurchasedItem.qml +++ b/interface/resources/qml/hifi/commerce/purchases/PurchasedItem.qml @@ -48,11 +48,13 @@ Item { property bool hasPermissionToRezThis; property bool permissionExplanationCardVisible; property bool isInstalled; + property string upgradeUrl; + property string upgradeTitle; property string originalStatusText; property string originalStatusColor; - height: 110; + height: root.upgradeUrl === "" ? 110 : 150; width: parent.width; Connections { @@ -137,500 +139,548 @@ Item { anchors.verticalCenter: parent.verticalCenter; height: root.height - 10; - Image { - id: itemPreviewImage; - source: root.itemPreviewImageUrl; + Item { + id: itemContainer; anchors.left: parent.left; + anchors.right: parent.right; anchors.top: parent.top; - anchors.bottom: parent.bottom; - width: height; - fillMode: Image.PreserveAspectCrop; + height: 100; - MouseArea { - anchors.fill: parent; - onClicked: { - sendToPurchases({method: 'purchases_itemInfoClicked', itemId: root.itemId}); - } - } - } + Image { + id: itemPreviewImage; + source: root.itemPreviewImageUrl; + anchors.left: parent.left; + anchors.top: parent.top; + anchors.bottom: parent.bottom; + width: height; + fillMode: Image.PreserveAspectCrop; - 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; - 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; - // 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; - } - } - } - 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 { - "" + MouseArea { + anchors.fill: parent; + onClicked: { + sendToPurchases({method: 'purchases_itemInfoClicked', itemId: root.itemId}); } } - 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; + } + + 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; + 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; + // Style + color: hifi.colors.blueAccent; + text: root.itemName; + elide: Text.ElideRight; + // Alignment + horizontalAlignment: Text.AlignLeft; verticalAlignment: Text.AlignVCenter; - onLinkActivated: { - sendToPurchases({method: 'showPermissionsExplanation', itemType: root.itemType}); + 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; + } } } - // "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; + 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: { - parent.text = hifi.glyphs.closeInverted; + noPermissionGlyph.color = hifi.colors.redHighlight; } onExited: { - parent.text = hifi.glyphs.close; + noPermissionGlyph.color = hifi.colors.redAccent; } onClicked: { - root.sendToPurchases({ method: 'openPermissionExplanationCard', closeAll: true }); + 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; + anchors.top: itemName.bottom; + anchors.topMargin: 4; + anchors.left: itemName.left; + anchors.right: buttonContainer.left; + anchors.rightMargin: 2; + height: 24; + + HiFiGlyphs { + id: certificateIcon; + text: hifi.glyphs.scriptNew; + // Size + size: 30; + // Anchors + anchors.top: parent.top; + anchors.left: parent.left; + anchors.bottom: parent.bottom; + width: 32; + // Style + color: hifi.colors.black; + } + + RalewayRegular { + id: viewCertificateText; + text: "VIEW CERTIFICATE"; + size: 13; + anchors.left: certificateIcon.right; + anchors.leftMargin: 4; + anchors.top: parent.top; + anchors.bottom: parent.bottom; + anchors.right: parent.right; + color: hifi.colors.black; + } + + MouseArea { + anchors.fill: parent; + hoverEnabled: enabled; + onClicked: { + sendToPurchases({method: 'purchases_itemCertificateClicked', itemCertificateId: root.certificateId}); + } + onEntered: { + certificateIcon.color = hifi.colors.lightGray; + viewCertificateText.color = hifi.colors.lightGray; + } + onExited: { + certificateIcon.color = hifi.colors.black; + viewCertificateText.color = hifi.colors.black; + } + } + } + + Item { + id: editionContainer; + visible: root.displayedItemCount > 1 && !statusContainer.visible; + anchors.left: itemName.left; + anchors.top: certificateContainer.bottom; + anchors.topMargin: 8; + anchors.bottom: parent.bottom; + anchors.right: buttonContainer.left; + anchors.rightMargin: 2; + + RalewayRegular { + anchors.left: parent.left; + anchors.top: parent.top; + anchors.bottom: parent.bottom; + width: paintedWidth; + text: "#" + root.itemEdition; + size: 13; + color: hifi.colors.black; + verticalAlignment: Text.AlignTop; + } + } + + Item { + id: statusContainer; + visible: root.purchaseStatus === "pending" || root.purchaseStatus === "invalidated" || root.purchaseStatusChanged; + anchors.left: itemName.left; + anchors.top: certificateContainer.bottom; + anchors.topMargin: 8; + anchors.bottom: parent.bottom; + anchors.right: buttonContainer.left; + anchors.rightMargin: 2; + + RalewaySemiBold { + id: statusText; + anchors.left: parent.left; + anchors.top: parent.top; + anchors.bottom: parent.bottom; + width: paintedWidth; + text: { + if (root.purchaseStatus === "pending") { + "PENDING..." + } else if (root.purchaseStatus === "invalidated") { + "INVALIDATED" + } else if (root.numberSold !== -1) { + ("Sales: " + root.numberSold + "/" + (root.limitedRun === -1 ? "\u221e" : root.limitedRun)) + } else { + "" + } + } + size: 18; + color: { + if (root.purchaseStatus === "pending") { + hifi.colors.blueAccent + } else if (root.purchaseStatus === "invalidated") { + hifi.colors.redAccent + } else { + hifi.colors.baseGray + } + } + verticalAlignment: Text.AlignTop; + } + + HiFiGlyphs { + id: statusIcon; + text: { + if (root.purchaseStatus === "pending") { + hifi.glyphs.question + } else if (root.purchaseStatus === "invalidated") { + hifi.glyphs.question + } else { + "" + } + } + // Size + size: 36; + // Anchors + anchors.top: parent.top; + anchors.topMargin: -8; + anchors.left: statusText.right; + anchors.bottom: parent.bottom; + // Style + color: { + if (root.purchaseStatus === "pending") { + hifi.colors.blueAccent + } else if (root.purchaseStatus === "invalidated") { + hifi.colors.redAccent + } else { + hifi.colors.baseGray + } + } + verticalAlignment: Text.AlignTop; + } + + MouseArea { + anchors.fill: parent; + hoverEnabled: enabled; + onClicked: { + if (root.purchaseStatus === "pending") { + sendToPurchases({method: 'showPendingLightbox'}); + } else if (root.purchaseStatus === "invalidated") { + sendToPurchases({method: 'showInvalidatedLightbox'}); + } + } + onEntered: { + if (root.purchaseStatus === "pending") { + statusText.color = hifi.colors.blueHighlight; + statusIcon.color = hifi.colors.blueHighlight; + } else if (root.purchaseStatus === "invalidated") { + statusText.color = hifi.colors.redAccent; + statusIcon.color = hifi.colors.redAccent; + } + } + onExited: { + if (root.purchaseStatus === "pending") { + statusText.color = hifi.colors.blueAccent; + statusIcon.color = hifi.colors.blueAccent; + } else if (root.purchaseStatus === "invalidated") { + statusText.color = hifi.colors.redHighlight; + statusIcon.color = hifi.colors.redHighlight; + } + } + } + } + + Rectangle { + id: rezzedNotifContainer; + z: 998; + visible: false; + color: "#1FC6A6"; + anchors.fill: buttonContainer; + MouseArea { + anchors.fill: parent; + propagateComposedEvents: false; + hoverEnabled: true; + } + + RalewayBold { + anchors.fill: parent; + text: (root.buttonTextClicked)[itemTypesArray.indexOf(root.itemType)]; + size: 15; + color: hifi.colors.white; + verticalAlignment: Text.AlignVCenter; + horizontalAlignment: Text.AlignHCenter; + } + + Timer { + id: rezzedNotifContainerTimer; + interval: 2000; + onTriggered: rezzedNotifContainer.visible = false + } + } + + Rectangle { + id: appButtonContainer; + color: hifi.colors.white; + z: 994; + visible: root.isInstalled; + anchors.fill: buttonContainer; + + HifiControlsUit.Button { + id: openAppButton; + color: hifi.buttons.blue; + colorScheme: hifi.colorSchemes.light; + anchors.top: parent.top; + anchors.right: parent.right; + anchors.left: parent.left; + width: 92; + height: 44; + text: "OPEN" + onClicked: { + Commerce.openApp(root.itemHref); + } + } + + HifiControlsUit.Button { + id: uninstallAppButton; + color: hifi.buttons.noneBorderless; + colorScheme: hifi.colorSchemes.light; + anchors.bottom: parent.bottom; + anchors.right: parent.right; + anchors.left: parent.left; + height: 44; + text: "UNINSTALL" + onClicked: { + Commerce.uninstallApp(root.itemHref); + } + } + } + + Button { + id: buttonContainer; + 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.hasPermissionToRezThis && + root.purchaseStatus !== "invalidated" && + MyAvatar.skeletonModelURL !== root.itemHref; + + onHoveredChanged: { + if (hovered) { + Tablet.playSound(TabletEnums.ButtonHover); + } + } + + onFocusChanged: { + if (focus) { + Tablet.playSound(TabletEnums.ButtonHover); + } + } + + onClicked: { + 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 if (root.itemType === "app") { + // "Run" and "Uninstall" buttons are separate. + Commerce.installApp(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 + color: { + if (!control.enabled) { + hifi.buttons.disabledColorStart[control.colorScheme] + } else if (control.pressed) { + hifi.buttons.pressedColor[control.color] + } else if (control.hovered) { + hifi.buttons.hoveredColor[control.color] + } else { + hifi.buttons.colorStart[control.color] + } + } + } + GradientStop { + position: 1.0 + color: { + if (!control.enabled) { + hifi.buttons.disabledColorFinish[control.colorScheme] + } else if (control.pressed) { + hifi.buttons.pressedColor[control.color] + } else if (control.hovered) { + hifi.buttons.hoveredColor[control.color] + } else { + hifi.buttons.colorFinish[control.color] + } + } + } + } + } + + label: Item { + HiFiGlyphs { + id: rezIcon; + text: (root.buttonGlyph)[itemTypesArray.indexOf(root.itemType)]; + // Size + size: 60; + // Anchors + anchors.top: parent.top; + anchors.topMargin: 0; + anchors.left: parent.left; + anchors.right: parent.right; + horizontalAlignment: Text.AlignHCenter; + // Style + color: enabled ? hifi.buttons.textColor[control.color] + : hifi.buttons.disabledTextColor[control.colorScheme] + } + RalewayBold { + 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: 15; + verticalAlignment: Text.AlignVCenter + horizontalAlignment: Text.AlignHCenter + text: MyAvatar.skeletonModelURL === root.itemHref ? "CURRENT" : (root.buttonTextNormal)[itemTypesArray.indexOf(root.itemType)]; + } } } } } - Item { - id: certificateContainer; - anchors.top: itemName.bottom; - anchors.topMargin: 4; - anchors.left: itemName.left; - anchors.right: buttonContainer.left; - anchors.rightMargin: 2; - height: 24; - - HiFiGlyphs { - id: certificateIcon; - text: hifi.glyphs.scriptNew; - // Size - size: 30; - // Anchors - anchors.top: parent.top; - anchors.left: parent.left; - anchors.bottom: parent.bottom; - width: 32; - // Style - color: hifi.colors.black; - } - - RalewayRegular { - id: viewCertificateText; - text: "VIEW CERTIFICATE"; - size: 13; - anchors.left: certificateIcon.right; - anchors.leftMargin: 4; - anchors.top: parent.top; - anchors.bottom: parent.bottom; - anchors.right: parent.right; - color: hifi.colors.black; - } - - MouseArea { - anchors.fill: parent; - hoverEnabled: enabled; - onClicked: { - sendToPurchases({method: 'purchases_itemCertificateClicked', itemCertificateId: root.certificateId}); - } - onEntered: { - certificateIcon.color = hifi.colors.lightGray; - viewCertificateText.color = hifi.colors.lightGray; - } - onExited: { - certificateIcon.color = hifi.colors.black; - viewCertificateText.color = hifi.colors.black; - } - } - } - - Item { - id: editionContainer; - visible: root.displayedItemCount > 1 && !statusContainer.visible; - anchors.left: itemName.left; - anchors.top: certificateContainer.bottom; - anchors.topMargin: 8; + Rectangle { + id: upgradeAvailableContainer; + visible: root.upgradeUrl !== ""; + anchors.top: itemContainer.bottom; anchors.bottom: parent.bottom; - anchors.right: buttonContainer.left; - anchors.rightMargin: 2; + anchors.left: parent.left; + anchors.right: parent.right; + color: "#E7F8FB"; RalewayRegular { + id: updateAvailableText; + text: "UPDATE AVAILABLE"; + size: 13; anchors.left: parent.left; + anchors.leftMargin: 12; anchors.top: parent.top; anchors.bottom: parent.bottom; width: paintedWidth; - text: "#" + root.itemEdition; - size: 13; color: hifi.colors.black; - verticalAlignment: Text.AlignTop; + verticalAlignment: Text.AlignVCenter; } - } - - Item { - id: statusContainer; - visible: root.purchaseStatus === "pending" || root.purchaseStatus === "invalidated" || root.purchaseStatusChanged; - anchors.left: itemName.left; - anchors.top: certificateContainer.bottom; - anchors.topMargin: 8; - anchors.bottom: parent.bottom; - anchors.right: buttonContainer.left; - anchors.rightMargin: 2; RalewaySemiBold { - id: statusText; - anchors.left: parent.left; + id: updateNowText; + text: "Update this item now"; + size: 13; + anchors.left: updateAvailableText.right; + anchors.leftMargin: 16; anchors.top: parent.top; anchors.bottom: parent.bottom; width: paintedWidth; - text: { - if (root.purchaseStatus === "pending") { - "PENDING..." - } else if (root.purchaseStatus === "invalidated") { - "INVALIDATED" - } else if (root.numberSold !== -1) { - ("Sales: " + root.numberSold + "/" + (root.limitedRun === -1 ? "\u221e" : root.limitedRun)) - } else { - "" - } - } - size: 18; - color: { - if (root.purchaseStatus === "pending") { - hifi.colors.blueAccent - } else if (root.purchaseStatus === "invalidated") { - hifi.colors.redAccent - } else { - hifi.colors.baseGray - } - } - verticalAlignment: Text.AlignTop; - } - - HiFiGlyphs { - id: statusIcon; - text: { - if (root.purchaseStatus === "pending") { - hifi.glyphs.question - } else if (root.purchaseStatus === "invalidated") { - hifi.glyphs.question - } else { - "" - } - } - // Size - size: 36; - // Anchors - anchors.top: parent.top; - anchors.topMargin: -8; - anchors.left: statusText.right; - anchors.bottom: parent.bottom; - // Style - color: { - if (root.purchaseStatus === "pending") { - hifi.colors.blueAccent - } else if (root.purchaseStatus === "invalidated") { - hifi.colors.redAccent - } else { - hifi.colors.baseGray - } - } - verticalAlignment: Text.AlignTop; - } - - MouseArea { - anchors.fill: parent; - hoverEnabled: enabled; - onClicked: { - if (root.purchaseStatus === "pending") { - sendToPurchases({method: 'showPendingLightbox'}); - } else if (root.purchaseStatus === "invalidated") { - sendToPurchases({method: 'showInvalidatedLightbox'}); - } - } - onEntered: { - if (root.purchaseStatus === "pending") { - statusText.color = hifi.colors.blueHighlight; - statusIcon.color = hifi.colors.blueHighlight; - } else if (root.purchaseStatus === "invalidated") { - statusText.color = hifi.colors.redAccent; - statusIcon.color = hifi.colors.redAccent; - } - } - onExited: { - if (root.purchaseStatus === "pending") { - statusText.color = hifi.colors.blueAccent; - statusIcon.color = hifi.colors.blueAccent; - } else if (root.purchaseStatus === "invalidated") { - statusText.color = hifi.colors.redHighlight; - statusIcon.color = hifi.colors.redHighlight; - } - } - } - } - - Rectangle { - id: rezzedNotifContainer; - z: 998; - visible: false; - color: "#1FC6A6"; - anchors.fill: buttonContainer; - MouseArea { - anchors.fill: parent; - propagateComposedEvents: false; - hoverEnabled: true; - } - - RalewayBold { - anchors.fill: parent; - text: (root.buttonTextClicked)[itemTypesArray.indexOf(root.itemType)]; - size: 15; - color: hifi.colors.white; + color: hifi.colors.black; verticalAlignment: Text.AlignVCenter; - horizontalAlignment: Text.AlignHCenter; - } - Timer { - id: rezzedNotifContainerTimer; - interval: 2000; - onTriggered: rezzedNotifContainer.visible = false - } - } - - Rectangle { - id: appButtonContainer; - color: hifi.colors.white; - z: 994; - visible: root.isInstalled; - anchors.fill: buttonContainer; - - HifiControlsUit.Button { - id: openAppButton; - color: hifi.buttons.blue; - colorScheme: hifi.colorSchemes.light; - anchors.top: parent.top; - anchors.right: parent.right; - anchors.left: parent.left; - width: 92; - height: 44; - text: "OPEN" - onClicked: { - Commerce.openApp(root.itemHref); - } - } - - HifiControlsUit.Button { - id: uninstallAppButton; - color: hifi.buttons.noneBorderless; - colorScheme: hifi.colorSchemes.light; - anchors.bottom: parent.bottom; - anchors.right: parent.right; - anchors.left: parent.left; - height: 44; - text: "UNINSTALL" - onClicked: { - Commerce.uninstallApp(root.itemHref); - } - } - } - - Button { - id: buttonContainer; - 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.hasPermissionToRezThis && - root.purchaseStatus !== "invalidated" && - MyAvatar.skeletonModelURL !== root.itemHref; - - onHoveredChanged: { - if (hovered) { - Tablet.playSound(TabletEnums.ButtonHover); - } - } - - onFocusChanged: { - if (focus) { - Tablet.playSound(TabletEnums.ButtonHover); - } - } - - onClicked: { - 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 if (root.itemType === "app") { - // "Run" and "Uninstall" buttons are separate. - Commerce.installApp(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 - color: { - if (!control.enabled) { - hifi.buttons.disabledColorStart[control.colorScheme] - } else if (control.pressed) { - hifi.buttons.pressedColor[control.color] - } else if (control.hovered) { - hifi.buttons.hoveredColor[control.color] - } else { - hifi.buttons.colorStart[control.color] - } - } - } - GradientStop { - position: 1.0 - color: { - if (!control.enabled) { - hifi.buttons.disabledColorFinish[control.colorScheme] - } else if (control.pressed) { - hifi.buttons.pressedColor[control.color] - } else if (control.hovered) { - hifi.buttons.hoveredColor[control.color] - } else { - hifi.buttons.colorFinish[control.color] - } - } - } - } - } - - label: Item { - HiFiGlyphs { - id: rezIcon; - text: (root.buttonGlyph)[itemTypesArray.indexOf(root.itemType)]; - // Size - size: 60; - // Anchors - anchors.top: parent.top; - anchors.topMargin: 0; - anchors.left: parent.left; - anchors.right: parent.right; - horizontalAlignment: Text.AlignHCenter; - // Style - color: enabled ? hifi.buttons.textColor[control.color] - : hifi.buttons.disabledTextColor[control.colorScheme] - } - RalewayBold { - 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: 15; - verticalAlignment: Text.AlignVCenter - horizontalAlignment: Text.AlignHCenter - text: MyAvatar.skeletonModelURL === root.itemHref ? "CURRENT" : (root.buttonTextNormal)[itemTypesArray.indexOf(root.itemType)]; - } + onLinkActivated: { + sendToPurchases({method: 'updateItemClicked', itemId: root.itemId, itemEdition: root.itemEdition, upgradeUrl: root.upgradeUrl}); } } } diff --git a/interface/resources/qml/hifi/commerce/purchases/Purchases.qml b/interface/resources/qml/hifi/commerce/purchases/Purchases.qml index c505baebf4..8952aad0cf 100644 --- a/interface/resources/qml/hifi/commerce/purchases/Purchases.qml +++ b/interface/resources/qml/hifi/commerce/purchases/Purchases.qml @@ -398,6 +398,8 @@ Rectangle { displayedItemCount: model.displayedItemCount; permissionExplanationCardVisible: model.permissionExplanationCardVisible; isInstalled: model.isInstalled; + upgradeUrl: model.upgrade_url; + upgradeTitle: model.upgrade_title; itemType: { if (model.root_file_url.indexOf(".fst") > -1) { "avatar"; @@ -485,6 +487,8 @@ Rectangle { filteredPurchasesModel.setProperty(i, "permissionExplanationCardVisible", true); } } + } else if (msg.method === "updateItemClicked") { + sendToScript(msg); } } } diff --git a/scripts/system/html/js/marketplacesInject.js b/scripts/system/html/js/marketplacesInject.js index fb49de1050..35f89a4a19 100644 --- a/scripts/system/html/js/marketplacesInject.js +++ b/scripts/system/html/js/marketplacesInject.js @@ -246,6 +246,7 @@ function buyButtonClicked(id, name, author, price, href, referrer) { EventBridge.emitWebEvent(JSON.stringify({ type: "CHECKOUT", + isUpdating: false, itemId: id, itemName: name, itemPrice: price ? parseInt(price, 10) : 0, @@ -255,6 +256,29 @@ })); } + function updateButtonClicked(id, name, author, href, referrer) { + EventBridge.emitWebEvent(JSON.stringify({ + type: "UPDATE", + isUpdating: true, + itemId: id, + itemName: name, + itemHref: href, + referrer: referrer, + itemAuthor: author + })); + } + + // From https://stackoverflow.com/questions/901115/how-can-i-get-query-string-values-in-javascript + function getParameterByName(name, url) { + if (!url) url = window.location.href; + name = name.replace(/[\[\]]/g, "\\$&"); + var regex = new RegExp("[?&]" + name + "(=([^&#]*)|&|#|$)"), + results = regex.exec(url); + if (!results) return null; + if (!results[2]) return ''; + return decodeURIComponent(results[2].replace(/\+/g, " ")); + } + function injectBuyButtonOnMainPage() { var cost; @@ -412,13 +436,25 @@ var cost = $('.item-cost').text(); if (availability !== 'available') { purchaseButton.html('UNAVAILABLE (' + availability + ')'); + } else if (url.indexOf('edition=' != -1)) { + purchaseButton.html('UPDATE FOR FREE'); } else if (parseInt(cost) > 0 && $('#side-info').find('#buyItemButton').size() === 0) { purchaseButton.html('PURCHASE ' + cost); } purchaseButton.on('click', function () { - if ('available' === availability) { + if (url.indexOf('edition=' != -1)) { + if (url.indexOf('upgradeUrl=' === -1)) { + console.log("ERROR! Item is an upgrade, but no upgradeUrl was specified."); + } else { + updateButtonClicked(window.location.pathname.split("/")[3], + $('#top-center').find('h1').text(), + $('#creator').find('.value').text(), + getParameterByName('upgradeUrl'), + "itemPage"); + } + } else if ('available' === availability) { buyButtonClicked(window.location.pathname.split("/")[3], $('#top-center').find('h1').text(), $('#creator').find('.value').text(), diff --git a/scripts/system/marketplaces/marketplaces.js b/scripts/system/marketplaces/marketplaces.js index 8f51d88f2d..dafd3e525d 100644 --- a/scripts/system/marketplaces/marketplaces.js +++ b/scripts/system/marketplaces/marketplaces.js @@ -417,7 +417,7 @@ var selectionDisplay = null; // for gridTool.js to ignore isDownloadBeingCancelled = false; } else { var parsedJsonMessage = JSON.parse(message); - if (parsedJsonMessage.type === "CHECKOUT") { + if (parsedJsonMessage.type === "CHECKOUT" || parsedJsonMessage.type === "UPDATE") { wireEventBridge(true); tablet.pushOntoStack(MARKETPLACE_CHECKOUT_QML_PATH); tablet.sendToQml({ @@ -560,6 +560,10 @@ var selectionDisplay = null; // for gridTool.js to ignore case 'purchases_goToMarketplaceClicked': tablet.gotoWebScreen(MARKETPLACE_URL_INITIAL, MARKETPLACES_INJECT_SCRIPT_URL); break; + case 'updateItemClicked': + tablet.gotoWebScreen(MARKETPLACE_URL + '/items/' + message.itemId + "?edition=" + message.itemEdition + "&upgradeUrl=" + message.upgradeUrl, + MARKETPLACES_INJECT_SCRIPT_URL); + break; case 'passphrasePopup_cancelClicked': case 'needsLogIn_cancelClicked': tablet.gotoWebScreen(MARKETPLACE_URL_INITIAL, MARKETPLACES_INJECT_SCRIPT_URL); From f10f9aa939cd774ba5fe7594c3162b1208cf0aae Mon Sep 17 00:00:00 2001 From: Zach Fox Date: Mon, 5 Mar 2018 10:01:40 -0800 Subject: [PATCH 02/32] Continued progress --- .../qml/hifi/commerce/checkout/Checkout.qml | 27 +++++++++++++++---- interface/src/commerce/Ledger.cpp | 9 +++++++ interface/src/commerce/Ledger.h | 4 +++ interface/src/commerce/QmlCommerce.cpp | 7 ++++- interface/src/commerce/QmlCommerce.h | 3 +++ scripts/system/html/js/marketplacesInject.js | 14 ++++------ scripts/system/marketplaces/marketplaces.js | 2 +- 7 files changed, 50 insertions(+), 16 deletions(-) diff --git a/interface/resources/qml/hifi/commerce/checkout/Checkout.qml b/interface/resources/qml/hifi/commerce/checkout/Checkout.qml index 328b662564..dd0c341412 100644 --- a/interface/resources/qml/hifi/commerce/checkout/Checkout.qml +++ b/interface/resources/qml/hifi/commerce/checkout/Checkout.qml @@ -30,6 +30,7 @@ Rectangle { property string activeView: "initialize"; property bool ownershipStatusReceived: false; property bool balanceReceived: false; + property bool availableUpdatesReceived: false; property string itemName; property string itemId; property string itemHref; @@ -130,11 +131,24 @@ Rectangle { root.isInstalled = true; } } + + onAvailableUpdatesResult: { + if (result.status !== 'success') { + console.log("Failed to get Available Updates", result.data.message); + } else { + root.availableUpdatesReceived = true; + if (result.data.updates.indexOf(root.itemId) > -1) { + root.isUpdating = true; + } + } + } } onItemIdChanged: { root.ownershipStatusReceived = false; Commerce.alreadyOwned(root.itemId); + root.availableUpdatesReceived = false; + Commerce.getAvailableUpdates(); itemPreviewImage.source = "https://hifi-metaverse.s3-us-west-1.amazonaws.com/marketplace/previews/" + itemId + "/thumbnail/hifi-mp-" + itemId + ".jpg"; } @@ -242,6 +256,7 @@ Rectangle { Component.onCompleted: { ownershipStatusReceived = false; balanceReceived = false; + availableUpdatesReceived = false; Commerce.getWalletStatus(); } } @@ -318,7 +333,7 @@ Rectangle { Rectangle { id: loading; z: 997; - visible: !root.ownershipStatusReceived || !root.balanceReceived; + visible: !root.ownershipStatusReceived || !root.balanceReceived || !root.availableUpdatesReceived; anchors.fill: parent; color: hifi.colors.white; @@ -542,7 +557,7 @@ Rectangle { HifiControlsUit.Button { id: buyButton; visible: !((root.itemType === "avatar" || root.itemType === "app") && viewInMyPurchasesButton.visible) - enabled: (root.balanceAfterPurchase >= 0 && ownershipStatusReceived && balanceReceived) || (!root.isCertified); + enabled: (root.balanceAfterPurchase >= 0 && ownershipStatusReceived && balanceReceived && availableUpdatesReceived) || (!root.isCertified); color: viewInMyPurchasesButton.visible ? hifi.buttons.white : hifi.buttons.blue; colorScheme: hifi.colorSchemes.light; anchors.top: viewInMyPurchasesButton.visible ? viewInMyPurchasesButton.bottom : @@ -551,7 +566,7 @@ Rectangle { height: 50; anchors.left: parent.left; anchors.right: parent.right; - text: root.isUpdating ? "CONFIRM UPDATE" : (((root.isCertified) ? ((ownershipStatusReceived && balanceReceived) ? + text: root.isUpdating ? "CONFIRM UPDATE" : (((root.isCertified) ? ((ownershipStatusReceived && balanceReceived && availableUpdatesReceived) ? (viewInMyPurchasesButton.visible ? "Buy It Again" : "Confirm Purchase") : "--") : "Get Item")); onClicked: { if (root.isCertified) { @@ -978,7 +993,7 @@ Rectangle { buyButton.color = hifi.buttons.red; root.shouldBuyWithControlledFailure = true; } else { - buyButton.text = (root.isCertified ? ((ownershipStatusReceived && balanceReceived) ? (root.alreadyOwned ? "Buy Another" : "Buy"): "--") : "Get Item"); + buyButton.text = (root.isCertified ? ((ownershipStatusReceived && balanceReceived && availableUpdatesReceived) ? (root.alreadyOwned ? "Buy Another" : "Buy"): "--") : "Get Item"); buyButton.color = hifi.buttons.blue; root.shouldBuyWithControlledFailure = false; } @@ -1021,7 +1036,7 @@ Rectangle { function refreshBuyUI() { if (root.isCertified) { - if (root.ownershipStatusReceived && root.balanceReceived) { + if (root.ownershipStatusReceived && root.balanceReceived && root.availableUpdatesReceived) { if (root.balanceAfterPurchase < 0 && !root.isUpdating) { if (root.alreadyOwned) { buyText.text = "Your Wallet does not have sufficient funds to purchase this item again."; @@ -1075,8 +1090,10 @@ Rectangle { } root.balanceReceived = false; root.ownershipStatusReceived = false; + root.availableUpdatesReceived = false; Commerce.alreadyOwned(root.itemId); Commerce.balance(); + Commerce.getAvailableUpdates(); } // diff --git a/interface/src/commerce/Ledger.cpp b/interface/src/commerce/Ledger.cpp index 712c505e8a..62411a095a 100644 --- a/interface/src/commerce/Ledger.cpp +++ b/interface/src/commerce/Ledger.cpp @@ -52,6 +52,7 @@ Handler(inventory) Handler(transferHfcToNode) Handler(transferHfcToUsername) Handler(alreadyOwned) +Handler(availableUpdates) void Ledger::send(const QString& endpoint, const QString& success, const QString& fail, QNetworkAccessManager::Operation method, AccountManagerAuth::Type authType, QJsonObject request) { auto accountManager = DependencyManager::get(); @@ -363,3 +364,11 @@ void Ledger::alreadyOwned(const QString& marketplaceId) { request["marketplace_item_id"] = marketplaceId; send(endpoint, "alreadyOwnedSuccess", "alreadyOwnedFailure", QNetworkAccessManager::PutOperation, AccountManagerAuth::Required, request); } + +void Ledger::getAvailableUpdates() { + auto wallet = DependencyManager::get(); + QString endpoint = "available_updates"; + QJsonObject request; + request["public_keys"] = QJsonArray::fromStringList(wallet->listPublicKeys()); + send(endpoint, "availableUpdatesSuccess", "availableUpdatesFailure", QNetworkAccessManager::PutOperation, AccountManagerAuth::Required, request); +} diff --git a/interface/src/commerce/Ledger.h b/interface/src/commerce/Ledger.h index 703ebda2dc..04c618d665 100644 --- a/interface/src/commerce/Ledger.h +++ b/interface/src/commerce/Ledger.h @@ -36,6 +36,7 @@ public: 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); + void getAvailableUpdates(); enum CertificateStatus { CERTIFICATE_STATUS_UNKNOWN = 0, @@ -57,6 +58,7 @@ signals: void transferHfcToNodeResult(QJsonObject result); void transferHfcToUsernameResult(QJsonObject result); void alreadyOwnedResult(QJsonObject result); + void availableUpdatesResult(QJsonObject result); void updateCertificateStatus(const QString& certID, uint certStatus); @@ -83,6 +85,8 @@ public slots: void transferHfcToUsernameFailure(QNetworkReply& reply); void alreadyOwnedSuccess(QNetworkReply& reply); void alreadyOwnedFailure(QNetworkReply& reply); + void availableUpdatesSuccess(QNetworkReply& reply); + void availableUpdatesFailure(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 557193c074..57d5e53794 100644 --- a/interface/src/commerce/QmlCommerce.cpp +++ b/interface/src/commerce/QmlCommerce.cpp @@ -38,7 +38,7 @@ QmlCommerce::QmlCommerce() { 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); + connect(ledger.data(), &Ledger::availableUpdatesResult, this, &QmlCommerce::availableUpdatesResult); auto accountManager = DependencyManager::get(); connect(accountManager.data(), &AccountManager::usernameChanged, this, [&]() { @@ -344,3 +344,8 @@ bool QmlCommerce::openApp(const QString& itemHref) { return true; } + +void QmlCommerce::getAvailableUpdates() { + auto ledger = DependencyManager::get(); + ledger->getAvailableUpdates(); +} diff --git a/interface/src/commerce/QmlCommerce.h b/interface/src/commerce/QmlCommerce.h index 6a4eaa2be2..f3615cc8a5 100644 --- a/interface/src/commerce/QmlCommerce.h +++ b/interface/src/commerce/QmlCommerce.h @@ -43,6 +43,7 @@ signals: void accountResult(QJsonObject result); void certificateInfoResult(QJsonObject result); void alreadyOwnedResult(QJsonObject result); + void availableUpdatesResult(QJsonObject result); void updateCertificateStatus(const QString& certID, uint certStatus); @@ -88,6 +89,8 @@ protected: Q_INVOKABLE bool uninstallApp(const QString& appHref); Q_INVOKABLE bool openApp(const QString& appHref); + Q_INVOKABLE void getAvailableUpdates(); + private: QString _appsPath; }; diff --git a/scripts/system/html/js/marketplacesInject.js b/scripts/system/html/js/marketplacesInject.js index 35f89a4a19..1546c2e6d5 100644 --- a/scripts/system/html/js/marketplacesInject.js +++ b/scripts/system/html/js/marketplacesInject.js @@ -445,15 +445,11 @@ purchaseButton.on('click', function () { if (url.indexOf('edition=' != -1)) { - if (url.indexOf('upgradeUrl=' === -1)) { - console.log("ERROR! Item is an upgrade, but no upgradeUrl was specified."); - } else { - updateButtonClicked(window.location.pathname.split("/")[3], - $('#top-center').find('h1').text(), - $('#creator').find('.value').text(), - getParameterByName('upgradeUrl'), - "itemPage"); - } + updateButtonClicked(window.location.pathname.split("/")[3], + $('#top-center').find('h1').text(), + $('#creator').find('.value').text(), + href, + "itemPage"); } else if ('available' === availability) { buyButtonClicked(window.location.pathname.split("/")[3], $('#top-center').find('h1').text(), diff --git a/scripts/system/marketplaces/marketplaces.js b/scripts/system/marketplaces/marketplaces.js index dafd3e525d..514f6dd975 100644 --- a/scripts/system/marketplaces/marketplaces.js +++ b/scripts/system/marketplaces/marketplaces.js @@ -561,7 +561,7 @@ var selectionDisplay = null; // for gridTool.js to ignore tablet.gotoWebScreen(MARKETPLACE_URL_INITIAL, MARKETPLACES_INJECT_SCRIPT_URL); break; case 'updateItemClicked': - tablet.gotoWebScreen(MARKETPLACE_URL + '/items/' + message.itemId + "?edition=" + message.itemEdition + "&upgradeUrl=" + message.upgradeUrl, + tablet.gotoWebScreen(message.upgradeUrl + "?edition=" + message.itemEdition + "&upgradeUrl=" + message.upgradeUrl, MARKETPLACES_INJECT_SCRIPT_URL); break; case 'passphrasePopup_cancelClicked': From 4046c873bdeacdaa7c83c0fd28e64a28814ce4f5 Mon Sep 17 00:00:00 2001 From: Zach Fox Date: Mon, 5 Mar 2018 11:20:38 -0800 Subject: [PATCH 03/32] Still need to send correct thing to metaverse --- .../qml/hifi/commerce/checkout/Checkout.qml | 44 +++++++++++++------ .../hifi/commerce/purchases/PurchasedItem.qml | 5 ++- .../qml/hifi/commerce/purchases/Purchases.qml | 1 + interface/src/commerce/Ledger.cpp | 12 +++++ interface/src/commerce/Ledger.h | 4 ++ interface/src/commerce/QmlCommerce.cpp | 14 ++++++ interface/src/commerce/QmlCommerce.h | 2 + scripts/system/html/js/marketplacesInject.js | 5 ++- scripts/system/marketplaces/marketplaces.js | 2 +- 9 files changed, 70 insertions(+), 19 deletions(-) diff --git a/interface/resources/qml/hifi/commerce/checkout/Checkout.qml b/interface/resources/qml/hifi/commerce/checkout/Checkout.qml index dd0c341412..562ceb1cc1 100644 --- a/interface/resources/qml/hifi/commerce/checkout/Checkout.qml +++ b/interface/resources/qml/hifi/commerce/checkout/Checkout.qml @@ -137,11 +137,27 @@ Rectangle { console.log("Failed to get Available Updates", result.data.message); } else { root.availableUpdatesReceived = true; - if (result.data.updates.indexOf(root.itemId) > -1) { - root.isUpdating = true; + for (var i = 0; i < result.data.updates.length; i++) { + if (result.data.updates[i].item_id === root.itemId) { + root.isUpdating = true; + break; + } } } } + + onUpdateItemResult: { + if (result.status !== 'success') { + failureErrorText.text = result.message; + root.activeView = "checkoutFailure"; + } else { + root.itemHref = result.data.download_url; + if (result.data.categories.indexOf("Wearables") > -1) { + root.itemType = "wearable"; + } + root.activeView = "checkoutSuccess"; + } + } } onItemIdChanged: { @@ -447,7 +463,7 @@ Rectangle { id: itemPriceText; text: root.isUpdating ? "FREE\nUPGRADE" : ((root.itemPrice === -1) ? "--" : root.itemPrice); // Text size - size: 26; + size: root.isUpdating ? 20 : 26; // Anchors anchors.top: parent.top; anchors.right: parent.right; @@ -569,7 +585,9 @@ Rectangle { text: root.isUpdating ? "CONFIRM UPDATE" : (((root.isCertified) ? ((ownershipStatusReceived && balanceReceived && availableUpdatesReceived) ? (viewInMyPurchasesButton.visible ? "Buy It Again" : "Confirm Purchase") : "--") : "Get Item")); onClicked: { - if (root.isCertified) { + if (root.isUpdating) { + Commerce.updateItem(root.itemId); + } else if (root.isCertified) { if (!root.shouldBuyWithControlledFailure) { if (root.itemType === "contentSet" && !Entities.canReplaceContent()) { lightboxPopup.titleText = "Purchase Content Set"; @@ -1020,12 +1038,12 @@ Rectangle { switch (message.method) { case 'updateCheckoutQML': root.isUpdating = message.params.isUpdating; - itemId = message.params.itemId; - itemName = message.params.itemName; + root.itemId = message.params.itemId; + root.itemName = message.params.itemName.trim(); root.itemPrice = message.params.itemPrice; - itemHref = message.params.itemHref; - referrer = message.params.referrer; - itemAuthor = message.params.itemAuthor; + root.itemHref = message.params.itemHref; + root.referrer = message.params.referrer; + root.itemAuthor = message.params.itemAuthor; refreshBuyUI(); break; default: @@ -1056,11 +1074,9 @@ Rectangle { } if (root.isUpdating) { - buyText.text = "By agreeing to update, you agree to trade in your old item for the update that replaces it."; - buyTextContainer.color = "#FFC3CD"; - buyTextContainer.border.color = "#F3808F"; - buyGlyph.text = hifi.glyphs.alert; - buyGlyph.size = 54; + buyText.text = "By pressing \"Confirm Update\", you agree to trade in your old item for the updated item that replaces it."; + buyTextContainer.color = "#FFFFFF"; + buyTextContainer.border.color = "#FFFFFF"; } else 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 + ""; diff --git a/interface/resources/qml/hifi/commerce/purchases/PurchasedItem.qml b/interface/resources/qml/hifi/commerce/purchases/PurchasedItem.qml index d8e02b08e5..df36bfd753 100644 --- a/interface/resources/qml/hifi/commerce/purchases/PurchasedItem.qml +++ b/interface/resources/qml/hifi/commerce/purchases/PurchasedItem.qml @@ -50,11 +50,12 @@ Item { property bool isInstalled; property string upgradeUrl; property string upgradeTitle; + property bool isShowingMyItems; property string originalStatusText; property string originalStatusColor; - height: root.upgradeUrl === "" ? 110 : 150; + height: (root.upgradeUrl === "" || root.isShowingMyItems) ? 110 : 150; width: parent.width; Connections { @@ -647,7 +648,7 @@ Item { Rectangle { id: upgradeAvailableContainer; - visible: root.upgradeUrl !== ""; + visible: root.upgradeUrl !== "" && !root.isShowingMyItems; anchors.top: itemContainer.bottom; anchors.bottom: parent.bottom; anchors.left: parent.left; diff --git a/interface/resources/qml/hifi/commerce/purchases/Purchases.qml b/interface/resources/qml/hifi/commerce/purchases/Purchases.qml index 8952aad0cf..485ae81305 100644 --- a/interface/resources/qml/hifi/commerce/purchases/Purchases.qml +++ b/interface/resources/qml/hifi/commerce/purchases/Purchases.qml @@ -400,6 +400,7 @@ Rectangle { isInstalled: model.isInstalled; upgradeUrl: model.upgrade_url; upgradeTitle: model.upgrade_title; + isShowingMyItems: root.isShowingMyItems; itemType: { if (model.root_file_url.indexOf(".fst") > -1) { "avatar"; diff --git a/interface/src/commerce/Ledger.cpp b/interface/src/commerce/Ledger.cpp index 62411a095a..c9b6bf3523 100644 --- a/interface/src/commerce/Ledger.cpp +++ b/interface/src/commerce/Ledger.cpp @@ -53,6 +53,7 @@ Handler(transferHfcToNode) Handler(transferHfcToUsername) Handler(alreadyOwned) Handler(availableUpdates) +Handler(updateItem) void Ledger::send(const QString& endpoint, const QString& success, const QString& fail, QNetworkAccessManager::Operation method, AccountManagerAuth::Type authType, QJsonObject request) { auto accountManager = DependencyManager::get(); @@ -372,3 +373,14 @@ void Ledger::getAvailableUpdates() { request["public_keys"] = QJsonArray::fromStringList(wallet->listPublicKeys()); send(endpoint, "availableUpdatesSuccess", "availableUpdatesFailure", QNetworkAccessManager::PutOperation, AccountManagerAuth::Required, request); } + +void Ledger::updateItem(const QString& hfc_key, const QString& asset_id, const QString& inventory_key) { + QJsonObject transaction; + transaction["hfc_key"] = hfc_key; + transaction["cost"] = 0; + transaction["asset_id"] = asset_id; + transaction["inventory_key"] = inventory_key; + QJsonDocument transactionDoc{ transaction }; + auto transactionString = transactionDoc.toJson(QJsonDocument::Compact); + signedSend("transaction", transactionString, hfc_key, "update_item", "updateItemSuccess", "updateItemFailure"); +} diff --git a/interface/src/commerce/Ledger.h b/interface/src/commerce/Ledger.h index 04c618d665..c68ed8493b 100644 --- a/interface/src/commerce/Ledger.h +++ b/interface/src/commerce/Ledger.h @@ -37,6 +37,7 @@ public: void transferHfcToUsername(const QString& hfc_key, const QString& username, const int& amount, const QString& optionalMessage); void alreadyOwned(const QString& marketplaceId); void getAvailableUpdates(); + void updateItem(const QString& hfc_key, const QString& asset_id, const QString& inventory_key); enum CertificateStatus { CERTIFICATE_STATUS_UNKNOWN = 0, @@ -59,6 +60,7 @@ signals: void transferHfcToUsernameResult(QJsonObject result); void alreadyOwnedResult(QJsonObject result); void availableUpdatesResult(QJsonObject result); + void updateItemResult(QJsonObject result); void updateCertificateStatus(const QString& certID, uint certStatus); @@ -87,6 +89,8 @@ public slots: void alreadyOwnedFailure(QNetworkReply& reply); void availableUpdatesSuccess(QNetworkReply& reply); void availableUpdatesFailure(QNetworkReply& reply); + void updateItemSuccess(QNetworkReply& reply); + void updateItemFailure(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 57d5e53794..701f2403d4 100644 --- a/interface/src/commerce/QmlCommerce.cpp +++ b/interface/src/commerce/QmlCommerce.cpp @@ -39,6 +39,7 @@ QmlCommerce::QmlCommerce() { connect(ledger.data(), &Ledger::transferHfcToNodeResult, this, &QmlCommerce::transferHfcToNodeResult); connect(ledger.data(), &Ledger::transferHfcToUsernameResult, this, &QmlCommerce::transferHfcToUsernameResult); connect(ledger.data(), &Ledger::availableUpdatesResult, this, &QmlCommerce::availableUpdatesResult); + connect(ledger.data(), &Ledger::updateItemResult, this, &QmlCommerce::updateItemResult); auto accountManager = DependencyManager::get(); connect(accountManager.data(), &AccountManager::usernameChanged, this, [&]() { @@ -349,3 +350,16 @@ void QmlCommerce::getAvailableUpdates() { auto ledger = DependencyManager::get(); ledger->getAvailableUpdates(); } + +void QmlCommerce::updateItem(const QString& marketplaceId) { + auto ledger = DependencyManager::get(); + auto wallet = DependencyManager::get(); + QStringList keys = wallet->listPublicKeys(); + if (keys.count() == 0) { + QJsonObject result{ { "status", "fail" },{ "message", "Uninitialized Wallet." } }; + return emit updateItemResult(result); + } + QString key = keys[0]; + // For now, we receive at the same key that pays for it. + ledger->updateItem(key, marketplaceId, key); +} diff --git a/interface/src/commerce/QmlCommerce.h b/interface/src/commerce/QmlCommerce.h index f3615cc8a5..fdca17e557 100644 --- a/interface/src/commerce/QmlCommerce.h +++ b/interface/src/commerce/QmlCommerce.h @@ -44,6 +44,7 @@ signals: void certificateInfoResult(QJsonObject result); void alreadyOwnedResult(QJsonObject result); void availableUpdatesResult(QJsonObject result); + void updateItemResult(QJsonObject result); void updateCertificateStatus(const QString& certID, uint certStatus); @@ -90,6 +91,7 @@ protected: Q_INVOKABLE bool openApp(const QString& appHref); Q_INVOKABLE void getAvailableUpdates(); + Q_INVOKABLE void updateItem(const QString& marketplaceId); private: QString _appsPath; diff --git a/scripts/system/html/js/marketplacesInject.js b/scripts/system/html/js/marketplacesInject.js index 1546c2e6d5..d033e6988d 100644 --- a/scripts/system/html/js/marketplacesInject.js +++ b/scripts/system/html/js/marketplacesInject.js @@ -262,6 +262,7 @@ isUpdating: true, itemId: id, itemName: name, + itemPrice: 0, itemHref: href, referrer: referrer, itemAuthor: author @@ -436,7 +437,7 @@ var cost = $('.item-cost').text(); if (availability !== 'available') { purchaseButton.html('UNAVAILABLE (' + availability + ')'); - } else if (url.indexOf('edition=' != -1)) { + } else if (window.location.href.indexOf('edition=' != -1)) { purchaseButton.html('UPDATE FOR FREE'); } else if (parseInt(cost) > 0 && $('#side-info').find('#buyItemButton').size() === 0) { purchaseButton.html('PURCHASE getAvailableUpdates(); } -void QmlCommerce::updateItem(const QString& marketplaceId) { +void QmlCommerce::updateItem(const QString& certificateId) { auto ledger = DependencyManager::get(); auto wallet = DependencyManager::get(); QStringList keys = wallet->listPublicKeys(); @@ -360,6 +360,5 @@ void QmlCommerce::updateItem(const QString& marketplaceId) { return emit updateItemResult(result); } QString key = keys[0]; - // For now, we receive at the same key that pays for it. - ledger->updateItem(key, marketplaceId, key); + ledger->updateItem(key, certificateId); } diff --git a/interface/src/commerce/QmlCommerce.h b/interface/src/commerce/QmlCommerce.h index fdca17e557..1b2c08ee95 100644 --- a/interface/src/commerce/QmlCommerce.h +++ b/interface/src/commerce/QmlCommerce.h @@ -91,7 +91,7 @@ protected: Q_INVOKABLE bool openApp(const QString& appHref); Q_INVOKABLE void getAvailableUpdates(); - Q_INVOKABLE void updateItem(const QString& marketplaceId); + Q_INVOKABLE void updateItem(const QString& certificateId); private: QString _appsPath; diff --git a/scripts/system/html/js/marketplacesInject.js b/scripts/system/html/js/marketplacesInject.js index d033e6988d..c97700bc69 100644 --- a/scripts/system/html/js/marketplacesInject.js +++ b/scripts/system/html/js/marketplacesInject.js @@ -246,7 +246,6 @@ function buyButtonClicked(id, name, author, price, href, referrer) { EventBridge.emitWebEvent(JSON.stringify({ type: "CHECKOUT", - isUpdating: false, itemId: id, itemName: name, itemPrice: price ? parseInt(price, 10) : 0, @@ -256,7 +255,7 @@ })); } - function updateButtonClicked(id, name, author, href, referrer) { + function updateButtonClicked(id, name, author, href, referrer, certId) { EventBridge.emitWebEvent(JSON.stringify({ type: "UPDATE", isUpdating: true, @@ -265,21 +264,11 @@ itemPrice: 0, itemHref: href, referrer: referrer, - itemAuthor: author + itemAuthor: author, + certificateId: certId })); } - // From https://stackoverflow.com/questions/901115/how-can-i-get-query-string-values-in-javascript - function getParameterByName(name, url) { - if (!url) url = window.location.href; - name = name.replace(/[\[\]]/g, "\\$&"); - var regex = new RegExp("[?&]" + name + "(=([^&#]*)|&|#|$)"), - results = regex.exec(url); - if (!results) return null; - if (!results[2]) return ''; - return decodeURIComponent(results[2].replace(/\+/g, " ")); - } - function injectBuyButtonOnMainPage() { var cost; @@ -437,7 +426,7 @@ var cost = $('.item-cost').text(); if (availability !== 'available') { purchaseButton.html('UNAVAILABLE (' + availability + ')'); - } else if (window.location.href.indexOf('edition=' != -1)) { + } else if (window.location.href.indexOf('certificateId=' != -1)) { purchaseButton.html('UPDATE FOR FREE'); } else if (parseInt(cost) > 0 && $('#side-info').find('#buyItemButton').size() === 0) { purchaseButton.html('PURCHASE Date: Wed, 7 Mar 2018 09:22:57 -0800 Subject: [PATCH 05/32] Filter out invalid purchases --- .../resources/qml/hifi/commerce/purchases/Purchases.qml | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/interface/resources/qml/hifi/commerce/purchases/Purchases.qml b/interface/resources/qml/hifi/commerce/purchases/Purchases.qml index a6bcce402b..8089818ba6 100644 --- a/interface/resources/qml/hifi/commerce/purchases/Purchases.qml +++ b/interface/resources/qml/hifi/commerce/purchases/Purchases.qml @@ -666,6 +666,10 @@ Rectangle { tempPurchasesModel.clear(); for (var i = 0; i < purchasesModel.count; i++) { if (purchasesModel.get(i).title.toLowerCase().indexOf(filterBar.text.toLowerCase()) !== -1) { + if (!purchasesModel.get(i).valid) { + continue; + } + if (purchasesModel.get(i).status !== "confirmed" && !root.isShowingMyItems) { tempPurchasesModel.insert(0, purchasesModel.get(i)); } else if ((root.isShowingMyItems && purchasesModel.get(i).edition_number === "0") || @@ -691,7 +695,10 @@ Rectangle { var currentId; for (var i = 0; i < tempPurchasesModel.count; i++) { currentId = tempPurchasesModel.get(i).id; - + + if (!purchasesModel.get(i).valid) { + continue; + } filteredPurchasesModel.append(tempPurchasesModel.get(i)); filteredPurchasesModel.setProperty(i, 'permissionExplanationCardVisible', false); filteredPurchasesModel.setProperty(i, 'isInstalled', ((root.installedApps).indexOf(currentId) > -1)); From d07d02b2bccab8c021ea70cc03ad01d25c213d2a Mon Sep 17 00:00:00 2001 From: Zach Fox Date: Wed, 7 Mar 2018 11:10:47 -0800 Subject: [PATCH 06/32] Take certID from backend instead of query params; Better loading; Uninstall app before updating --- .../qml/hifi/commerce/checkout/Checkout.qml | 22 ++++++++++++++----- .../hifi/commerce/purchases/PurchasedItem.qml | 2 +- scripts/system/html/js/marketplacesInject.js | 10 ++++----- scripts/system/marketplaces/marketplaces.js | 2 +- 4 files changed, 23 insertions(+), 13 deletions(-) diff --git a/interface/resources/qml/hifi/commerce/checkout/Checkout.qml b/interface/resources/qml/hifi/commerce/checkout/Checkout.qml index 9ffcbefdb5..6a53080899 100644 --- a/interface/resources/qml/hifi/commerce/checkout/Checkout.qml +++ b/interface/resources/qml/hifi/commerce/checkout/Checkout.qml @@ -52,6 +52,7 @@ Rectangle { property string referrer; property bool isInstalled; property bool isUpdating; + property string baseAppURL; // Style color: hifi.colors.white; Connections { @@ -106,9 +107,9 @@ Rectangle { if (result.status !== 'success') { console.log("Failed to get balance", result.data.message); } else { - root.balanceReceived = true; root.balanceAfterPurchase = result.data.balance - root.itemPrice; root.refreshBuyUI(); + root.balanceReceived = true; } } @@ -116,7 +117,6 @@ Rectangle { if (result.status !== 'success') { console.log("Failed to get Already Owned status", result.data.message); } else { - root.ownershipStatusReceived = true; if (result.data.marketplace_item_id === root.itemId) { root.alreadyOwned = result.data.already_owned; } else { @@ -124,6 +124,7 @@ Rectangle { root.alreadyOwned = false; } root.refreshBuyUI(); + root.ownershipStatusReceived = true; } } @@ -137,13 +138,20 @@ Rectangle { if (result.status !== 'success') { console.log("Failed to get Available Updates", result.data.message); } else { - root.availableUpdatesReceived = true; for (var i = 0; i < result.data.updates.length; i++) { - if (result.data.updates[i].item_id === root.itemId) { + // If the ItemID of the item we're looking at matches EITHER the ID of a "base" item + // OR the ID of an "upgrade" item, we're updating. + if (root.itemId === result.data.updates[i].item_id || + root.itemId === result.data.updates[i].updated_item_id) { root.isUpdating = true; + root.certificateId = result.data.updates[i].certificate_id; + if (root.itemType === "app") { + root.baseAppURL = result.data.updates[i].base_download_url; + } break; } } + root.availableUpdatesReceived = true; } } @@ -587,6 +595,11 @@ Rectangle { (viewInMyPurchasesButton.visible ? "Buy It Again" : "Confirm Purchase") : "--") : "Get Item")); onClicked: { if (root.isUpdating) { + // If we're updating an app, the existing app needs to be uninstalled. + // This call will fail/return `false` if the app isn't installed, but that's OK. + if (root.itemType === "app") { + Commerce.uninstallApp(root.baseAppURL); + } Commerce.updateItem(root.certificateId); } else if (root.isCertified) { if (!root.shouldBuyWithControlledFailure) { @@ -1047,7 +1060,6 @@ Rectangle { root.itemHref = message.params.itemHref; root.referrer = message.params.referrer; root.itemAuthor = message.params.itemAuthor; - root.certificateId = message.params.certificateId; refreshBuyUI(); break; default: diff --git a/interface/resources/qml/hifi/commerce/purchases/PurchasedItem.qml b/interface/resources/qml/hifi/commerce/purchases/PurchasedItem.qml index 51a31ed70a..df36bfd753 100644 --- a/interface/resources/qml/hifi/commerce/purchases/PurchasedItem.qml +++ b/interface/resources/qml/hifi/commerce/purchases/PurchasedItem.qml @@ -681,7 +681,7 @@ Item { verticalAlignment: Text.AlignVCenter; onLinkActivated: { - sendToPurchases({method: 'updateItemClicked', itemId: root.itemId, itemEdition: root.itemEdition, upgradeUrl: root.upgradeUrl, certificateId: root.certificateId}); + sendToPurchases({method: 'updateItemClicked', itemId: root.itemId, itemEdition: root.itemEdition, upgradeUrl: root.upgradeUrl}); } } } diff --git a/scripts/system/html/js/marketplacesInject.js b/scripts/system/html/js/marketplacesInject.js index c97700bc69..903d15223c 100644 --- a/scripts/system/html/js/marketplacesInject.js +++ b/scripts/system/html/js/marketplacesInject.js @@ -255,7 +255,7 @@ })); } - function updateButtonClicked(id, name, author, href, referrer, certId) { + function updateButtonClicked(id, name, author, href, referrer) { EventBridge.emitWebEvent(JSON.stringify({ type: "UPDATE", isUpdating: true, @@ -264,8 +264,7 @@ itemPrice: 0, itemHref: href, referrer: referrer, - itemAuthor: author, - certificateId: certId + itemAuthor: author })); } @@ -436,13 +435,12 @@ purchaseButton.on('click', function () { var urlParams = new URLSearchParams(window.location.search); - if (window.location.href.indexOf('certificateId=' != -1)) { + if (window.location.href.indexOf('edition=' != -1)) { // "Upgrading" case updateButtonClicked(window.location.pathname.split("/")[3], $('#top-center').find('h1').text(), $('#creator').find('.value').text(), href, - "itemPage", - urlParams.get('certificateId')); + "itemPage"); } else if ('available' === availability) { buyButtonClicked(window.location.pathname.split("/")[3], $('#top-center').find('h1').text(), diff --git a/scripts/system/marketplaces/marketplaces.js b/scripts/system/marketplaces/marketplaces.js index e54a986aac..6e010943c7 100644 --- a/scripts/system/marketplaces/marketplaces.js +++ b/scripts/system/marketplaces/marketplaces.js @@ -564,7 +564,7 @@ var selectionDisplay = null; // for gridTool.js to ignore tablet.gotoWebScreen(MARKETPLACE_URL_INITIAL, MARKETPLACES_INJECT_SCRIPT_URL); break; case 'updateItemClicked': - tablet.gotoWebScreen(message.upgradeUrl + "?edition=" + message.itemEdition + "&certificateId=" + message.certificateId.replace(/\+/g, "%2B"), + tablet.gotoWebScreen(message.upgradeUrl + "?edition=" + message.itemEdition, MARKETPLACES_INJECT_SCRIPT_URL); break; case 'passphrasePopup_cancelClicked': From 08b2be85d20154753cb2bb5273aa7350b129e4de Mon Sep 17 00:00:00 2001 From: Zach Fox Date: Wed, 7 Mar 2018 11:34:48 -0800 Subject: [PATCH 07/32] Don't pass isUpdating around --- .../qml/hifi/commerce/checkout/Checkout.qml | 3 -- scripts/system/html/js/marketplacesInject.js | 30 ++++--------------- scripts/system/marketplaces/marketplaces.js | 2 +- 3 files changed, 6 insertions(+), 29 deletions(-) diff --git a/interface/resources/qml/hifi/commerce/checkout/Checkout.qml b/interface/resources/qml/hifi/commerce/checkout/Checkout.qml index 6a53080899..ac11b332ff 100644 --- a/interface/resources/qml/hifi/commerce/checkout/Checkout.qml +++ b/interface/resources/qml/hifi/commerce/checkout/Checkout.qml @@ -1051,9 +1051,6 @@ Rectangle { function fromScript(message) { switch (message.method) { case 'updateCheckoutQML': - if (message.params.isUpdating) { - root.isUpdating = message.params.isUpdating; - } root.itemId = message.params.itemId; root.itemName = message.params.itemName.trim(); root.itemPrice = message.params.itemPrice; diff --git a/scripts/system/html/js/marketplacesInject.js b/scripts/system/html/js/marketplacesInject.js index 903d15223c..46b0b6c971 100644 --- a/scripts/system/html/js/marketplacesInject.js +++ b/scripts/system/html/js/marketplacesInject.js @@ -255,19 +255,6 @@ })); } - function updateButtonClicked(id, name, author, href, referrer) { - EventBridge.emitWebEvent(JSON.stringify({ - type: "UPDATE", - isUpdating: true, - itemId: id, - itemName: name, - itemPrice: 0, - itemHref: href, - referrer: referrer, - itemAuthor: author - })); - } - function injectBuyButtonOnMainPage() { var cost; @@ -423,25 +410,18 @@ } var cost = $('.item-cost').text(); - if (availability !== 'available') { - purchaseButton.html('UNAVAILABLE (' + availability + ')'); - } else if (window.location.href.indexOf('certificateId=' != -1)) { + var isUpdating = window.location.href.indexOf('edition=') > -1; + if (isUpdating) { purchaseButton.html('UPDATE FOR FREE'); + } else if (availability !== 'available') { + purchaseButton.html('UNAVAILABLE (' + availability + ')'); } else if (parseInt(cost) > 0 && $('#side-info').find('#buyItemButton').size() === 0) { purchaseButton.html('PURCHASE ' + cost); } purchaseButton.on('click', function () { - var urlParams = new URLSearchParams(window.location.search); - - if (window.location.href.indexOf('edition=' != -1)) { // "Upgrading" case - updateButtonClicked(window.location.pathname.split("/")[3], - $('#top-center').find('h1').text(), - $('#creator').find('.value').text(), - href, - "itemPage"); - } else if ('available' === availability) { + if ('available' === availability || isUpdating) { buyButtonClicked(window.location.pathname.split("/")[3], $('#top-center').find('h1').text(), $('#creator').find('.value').text(), diff --git a/scripts/system/marketplaces/marketplaces.js b/scripts/system/marketplaces/marketplaces.js index 6e010943c7..d3620968c7 100644 --- a/scripts/system/marketplaces/marketplaces.js +++ b/scripts/system/marketplaces/marketplaces.js @@ -420,7 +420,7 @@ var selectionDisplay = null; // for gridTool.js to ignore isDownloadBeingCancelled = false; } else { var parsedJsonMessage = JSON.parse(message); - if (parsedJsonMessage.type === "CHECKOUT" || parsedJsonMessage.type === "UPDATE") { + if (parsedJsonMessage.type === "CHECKOUT") { wireEventBridge(true); tablet.pushOntoStack(MARKETPLACE_CHECKOUT_QML_PATH); tablet.sendToQml({ From a2eb13b70bcf5ded3cdb137321419eec39646a3b Mon Sep 17 00:00:00 2001 From: Zach Fox Date: Wed, 7 Mar 2018 11:49:31 -0800 Subject: [PATCH 08/32] Prevent logspam --- .../qml/hifi/commerce/checkout/Checkout.qml | 15 ++++++--------- 1 file changed, 6 insertions(+), 9 deletions(-) diff --git a/interface/resources/qml/hifi/commerce/checkout/Checkout.qml b/interface/resources/qml/hifi/commerce/checkout/Checkout.qml index ac11b332ff..cd1dea50a1 100644 --- a/interface/resources/qml/hifi/commerce/checkout/Checkout.qml +++ b/interface/resources/qml/hifi/commerce/checkout/Checkout.qml @@ -108,8 +108,8 @@ Rectangle { console.log("Failed to get balance", result.data.message); } else { root.balanceAfterPurchase = result.data.balance - root.itemPrice; - root.refreshBuyUI(); root.balanceReceived = true; + root.refreshBuyUI(); } } @@ -123,8 +123,8 @@ Rectangle { console.log("WARNING - Received 'Already Owned' status about different Marketplace ID!"); root.alreadyOwned = false; } - root.refreshBuyUI(); root.ownershipStatusReceived = true; + root.refreshBuyUI(); } } @@ -140,10 +140,11 @@ Rectangle { } else { for (var i = 0; i < result.data.updates.length; i++) { // If the ItemID of the item we're looking at matches EITHER the ID of a "base" item - // OR the ID of an "upgrade" item, we're updating. + // OR the ID of an "updated" item, we're updating. if (root.itemId === result.data.updates[i].item_id || root.itemId === result.data.updates[i].updated_item_id) { root.isUpdating = true; + // This CertID is the one corresponding to the base item CertID that the user already owns root.certificateId = result.data.updates[i].certificate_id; if (root.itemType === "app") { root.baseAppURL = result.data.updates[i].base_download_url; @@ -152,6 +153,7 @@ Rectangle { } } root.availableUpdatesReceived = true; + refreshBuyUI(); } } @@ -202,6 +204,7 @@ Rectangle { } onItemPriceChanged: { + root.balanceReceived = false; Commerce.balance(); } @@ -1117,12 +1120,6 @@ Rectangle { } else { root.activeView = "checkoutSuccess"; } - root.balanceReceived = false; - root.ownershipStatusReceived = false; - root.availableUpdatesReceived = false; - Commerce.alreadyOwned(root.itemId); - Commerce.balance(); - Commerce.getAvailableUpdates(); } // From 0285bf937902ae3d0da67d204cd17dba12e39e95 Mon Sep 17 00:00:00 2001 From: Zach Fox Date: Wed, 7 Mar 2018 13:27:40 -0800 Subject: [PATCH 09/32] purposeful incorrect indentation to prevent insane diff --- .../hifi/commerce/purchases/PurchasedItem.qml | 970 +++++++++--------- 1 file changed, 486 insertions(+), 484 deletions(-) diff --git a/interface/resources/qml/hifi/commerce/purchases/PurchasedItem.qml b/interface/resources/qml/hifi/commerce/purchases/PurchasedItem.qml index df36bfd753..38b0e16846 100644 --- a/interface/resources/qml/hifi/commerce/purchases/PurchasedItem.qml +++ b/interface/resources/qml/hifi/commerce/purchases/PurchasedItem.qml @@ -140,512 +140,514 @@ Item { anchors.verticalCenter: parent.verticalCenter; height: root.height - 10; - Item { - id: itemContainer; + // START "incorrect indentation to prevent insane diffs" + Item { + id: itemContainer; + anchors.left: parent.left; + anchors.right: parent.right; + anchors.top: parent.top; + height: 100; + + Image { + id: itemPreviewImage; + source: root.itemPreviewImageUrl; anchors.left: parent.left; - anchors.right: parent.right; anchors.top: parent.top; - height: 100; + anchors.bottom: parent.bottom; + width: height; + fillMode: Image.PreserveAspectCrop; - Image { - id: itemPreviewImage; - source: root.itemPreviewImageUrl; - anchors.left: parent.left; - anchors.top: parent.top; - anchors.bottom: parent.bottom; - width: height; - fillMode: Image.PreserveAspectCrop; - - MouseArea { - anchors.fill: parent; - onClicked: { - sendToPurchases({method: 'purchases_itemInfoClicked', itemId: root.itemId}); - } + MouseArea { + anchors.fill: parent; + onClicked: { + sendToPurchases({method: 'purchases_itemInfoClicked', itemId: root.itemId}); } } + } - TextMetrics { - id: itemNameTextMetrics; - font: itemName.font; - text: itemName.text; + 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; + 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; + // 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; + } } - RalewaySemiBold { - id: itemName; - anchors.top: itemPreviewImage.top; - anchors.topMargin: 4; - anchors.left: itemPreviewImage.right; - anchors.leftMargin: 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; - // Style - color: hifi.colors.blueAccent; - text: root.itemName; - elide: Text.ElideRight; - // Alignment - horizontalAlignment: Text.AlignLeft; + } + 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; - 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; - } + onLinkActivated: { + sendToPurchases({method: 'showPermissionsExplanation', itemType: root.itemType}); } } + // "Close" button 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; - anchors.top: itemName.bottom; - anchors.topMargin: 4; - anchors.left: itemName.left; - anchors.right: buttonContainer.left; - anchors.rightMargin: 2; - height: 24; - - HiFiGlyphs { - id: certificateIcon; - text: hifi.glyphs.scriptNew; - // Size - size: 30; - // Anchors - anchors.top: parent.top; - anchors.left: parent.left; - anchors.bottom: parent.bottom; - width: 32; - // Style - color: hifi.colors.black; - } - - RalewayRegular { - id: viewCertificateText; - text: "VIEW CERTIFICATE"; - size: 13; - anchors.left: certificateIcon.right; - anchors.leftMargin: 4; - anchors.top: parent.top; - anchors.bottom: parent.bottom; - anchors.right: parent.right; - color: hifi.colors.black; - } - - MouseArea { - anchors.fill: parent; - hoverEnabled: enabled; - onClicked: { - sendToPurchases({method: 'purchases_itemCertificateClicked', itemCertificateId: root.certificateId}); - } - onEntered: { - certificateIcon.color = hifi.colors.lightGray; - viewCertificateText.color = hifi.colors.lightGray; - } - onExited: { - certificateIcon.color = hifi.colors.black; - viewCertificateText.color = hifi.colors.black; - } - } - } - - Item { - id: editionContainer; - visible: root.displayedItemCount > 1 && !statusContainer.visible; - anchors.left: itemName.left; - anchors.top: certificateContainer.bottom; - anchors.topMargin: 8; - anchors.bottom: parent.bottom; - anchors.right: buttonContainer.left; - anchors.rightMargin: 2; - - RalewayRegular { - anchors.left: parent.left; - anchors.top: parent.top; - anchors.bottom: parent.bottom; - width: paintedWidth; - text: "#" + root.itemEdition; - size: 13; - color: hifi.colors.black; - verticalAlignment: Text.AlignTop; - } - } - - Item { - id: statusContainer; - visible: root.purchaseStatus === "pending" || root.purchaseStatus === "invalidated" || root.purchaseStatusChanged; - anchors.left: itemName.left; - anchors.top: certificateContainer.bottom; - anchors.topMargin: 8; - anchors.bottom: parent.bottom; - anchors.right: buttonContainer.left; - anchors.rightMargin: 2; - - RalewaySemiBold { - id: statusText; - anchors.left: parent.left; - anchors.top: parent.top; - anchors.bottom: parent.bottom; - width: paintedWidth; - text: { - if (root.purchaseStatus === "pending") { - "PENDING..." - } else if (root.purchaseStatus === "invalidated") { - "INVALIDATED" - } else if (root.numberSold !== -1) { - ("Sales: " + root.numberSold + "/" + (root.limitedRun === -1 ? "\u221e" : root.limitedRun)) - } else { - "" - } - } - size: 18; - color: { - if (root.purchaseStatus === "pending") { - hifi.colors.blueAccent - } else if (root.purchaseStatus === "invalidated") { - hifi.colors.redAccent - } else { - hifi.colors.baseGray - } - } - verticalAlignment: Text.AlignTop; - } - - HiFiGlyphs { - id: statusIcon; - text: { - if (root.purchaseStatus === "pending") { - hifi.glyphs.question - } else if (root.purchaseStatus === "invalidated") { - hifi.glyphs.question - } else { - "" - } - } - // Size - size: 36; - // Anchors - anchors.top: parent.top; - anchors.topMargin: -8; - anchors.left: statusText.right; - anchors.bottom: parent.bottom; - // Style - color: { - if (root.purchaseStatus === "pending") { - hifi.colors.blueAccent - } else if (root.purchaseStatus === "invalidated") { - hifi.colors.redAccent - } else { - hifi.colors.baseGray - } - } - verticalAlignment: Text.AlignTop; - } - - MouseArea { - anchors.fill: parent; - hoverEnabled: enabled; - onClicked: { - if (root.purchaseStatus === "pending") { - sendToPurchases({method: 'showPendingLightbox'}); - } else if (root.purchaseStatus === "invalidated") { - sendToPurchases({method: 'showInvalidatedLightbox'}); - } - } - onEntered: { - if (root.purchaseStatus === "pending") { - statusText.color = hifi.colors.blueHighlight; - statusIcon.color = hifi.colors.blueHighlight; - } else if (root.purchaseStatus === "invalidated") { - statusText.color = hifi.colors.redAccent; - statusIcon.color = hifi.colors.redAccent; - } - } - onExited: { - if (root.purchaseStatus === "pending") { - statusText.color = hifi.colors.blueAccent; - statusIcon.color = hifi.colors.blueAccent; - } else if (root.purchaseStatus === "invalidated") { - statusText.color = hifi.colors.redHighlight; - statusIcon.color = hifi.colors.redHighlight; - } - } - } - } - - Rectangle { - id: rezzedNotifContainer; - z: 998; - visible: false; - color: "#1FC6A6"; - anchors.fill: buttonContainer; - MouseArea { - anchors.fill: parent; - propagateComposedEvents: false; - hoverEnabled: true; - } - - RalewayBold { - anchors.fill: parent; - text: (root.buttonTextClicked)[itemTypesArray.indexOf(root.itemType)]; - size: 15; - color: hifi.colors.white; - verticalAlignment: Text.AlignVCenter; - horizontalAlignment: Text.AlignHCenter; - } - - Timer { - id: rezzedNotifContainerTimer; - interval: 2000; - onTriggered: rezzedNotifContainer.visible = false - } - } - - Rectangle { - id: appButtonContainer; - color: hifi.colors.white; - z: 994; - visible: root.isInstalled; - anchors.fill: buttonContainer; - - HifiControlsUit.Button { - id: openAppButton; - color: hifi.buttons.blue; - colorScheme: hifi.colorSchemes.light; - anchors.top: parent.top; - anchors.right: parent.right; - anchors.left: parent.left; - width: 92; - height: 44; - text: "OPEN" - onClicked: { - Commerce.openApp(root.itemHref); - } - } - - HifiControlsUit.Button { - id: uninstallAppButton; - color: hifi.buttons.noneBorderless; - colorScheme: hifi.colorSchemes.light; - anchors.bottom: parent.bottom; - anchors.right: parent.right; - anchors.left: parent.left; - height: 44; - text: "UNINSTALL" - onClicked: { - Commerce.uninstallApp(root.itemHref); - } - } - } - - Button { - id: buttonContainer; - property int color: hifi.buttons.blue; - property int colorScheme: hifi.colorSchemes.light; - + id: permissionExplanationGlyph; + text: hifi.glyphs.close; + color: hifi.colors.baseGray; + size: 26; 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.hasPermissionToRezThis && - root.purchaseStatus !== "invalidated" && - MyAvatar.skeletonModelURL !== root.itemHref; - - onHoveredChanged: { - if (hovered) { - Tablet.playSound(TabletEnums.ButtonHover); + width: 77; + horizontalAlignment: Text.AlignHCenter; + verticalAlignment: Text.AlignVCenter; + MouseArea { + anchors.fill: parent; + hoverEnabled: true; + onEntered: { + parent.text = hifi.glyphs.closeInverted; } - } - - onFocusChanged: { - if (focus) { - Tablet.playSound(TabletEnums.ButtonHover); + onExited: { + parent.text = hifi.glyphs.close; } - } - - onClicked: { - 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 if (root.itemType === "app") { - // "Run" and "Uninstall" buttons are separate. - Commerce.installApp(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 - color: { - if (!control.enabled) { - hifi.buttons.disabledColorStart[control.colorScheme] - } else if (control.pressed) { - hifi.buttons.pressedColor[control.color] - } else if (control.hovered) { - hifi.buttons.hoveredColor[control.color] - } else { - hifi.buttons.colorStart[control.color] - } - } - } - GradientStop { - position: 1.0 - color: { - if (!control.enabled) { - hifi.buttons.disabledColorFinish[control.colorScheme] - } else if (control.pressed) { - hifi.buttons.pressedColor[control.color] - } else if (control.hovered) { - hifi.buttons.hoveredColor[control.color] - } else { - hifi.buttons.colorFinish[control.color] - } - } - } - } - } - - label: Item { - HiFiGlyphs { - id: rezIcon; - text: (root.buttonGlyph)[itemTypesArray.indexOf(root.itemType)]; - // Size - size: 60; - // Anchors - anchors.top: parent.top; - anchors.topMargin: 0; - anchors.left: parent.left; - anchors.right: parent.right; - horizontalAlignment: Text.AlignHCenter; - // Style - color: enabled ? hifi.buttons.textColor[control.color] - : hifi.buttons.disabledTextColor[control.colorScheme] - } - RalewayBold { - 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: 15; - verticalAlignment: Text.AlignVCenter - horizontalAlignment: Text.AlignHCenter - text: MyAvatar.skeletonModelURL === root.itemHref ? "CURRENT" : (root.buttonTextNormal)[itemTypesArray.indexOf(root.itemType)]; - } + onClicked: { + root.sendToPurchases({ method: 'openPermissionExplanationCard', closeAll: true }); } } } } + Item { + id: certificateContainer; + anchors.top: itemName.bottom; + anchors.topMargin: 4; + anchors.left: itemName.left; + anchors.right: buttonContainer.left; + anchors.rightMargin: 2; + height: 24; + + HiFiGlyphs { + id: certificateIcon; + text: hifi.glyphs.scriptNew; + // Size + size: 30; + // Anchors + anchors.top: parent.top; + anchors.left: parent.left; + anchors.bottom: parent.bottom; + width: 32; + // Style + color: hifi.colors.black; + } + + RalewayRegular { + id: viewCertificateText; + text: "VIEW CERTIFICATE"; + size: 13; + anchors.left: certificateIcon.right; + anchors.leftMargin: 4; + anchors.top: parent.top; + anchors.bottom: parent.bottom; + anchors.right: parent.right; + color: hifi.colors.black; + } + + MouseArea { + anchors.fill: parent; + hoverEnabled: enabled; + onClicked: { + sendToPurchases({method: 'purchases_itemCertificateClicked', itemCertificateId: root.certificateId}); + } + onEntered: { + certificateIcon.color = hifi.colors.lightGray; + viewCertificateText.color = hifi.colors.lightGray; + } + onExited: { + certificateIcon.color = hifi.colors.black; + viewCertificateText.color = hifi.colors.black; + } + } + } + + Item { + id: editionContainer; + visible: root.displayedItemCount > 1 && !statusContainer.visible; + anchors.left: itemName.left; + anchors.top: certificateContainer.bottom; + anchors.topMargin: 8; + anchors.bottom: parent.bottom; + anchors.right: buttonContainer.left; + anchors.rightMargin: 2; + + RalewayRegular { + anchors.left: parent.left; + anchors.top: parent.top; + anchors.bottom: parent.bottom; + width: paintedWidth; + text: "#" + root.itemEdition; + size: 13; + color: hifi.colors.black; + verticalAlignment: Text.AlignTop; + } + } + + Item { + id: statusContainer; + visible: root.purchaseStatus === "pending" || root.purchaseStatus === "invalidated" || root.purchaseStatusChanged; + anchors.left: itemName.left; + anchors.top: certificateContainer.bottom; + anchors.topMargin: 8; + anchors.bottom: parent.bottom; + anchors.right: buttonContainer.left; + anchors.rightMargin: 2; + + RalewaySemiBold { + id: statusText; + anchors.left: parent.left; + anchors.top: parent.top; + anchors.bottom: parent.bottom; + width: paintedWidth; + text: { + if (root.purchaseStatus === "pending") { + "PENDING..." + } else if (root.purchaseStatus === "invalidated") { + "INVALIDATED" + } else if (root.numberSold !== -1) { + ("Sales: " + root.numberSold + "/" + (root.limitedRun === -1 ? "\u221e" : root.limitedRun)) + } else { + "" + } + } + size: 18; + color: { + if (root.purchaseStatus === "pending") { + hifi.colors.blueAccent + } else if (root.purchaseStatus === "invalidated") { + hifi.colors.redAccent + } else { + hifi.colors.baseGray + } + } + verticalAlignment: Text.AlignTop; + } + + HiFiGlyphs { + id: statusIcon; + text: { + if (root.purchaseStatus === "pending") { + hifi.glyphs.question + } else if (root.purchaseStatus === "invalidated") { + hifi.glyphs.question + } else { + "" + } + } + // Size + size: 36; + // Anchors + anchors.top: parent.top; + anchors.topMargin: -8; + anchors.left: statusText.right; + anchors.bottom: parent.bottom; + // Style + color: { + if (root.purchaseStatus === "pending") { + hifi.colors.blueAccent + } else if (root.purchaseStatus === "invalidated") { + hifi.colors.redAccent + } else { + hifi.colors.baseGray + } + } + verticalAlignment: Text.AlignTop; + } + + MouseArea { + anchors.fill: parent; + hoverEnabled: enabled; + onClicked: { + if (root.purchaseStatus === "pending") { + sendToPurchases({method: 'showPendingLightbox'}); + } else if (root.purchaseStatus === "invalidated") { + sendToPurchases({method: 'showInvalidatedLightbox'}); + } + } + onEntered: { + if (root.purchaseStatus === "pending") { + statusText.color = hifi.colors.blueHighlight; + statusIcon.color = hifi.colors.blueHighlight; + } else if (root.purchaseStatus === "invalidated") { + statusText.color = hifi.colors.redAccent; + statusIcon.color = hifi.colors.redAccent; + } + } + onExited: { + if (root.purchaseStatus === "pending") { + statusText.color = hifi.colors.blueAccent; + statusIcon.color = hifi.colors.blueAccent; + } else if (root.purchaseStatus === "invalidated") { + statusText.color = hifi.colors.redHighlight; + statusIcon.color = hifi.colors.redHighlight; + } + } + } + } + + Rectangle { + id: rezzedNotifContainer; + z: 998; + visible: false; + color: "#1FC6A6"; + anchors.fill: buttonContainer; + MouseArea { + anchors.fill: parent; + propagateComposedEvents: false; + hoverEnabled: true; + } + + RalewayBold { + anchors.fill: parent; + text: (root.buttonTextClicked)[itemTypesArray.indexOf(root.itemType)]; + size: 15; + color: hifi.colors.white; + verticalAlignment: Text.AlignVCenter; + horizontalAlignment: Text.AlignHCenter; + } + + Timer { + id: rezzedNotifContainerTimer; + interval: 2000; + onTriggered: rezzedNotifContainer.visible = false + } + } + + Rectangle { + id: appButtonContainer; + color: hifi.colors.white; + z: 994; + visible: root.isInstalled; + anchors.fill: buttonContainer; + + HifiControlsUit.Button { + id: openAppButton; + color: hifi.buttons.blue; + colorScheme: hifi.colorSchemes.light; + anchors.top: parent.top; + anchors.right: parent.right; + anchors.left: parent.left; + width: 92; + height: 44; + text: "OPEN" + onClicked: { + Commerce.openApp(root.itemHref); + } + } + + HifiControlsUit.Button { + id: uninstallAppButton; + color: hifi.buttons.noneBorderless; + colorScheme: hifi.colorSchemes.light; + anchors.bottom: parent.bottom; + anchors.right: parent.right; + anchors.left: parent.left; + height: 44; + text: "UNINSTALL" + onClicked: { + Commerce.uninstallApp(root.itemHref); + } + } + } + + Button { + id: buttonContainer; + 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.hasPermissionToRezThis && + root.purchaseStatus !== "invalidated" && + MyAvatar.skeletonModelURL !== root.itemHref; + + onHoveredChanged: { + if (hovered) { + Tablet.playSound(TabletEnums.ButtonHover); + } + } + + onFocusChanged: { + if (focus) { + Tablet.playSound(TabletEnums.ButtonHover); + } + } + + onClicked: { + 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 if (root.itemType === "app") { + // "Run" and "Uninstall" buttons are separate. + Commerce.installApp(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 + color: { + if (!control.enabled) { + hifi.buttons.disabledColorStart[control.colorScheme] + } else if (control.pressed) { + hifi.buttons.pressedColor[control.color] + } else if (control.hovered) { + hifi.buttons.hoveredColor[control.color] + } else { + hifi.buttons.colorStart[control.color] + } + } + } + GradientStop { + position: 1.0 + color: { + if (!control.enabled) { + hifi.buttons.disabledColorFinish[control.colorScheme] + } else if (control.pressed) { + hifi.buttons.pressedColor[control.color] + } else if (control.hovered) { + hifi.buttons.hoveredColor[control.color] + } else { + hifi.buttons.colorFinish[control.color] + } + } + } + } + } + + label: Item { + HiFiGlyphs { + id: rezIcon; + text: (root.buttonGlyph)[itemTypesArray.indexOf(root.itemType)]; + // Size + size: 60; + // Anchors + anchors.top: parent.top; + anchors.topMargin: 0; + anchors.left: parent.left; + anchors.right: parent.right; + horizontalAlignment: Text.AlignHCenter; + // Style + color: enabled ? hifi.buttons.textColor[control.color] + : hifi.buttons.disabledTextColor[control.colorScheme] + } + RalewayBold { + 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: 15; + verticalAlignment: Text.AlignVCenter + horizontalAlignment: Text.AlignHCenter + text: MyAvatar.skeletonModelURL === root.itemHref ? "CURRENT" : (root.buttonTextNormal)[itemTypesArray.indexOf(root.itemType)]; + } + } + } + } + } + // END "incorrect indentation to prevent insane diffs" + Rectangle { id: upgradeAvailableContainer; visible: root.upgradeUrl !== "" && !root.isShowingMyItems; From eded8586a5fec0142620420bd25e1bf3ddf2e6df Mon Sep 17 00:00:00 2001 From: Zach Fox Date: Wed, 7 Mar 2018 13:40:55 -0800 Subject: [PATCH 10/32] Use correct backend terminology --- interface/resources/qml/hifi/commerce/checkout/Checkout.qml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/interface/resources/qml/hifi/commerce/checkout/Checkout.qml b/interface/resources/qml/hifi/commerce/checkout/Checkout.qml index cd1dea50a1..3e29c9328d 100644 --- a/interface/resources/qml/hifi/commerce/checkout/Checkout.qml +++ b/interface/resources/qml/hifi/commerce/checkout/Checkout.qml @@ -147,7 +147,7 @@ Rectangle { // This CertID is the one corresponding to the base item CertID that the user already owns root.certificateId = result.data.updates[i].certificate_id; if (root.itemType === "app") { - root.baseAppURL = result.data.updates[i].base_download_url; + root.baseAppURL = result.data.updates[i].item_download_url; } break; } From d71fc22ed82ea7f2e881c0cecef560067562ece4 Mon Sep 17 00:00:00 2001 From: Zach Fox Date: Wed, 7 Mar 2018 14:06:29 -0800 Subject: [PATCH 11/32] Support argument to available_updates endpoint --- interface/resources/qml/hifi/commerce/checkout/Checkout.qml | 2 +- interface/src/commerce/Ledger.cpp | 5 ++++- interface/src/commerce/Ledger.h | 2 +- interface/src/commerce/QmlCommerce.cpp | 4 ++-- interface/src/commerce/QmlCommerce.h | 2 +- 5 files changed, 9 insertions(+), 6 deletions(-) diff --git a/interface/resources/qml/hifi/commerce/checkout/Checkout.qml b/interface/resources/qml/hifi/commerce/checkout/Checkout.qml index 3e29c9328d..66f3537d8d 100644 --- a/interface/resources/qml/hifi/commerce/checkout/Checkout.qml +++ b/interface/resources/qml/hifi/commerce/checkout/Checkout.qml @@ -175,7 +175,7 @@ Rectangle { root.ownershipStatusReceived = false; Commerce.alreadyOwned(root.itemId); root.availableUpdatesReceived = false; - Commerce.getAvailableUpdates(); + Commerce.getAvailableUpdates(root.itemId); itemPreviewImage.source = "https://hifi-metaverse.s3-us-west-1.amazonaws.com/marketplace/previews/" + itemId + "/thumbnail/hifi-mp-" + itemId + ".jpg"; } diff --git a/interface/src/commerce/Ledger.cpp b/interface/src/commerce/Ledger.cpp index d0683b9872..65575de29e 100644 --- a/interface/src/commerce/Ledger.cpp +++ b/interface/src/commerce/Ledger.cpp @@ -366,11 +366,14 @@ void Ledger::alreadyOwned(const QString& marketplaceId) { send(endpoint, "alreadyOwnedSuccess", "alreadyOwnedFailure", QNetworkAccessManager::PutOperation, AccountManagerAuth::Required, request); } -void Ledger::getAvailableUpdates() { +void Ledger::getAvailableUpdates(const QString& itemId) { auto wallet = DependencyManager::get(); QString endpoint = "available_updates"; QJsonObject request; request["public_keys"] = QJsonArray::fromStringList(wallet->listPublicKeys()); + if (!itemId.isEmpty()) { + request["marketplace_item_id"] = itemId; + } send(endpoint, "availableUpdatesSuccess", "availableUpdatesFailure", QNetworkAccessManager::PutOperation, AccountManagerAuth::Required, request); } diff --git a/interface/src/commerce/Ledger.h b/interface/src/commerce/Ledger.h index 45506569fc..da97206bbc 100644 --- a/interface/src/commerce/Ledger.h +++ b/interface/src/commerce/Ledger.h @@ -36,7 +36,7 @@ public: 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); - void getAvailableUpdates(); + void getAvailableUpdates(const QString& itemId = ""); void updateItem(const QString& hfc_key, const QString& certificate_id); enum CertificateStatus { diff --git a/interface/src/commerce/QmlCommerce.cpp b/interface/src/commerce/QmlCommerce.cpp index 41cad52842..17f9c5f3ea 100644 --- a/interface/src/commerce/QmlCommerce.cpp +++ b/interface/src/commerce/QmlCommerce.cpp @@ -346,9 +346,9 @@ bool QmlCommerce::openApp(const QString& itemHref) { return true; } -void QmlCommerce::getAvailableUpdates() { +void QmlCommerce::getAvailableUpdates(const QString& itemId) { auto ledger = DependencyManager::get(); - ledger->getAvailableUpdates(); + ledger->getAvailableUpdates(itemId); } void QmlCommerce::updateItem(const QString& certificateId) { diff --git a/interface/src/commerce/QmlCommerce.h b/interface/src/commerce/QmlCommerce.h index 1b2c08ee95..fae3f36d87 100644 --- a/interface/src/commerce/QmlCommerce.h +++ b/interface/src/commerce/QmlCommerce.h @@ -90,7 +90,7 @@ protected: Q_INVOKABLE bool uninstallApp(const QString& appHref); Q_INVOKABLE bool openApp(const QString& appHref); - Q_INVOKABLE void getAvailableUpdates(); + Q_INVOKABLE void getAvailableUpdates(const QString& itemId = ""); Q_INVOKABLE void updateItem(const QString& certificateId); private: From db7eea543e73ae720608de3da79f6c50d2dface0 Mon Sep 17 00:00:00 2001 From: Zach Fox Date: Mon, 12 Mar 2018 15:02:35 -0700 Subject: [PATCH 12/32] Initial progress --- .../resources/qml/controls-uit/FilterBar.qml | 296 ++++++++++++++++++ .../qml/hifi/commerce/purchases/Purchases.qml | 13 +- 2 files changed, 303 insertions(+), 6 deletions(-) create mode 100644 interface/resources/qml/controls-uit/FilterBar.qml diff --git a/interface/resources/qml/controls-uit/FilterBar.qml b/interface/resources/qml/controls-uit/FilterBar.qml new file mode 100644 index 0000000000..591d9ef9ed --- /dev/null +++ b/interface/resources/qml/controls-uit/FilterBar.qml @@ -0,0 +1,296 @@ +// +// FilterBar.qml +// +// Created by Zach Fox on 17 Feb 2018-03-12 +// Copyright 2018 High Fidelity, Inc. +// +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// + +import QtQuick 2.5 +import QtQuick.Controls 1.4 +import QtQuick.Controls.Styles 1.4 + +import "../styles-uit" +import "../controls-uit" as HifiControls + +Item { + id: root; + + property int colorScheme: hifi.colorSchemes.light + readonly property bool isLightColorScheme: colorScheme == hifi.colorSchemes.light + readonly property bool isFaintGrayColorScheme: colorScheme == hifi.colorSchemes.faintGray + property bool error: false; + property alias textFieldHeight: textField.height; + property string placeholderText; + property alias dropdownHeight: dropdownContainer.height; + property alias text: textField.text; + signal accepted; + + TextField { + id: textField; + + anchors.top: parent.top; + anchors.right: parent.right; + anchors.left: parent.left; + + font.family: "Fira Sans" + font.pixelSize: hifi.fontSizes.textFieldInput + + property string primaryFilter: ""; + placeholderText: primaryFilter === "" ? root.placeholderText : ""; + + TextMetrics { + id: primaryFilterTextMetrics; + font.family: "FiraSans Regular"; + text: textField.primaryFilter; + } + + // workaround for https://bugreports.qt.io/browse/QTBUG-49297 + Keys.onPressed: { + switch (event.key) { + case Qt.Key_Return: + case Qt.Key_Enter: + event.accepted = true; + + // emit accepted signal manually + if (acceptableInput) { + root.accepted(); + } + break; + case Qt.Key_Backspace: + if (textField.text === "") { + textField.primaryFilter = ""; + } + break; + } + } + + onAccepted: { + root.accepted(); + } + + onActiveFocusChanged: { + if (!activeFocus) { + dropdownContainer.visible = false; + } + } + + style: TextFieldStyle { + id: style; + textColor: { + if (isLightColorScheme) { + if (root.activeFocus) { + hifi.colors.black + } else { + hifi.colors.lightGray + } + } else if (isFaintGrayColorScheme) { + if (root.activeFocus) { + hifi.colors.black + } else { + hifi.colors.lightGray + } + } else { + if (root.activeFocus) { + hifi.colors.white + } else { + hifi.colors.lightGrayText + } + } + } + background: Rectangle { + id: mainFilterBarRectangle; + + color: { + if (isLightColorScheme) { + if (root.activeFocus) { + hifi.colors.white + } else { + hifi.colors.textFieldLightBackground + } + } else if (isFaintGrayColorScheme) { + if (root.activeFocus) { + hifi.colors.white + } else { + hifi.colors.faintGray50 + } + } else { + if (root.activeFocus) { + hifi.colors.black + } else { + hifi.colors.baseGrayShadow + } + } + } + + border.color: root.error ? hifi.colors.redHighlight : + (root.activeFocus ? hifi.colors.primaryHighlight : (isFaintGrayColorScheme ? hifi.colors.lightGrayText : hifi.colors.lightGray)) + border.width: 1 + radius: 4 + + Item { + id: searchButtonContainer; + anchors.left: parent.left; + anchors.verticalCenter: parent.verticalCenter; + height: parent.height; + width: 42; + + // Search icon + HiFiGlyphs { + id: searchIcon; + text: hifi.glyphs.search + color: textColor + size: 40; + anchors.left: parent.left + anchors.verticalCenter: parent.verticalCenter + width: paintedWidth; + } + + // Carat + HiFiGlyphs { + text: hifi.glyphs.caratDn; + color: textColor; + size: 40; + anchors.left: parent.left; + anchors.leftMargin: 14; + width: paintedWidth; + } + + MouseArea { + anchors.fill: parent; + onClicked: { + textField.focus = true; + dropdownContainer.visible = !dropdownContainer.visible; + } + } + } + + Rectangle { + z: 999; + id: primaryFilterContainer; + color: hifi.colors.lightGray; + width: primaryFilterTextMetrics.tightBoundingRect.width + 24; + height: parent.height - 8; + anchors.verticalCenter: parent.verticalCenter; + anchors.left: searchButtonContainer.right; + anchors.leftMargin: 4; + visible: primaryFilterText.text !== ""; + + FiraSansRegular { + id: primaryFilterText; + text: textField.primaryFilter; + anchors.fill: parent; + color: hifi.colors.white; + horizontalAlignment: Text.AlignHCenter; + verticalAlignment: Text.AlignVCenter; + size: 16; + } + } + + // "Clear" button + HiFiGlyphs { + text: hifi.glyphs.error + color: textColor + size: 40 + anchors.right: parent.right + anchors.rightMargin: hifi.dimensions.textPadding - 2 + anchors.verticalCenter: parent.verticalCenter + visible: root.text !== "" || textField.primaryFilter !== ""; + + MouseArea { + anchors.fill: parent; + onClicked: { + root.text = ""; + textField.primaryFilter = ""; + } + } + } + } + + placeholderTextColor: isFaintGrayColorScheme ? hifi.colors.lightGrayText : hifi.colors.lightGray + selectedTextColor: hifi.colors.black + selectionColor: hifi.colors.primaryHighlight + padding.left: 44 + (textField.primaryFilter === "" ? 0 : primaryFilterTextMetrics.tightBoundingRect.width + 32); + padding.right: 44 + } + } + + Component { + id: dropDownButtonComponent; + + Rectangle { + id: dropDownButton; + color: hifi.colors.white; + property alias text: dropDownButtonText.text; + + RalewaySemiBold { + id: dropDownButtonText; + anchors.fill: parent; + anchors.leftMargin: 12; + color: hifi.colors.baseGray; + horizontalAlignment: Text.AlignLeft; + verticalAlignment: Text.AlignVCenter; + size: 18; + } + + MouseArea { + anchors.fill: parent; + hoverEnabled: true; + propagateComposedEvents: false; + onEntered: { + dropDownButton.color = hifi.colors.blueHighlight; + } + onExited: { + dropDownButton.color = hifi.colors.white; + } + onClicked: { + textField.focus = true; + dropdownContainer.buttonClicked(dropDownButtonText.text); + dropdownContainer.visible = false; + } + } + } + } + + Rectangle { + id: dropdownContainer; + visible: false; + height: 100; + width: parent.width; + anchors.top: textField.bottom; + anchors.left: parent.left; + anchors.right: parent.right; + color: hifi.colors.white; + signal buttonClicked(string text); + + onButtonClicked: { + textField.primaryFilter = text; + } + + Loader { + id: item1; + sourceComponent: dropDownButtonComponent; + width: parent.width; + height: 50; + x: 0; + y: 0; + onLoaded: { + item.text = "item1"; + } + } + + Loader { + id: item2; + sourceComponent: dropDownButtonComponent; + width: parent.width; + height: 50; + x: 0; + y: 50; + onLoaded: { + item.text = "item2"; + } + } + } +} diff --git a/interface/resources/qml/hifi/commerce/purchases/Purchases.qml b/interface/resources/qml/hifi/commerce/purchases/Purchases.qml index c505baebf4..cf24ae04d2 100644 --- a/interface/resources/qml/hifi/commerce/purchases/Purchases.qml +++ b/interface/resources/qml/hifi/commerce/purchases/Purchases.qml @@ -296,6 +296,7 @@ Rectangle { // FILTER BAR START // Item { + z: 997; id: filterBarContainer; // Size height: 40; @@ -321,17 +322,16 @@ Rectangle { size: 22; } - HifiControlsUit.TextField { + HifiControlsUit.FilterBar { id: filterBar; property string previousText: ""; colorScheme: hifi.colorSchemes.faintGray; - hasClearButton: true; - hasRoundedBorder: true; + anchors.top: parent.top; + anchors.right: parent.right; anchors.left: myText.right; anchors.leftMargin: 16; - height: 39; - anchors.verticalCenter: parent.verticalCenter; - anchors.right: parent.right; + textFieldHeight: 39; + height: textFieldHeight + dropdownHeight; placeholderText: "filter items"; onTextChanged: { @@ -350,6 +350,7 @@ Rectangle { // HifiControlsUit.Separator { + z: 996; id: separator; colorScheme: 2; anchors.left: parent.left; From f1121db4da04b6eece444d8182206e82c4276869 Mon Sep 17 00:00:00 2001 From: Zach Fox Date: Mon, 12 Mar 2018 15:54:11 -0700 Subject: [PATCH 13/32] It's working! --- .../resources/qml/controls-uit/FilterBar.qml | 115 ++++++++---------- .../qml/hifi/commerce/purchases/Purchases.qml | 84 ++++++++++--- 2 files changed, 117 insertions(+), 82 deletions(-) diff --git a/interface/resources/qml/controls-uit/FilterBar.qml b/interface/resources/qml/controls-uit/FilterBar.qml index 591d9ef9ed..14e7aa5b5c 100644 --- a/interface/resources/qml/controls-uit/FilterBar.qml +++ b/interface/resources/qml/controls-uit/FilterBar.qml @@ -26,6 +26,8 @@ Item { property string placeholderText; property alias dropdownHeight: dropdownContainer.height; property alias text: textField.text; + property alias primaryFilterChoices: filterBarModel; + property string primaryFilter: ""; signal accepted; TextField { @@ -38,13 +40,12 @@ Item { font.family: "Fira Sans" font.pixelSize: hifi.fontSizes.textFieldInput - property string primaryFilter: ""; - placeholderText: primaryFilter === "" ? root.placeholderText : ""; + placeholderText: root.primaryFilter === "" ? root.placeholderText : ""; TextMetrics { id: primaryFilterTextMetrics; font.family: "FiraSans Regular"; - text: textField.primaryFilter; + text: root.primaryFilter; } // workaround for https://bugreports.qt.io/browse/QTBUG-49297 @@ -61,7 +62,7 @@ Item { break; case Qt.Key_Backspace: if (textField.text === "") { - textField.primaryFilter = ""; + root.primaryFilter = ""; } break; } @@ -180,7 +181,7 @@ Item { FiraSansRegular { id: primaryFilterText; - text: textField.primaryFilter; + text: root.primaryFilter; anchors.fill: parent; color: hifi.colors.white; horizontalAlignment: Text.AlignHCenter; @@ -197,13 +198,13 @@ Item { anchors.right: parent.right anchors.rightMargin: hifi.dimensions.textPadding - 2 anchors.verticalCenter: parent.verticalCenter - visible: root.text !== "" || textField.primaryFilter !== ""; + visible: root.text !== "" || root.primaryFilter !== ""; MouseArea { anchors.fill: parent; onClicked: { root.text = ""; - textField.primaryFilter = ""; + root.primaryFilter = ""; } } } @@ -212,52 +213,15 @@ Item { placeholderTextColor: isFaintGrayColorScheme ? hifi.colors.lightGrayText : hifi.colors.lightGray selectedTextColor: hifi.colors.black selectionColor: hifi.colors.primaryHighlight - padding.left: 44 + (textField.primaryFilter === "" ? 0 : primaryFilterTextMetrics.tightBoundingRect.width + 32); + padding.left: 44 + (root.primaryFilter === "" ? 0 : primaryFilterTextMetrics.tightBoundingRect.width + 32); padding.right: 44 } } - Component { - id: dropDownButtonComponent; - - Rectangle { - id: dropDownButton; - color: hifi.colors.white; - property alias text: dropDownButtonText.text; - - RalewaySemiBold { - id: dropDownButtonText; - anchors.fill: parent; - anchors.leftMargin: 12; - color: hifi.colors.baseGray; - horizontalAlignment: Text.AlignLeft; - verticalAlignment: Text.AlignVCenter; - size: 18; - } - - MouseArea { - anchors.fill: parent; - hoverEnabled: true; - propagateComposedEvents: false; - onEntered: { - dropDownButton.color = hifi.colors.blueHighlight; - } - onExited: { - dropDownButton.color = hifi.colors.white; - } - onClicked: { - textField.focus = true; - dropdownContainer.buttonClicked(dropDownButtonText.text); - dropdownContainer.visible = false; - } - } - } - } - Rectangle { id: dropdownContainer; visible: false; - height: 100; + height: 50 * filterBarModel.count; width: parent.width; anchors.top: textField.bottom; anchors.left: parent.left; @@ -266,30 +230,49 @@ Item { signal buttonClicked(string text); onButtonClicked: { - textField.primaryFilter = text; + root.primaryFilter = text; } - Loader { - id: item1; - sourceComponent: dropDownButtonComponent; - width: parent.width; - height: 50; - x: 0; - y: 0; - onLoaded: { - item.text = "item1"; - } + ListModel { + id: filterBarModel; } - Loader { - id: item2; - sourceComponent: dropDownButtonComponent; - width: parent.width; - height: 50; - x: 0; - y: 50; - onLoaded: { - item.text = "item2"; + ListView { + anchors.fill: parent; + model: filterBarModel; + delegate: Rectangle { + id: dropDownButton; + color: hifi.colors.white; + width: parent.width; + height: 50; + + RalewaySemiBold { + id: dropDownButtonText; + text: model.displayName; + anchors.fill: parent; + anchors.leftMargin: 12; + color: hifi.colors.baseGray; + horizontalAlignment: Text.AlignLeft; + verticalAlignment: Text.AlignVCenter; + size: 18; + } + + MouseArea { + anchors.fill: parent; + hoverEnabled: true; + propagateComposedEvents: false; + onEntered: { + dropDownButton.color = hifi.colors.blueHighlight; + } + onExited: { + dropDownButton.color = hifi.colors.white; + } + onClicked: { + textField.focus = true; + dropdownContainer.buttonClicked(model.filterName); + dropdownContainer.visible = false; + } + } } } } diff --git a/interface/resources/qml/hifi/commerce/purchases/Purchases.qml b/interface/resources/qml/hifi/commerce/purchases/Purchases.qml index cf24ae04d2..83a4ba4d98 100644 --- a/interface/resources/qml/hifi/commerce/purchases/Purchases.qml +++ b/interface/resources/qml/hifi/commerce/purchases/Purchases.qml @@ -325,6 +325,7 @@ Rectangle { HifiControlsUit.FilterBar { id: filterBar; property string previousText: ""; + property string previousPrimaryFilter: ""; colorScheme: hifi.colorSchemes.faintGray; anchors.top: parent.top; anchors.right: parent.right; @@ -334,6 +335,39 @@ Rectangle { height: textFieldHeight + dropdownHeight; placeholderText: "filter items"; + Component.onCompleted: { + var choices = [ + { + "displayName": "App", + "filterName": "app" + }, + { + "displayName": "Avatar", + "filterName": "avatar" + }, + { + "displayName": "Content Set", + "filterName": "contentSet" + }, + { + "displayName": "Entity", + "filterName": "entity" + }, + { + "displayName": "Wearable", + "filterName": "wearable" + } + ] + filterBar.primaryFilterChoices.clear(); + filterBar.primaryFilterChoices.append(choices); + } + + onPrimaryFilterChanged: { + buildFilteredPurchasesModel(); + purchasesContentsList.positionViewAtIndex(0, ListView.Beginning) + filterBar.previousPrimaryFilter = filterBar.primaryFilter; + } + onTextChanged: { buildFilteredPurchasesModel(); purchasesContentsList.positionViewAtIndex(0, ListView.Beginning) @@ -399,21 +433,7 @@ Rectangle { displayedItemCount: model.displayedItemCount; permissionExplanationCardVisible: model.permissionExplanationCardVisible; isInstalled: model.isInstalled; - 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"; - } - } + itemType: model.itemType; anchors.topMargin: 10; anchors.bottomMargin: 10; @@ -661,6 +681,7 @@ Rectangle { var sameItemCount = 0; tempPurchasesModel.clear(); + for (var i = 0; i < purchasesModel.count; i++) { if (purchasesModel.get(i).title.toLowerCase().indexOf(filterBar.text.toLowerCase()) !== -1) { if (purchasesModel.get(i).status !== "confirmed" && !root.isShowingMyItems) { @@ -672,6 +693,35 @@ Rectangle { } } + // primaryFilter filtering and adding of itemType property to model + var currentItemType, currentRootFileUrl, currentCategories; + for (var i = 0; i < tempPurchasesModel.count; i++) { + currentRootFileUrl = tempPurchasesModel.get(i).root_file_url; + currentCategories = tempPurchasesModel.get(i).categories; + + if (currentRootFileUrl.indexOf(".fst") > -1) { + currentItemType = "avatar"; + } else if (currentCategories.indexOf("Wearables") > -1) { + currentItemType = "wearable"; + } else if (currentRootFileUrl.endsWith('.json.gz')) { + currentItemType = "contentSet"; + } else if (currentRootFileUrl.endsWith('.app.json')) { + currentItemType = "app"; + } else if (currentRootFileUrl.endsWith('.json')) { + currentItemType = "entity"; + } else { + currentItemType = "unknown"; + } + + if (filterBar.primaryFilter !== "" && + currentItemType.toLowerCase() !== filterBar.primaryFilter.toLowerCase()) { + tempPurchasesModel.remove(i); + i--; + } else { + tempPurchasesModel.setProperty(i, 'itemType', currentItemType); + } + } + for (var i = 0; i < tempPurchasesModel.count; i++) { if (!filteredPurchasesModel.get(i)) { sameItemCount = -1; @@ -683,7 +733,9 @@ Rectangle { } } - if (sameItemCount !== tempPurchasesModel.count || filterBar.text !== filterBar.previousText) { + if (sameItemCount !== tempPurchasesModel.count || + filterBar.text !== filterBar.previousText || + filterBar.primaryFilter !== filterBar.previousPrimaryFilter) { filteredPurchasesModel.clear(); var currentId; for (var i = 0; i < tempPurchasesModel.count; i++) { From 9c7d857ecbbcff36441053a76a6edef5d934129d Mon Sep 17 00:00:00 2001 From: Zach Fox Date: Mon, 12 Mar 2018 16:15:50 -0700 Subject: [PATCH 14/32] Fix some focus issues --- interface/resources/qml/controls-uit/FilterBar.qml | 8 +++++--- .../resources/qml/hifi/commerce/purchases/Purchases.qml | 6 +----- 2 files changed, 6 insertions(+), 8 deletions(-) diff --git a/interface/resources/qml/controls-uit/FilterBar.qml b/interface/resources/qml/controls-uit/FilterBar.qml index 14e7aa5b5c..47395265e3 100644 --- a/interface/resources/qml/controls-uit/FilterBar.qml +++ b/interface/resources/qml/controls-uit/FilterBar.qml @@ -27,6 +27,7 @@ Item { property alias dropdownHeight: dropdownContainer.height; property alias text: textField.text; property alias primaryFilterChoices: filterBarModel; + property alias textFieldFocused: textField.focus; property string primaryFilter: ""; signal accepted; @@ -69,7 +70,7 @@ Item { } onAccepted: { - root.accepted(); + root.forceActiveFocus(); } onActiveFocusChanged: { @@ -162,7 +163,7 @@ Item { MouseArea { anchors.fill: parent; onClicked: { - textField.focus = true; + textField.forceActiveFocus(); dropdownContainer.visible = !dropdownContainer.visible; } } @@ -205,6 +206,7 @@ Item { onClicked: { root.text = ""; root.primaryFilter = ""; + root.forceActiveFocus(); } } } @@ -268,7 +270,7 @@ Item { dropDownButton.color = hifi.colors.white; } onClicked: { - textField.focus = true; + textField.forceActiveFocus(); dropdownContainer.buttonClicked(model.filterName); dropdownContainer.visible = false; } diff --git a/interface/resources/qml/hifi/commerce/purchases/Purchases.qml b/interface/resources/qml/hifi/commerce/purchases/Purchases.qml index 83a4ba4d98..a9415d434d 100644 --- a/interface/resources/qml/hifi/commerce/purchases/Purchases.qml +++ b/interface/resources/qml/hifi/commerce/purchases/Purchases.qml @@ -373,10 +373,6 @@ Rectangle { purchasesContentsList.positionViewAtIndex(0, ListView.Beginning) filterBar.previousText = filterBar.text; } - - onAccepted: { - focus = false; - } } } // @@ -610,7 +606,7 @@ Rectangle { HifiControlsUit.Keyboard { id: keyboard; - raised: HMD.mounted && filterBar.focus; + raised: HMD.mounted && filterBar.textFieldFocused; numeric: parent.punctuationMode; anchors { bottom: parent.bottom; From bce7fb1ff1fa03c530d4f863961dc55c839495b5 Mon Sep 17 00:00:00 2001 From: Zach Fox Date: Mon, 12 Mar 2018 16:40:11 -0700 Subject: [PATCH 15/32] Improvements --- .../resources/qml/controls-uit/FilterBar.qml | 66 +++++++++++-------- .../qml/hifi/commerce/purchases/Purchases.qml | 10 +-- 2 files changed, 44 insertions(+), 32 deletions(-) diff --git a/interface/resources/qml/controls-uit/FilterBar.qml b/interface/resources/qml/controls-uit/FilterBar.qml index 47395265e3..05e9869e0a 100644 --- a/interface/resources/qml/controls-uit/FilterBar.qml +++ b/interface/resources/qml/controls-uit/FilterBar.qml @@ -28,9 +28,21 @@ Item { property alias text: textField.text; property alias primaryFilterChoices: filterBarModel; property alias textFieldFocused: textField.focus; - property string primaryFilter: ""; + property int primaryFilter_index: -1; + property string primaryFilter_filterName: ""; + property string primaryFilter_displayName: ""; signal accepted; + onPrimaryFilter_indexChanged: { + if (primaryFilter_index === -1) { + primaryFilter_filterName = ""; + primaryFilter_displayName = ""; + } else { + primaryFilter_filterName = filterBarModel.get(primaryFilter_index).filterName; + primaryFilter_displayName = filterBarModel.get(primaryFilter_index).displayName; + } + } + TextField { id: textField; @@ -39,14 +51,15 @@ Item { anchors.left: parent.left; font.family: "Fira Sans" - font.pixelSize: hifi.fontSizes.textFieldInput + font.pixelSize: hifi.fontSizes.textFieldInput; - placeholderText: root.primaryFilter === "" ? root.placeholderText : ""; + placeholderText: root.primaryFilter_index === -1 ? root.placeholderText : ""; TextMetrics { id: primaryFilterTextMetrics; font.family: "FiraSans Regular"; - text: root.primaryFilter; + font.pixelSize: hifi.fontSizes.textFieldInput; + text: root.primaryFilter_displayName; } // workaround for https://bugreports.qt.io/browse/QTBUG-49297 @@ -59,11 +72,12 @@ Item { // emit accepted signal manually if (acceptableInput) { root.accepted(); + root.forceActiveFocus(); } break; case Qt.Key_Backspace: if (textField.text === "") { - root.primaryFilter = ""; + primaryFilter_index = -1; } break; } @@ -83,19 +97,19 @@ Item { id: style; textColor: { if (isLightColorScheme) { - if (root.activeFocus) { + if (textField.activeFocus) { hifi.colors.black } else { hifi.colors.lightGray } } else if (isFaintGrayColorScheme) { - if (root.activeFocus) { + if (textField.activeFocus) { hifi.colors.black } else { hifi.colors.lightGray } } else { - if (root.activeFocus) { + if (textField.activeFocus) { hifi.colors.white } else { hifi.colors.lightGrayText @@ -107,19 +121,19 @@ Item { color: { if (isLightColorScheme) { - if (root.activeFocus) { + if (textField.activeFocus) { hifi.colors.white } else { hifi.colors.textFieldLightBackground } } else if (isFaintGrayColorScheme) { - if (root.activeFocus) { + if (textField.activeFocus) { hifi.colors.white } else { hifi.colors.faintGray50 } } else { - if (root.activeFocus) { + if (textField.activeFocus) { hifi.colors.black } else { hifi.colors.baseGrayShadow @@ -127,8 +141,8 @@ Item { } } - border.color: root.error ? hifi.colors.redHighlight : - (root.activeFocus ? hifi.colors.primaryHighlight : (isFaintGrayColorScheme ? hifi.colors.lightGrayText : hifi.colors.lightGray)) + border.color: textField.error ? hifi.colors.redHighlight : + (textField.activeFocus ? hifi.colors.primaryHighlight : (isFaintGrayColorScheme ? hifi.colors.lightGrayText : hifi.colors.lightGray)) border.width: 1 radius: 4 @@ -156,7 +170,7 @@ Item { color: textColor; size: 40; anchors.left: parent.left; - anchors.leftMargin: 14; + anchors.leftMargin: 15; width: paintedWidth; } @@ -173,7 +187,7 @@ Item { z: 999; id: primaryFilterContainer; color: hifi.colors.lightGray; - width: primaryFilterTextMetrics.tightBoundingRect.width + 24; + width: primaryFilterTextMetrics.tightBoundingRect.width + 14; height: parent.height - 8; anchors.verticalCenter: parent.verticalCenter; anchors.left: searchButtonContainer.right; @@ -182,12 +196,12 @@ Item { FiraSansRegular { id: primaryFilterText; - text: root.primaryFilter; + text: root.primaryFilter_displayName; anchors.fill: parent; color: hifi.colors.white; horizontalAlignment: Text.AlignHCenter; verticalAlignment: Text.AlignVCenter; - size: 16; + size: hifi.fontSizes.textFieldInput; } } @@ -199,14 +213,15 @@ Item { anchors.right: parent.right anchors.rightMargin: hifi.dimensions.textPadding - 2 anchors.verticalCenter: parent.verticalCenter - visible: root.text !== "" || root.primaryFilter !== ""; + visible: root.text !== "" || root.primaryFilter_index !== -1; MouseArea { anchors.fill: parent; onClicked: { root.text = ""; - root.primaryFilter = ""; - root.forceActiveFocus(); + root.primaryFilter_index = -1; + dropdownContainer.visible = false; + textField.forceActiveFocus(); } } } @@ -215,7 +230,7 @@ Item { placeholderTextColor: isFaintGrayColorScheme ? hifi.colors.lightGrayText : hifi.colors.lightGray selectedTextColor: hifi.colors.black selectionColor: hifi.colors.primaryHighlight - padding.left: 44 + (root.primaryFilter === "" ? 0 : primaryFilterTextMetrics.tightBoundingRect.width + 32); + padding.left: 44 + (root.primaryFilter_index === -1 ? 0 : primaryFilterTextMetrics.tightBoundingRect.width + 24); padding.right: 44 } } @@ -229,17 +244,14 @@ Item { anchors.left: parent.left; anchors.right: parent.right; color: hifi.colors.white; - signal buttonClicked(string text); - - onButtonClicked: { - root.primaryFilter = text; - } ListModel { id: filterBarModel; } ListView { + id: dropdownListView; + interactive: false; anchors.fill: parent; model: filterBarModel; delegate: Rectangle { @@ -271,7 +283,7 @@ Item { } onClicked: { textField.forceActiveFocus(); - dropdownContainer.buttonClicked(model.filterName); + root.primaryFilter_index = index; dropdownContainer.visible = false; } } diff --git a/interface/resources/qml/hifi/commerce/purchases/Purchases.qml b/interface/resources/qml/hifi/commerce/purchases/Purchases.qml index a9415d434d..bfbe240aca 100644 --- a/interface/resources/qml/hifi/commerce/purchases/Purchases.qml +++ b/interface/resources/qml/hifi/commerce/purchases/Purchases.qml @@ -362,10 +362,10 @@ Rectangle { filterBar.primaryFilterChoices.append(choices); } - onPrimaryFilterChanged: { + onPrimaryFilter_displayNameChanged: { buildFilteredPurchasesModel(); purchasesContentsList.positionViewAtIndex(0, ListView.Beginning) - filterBar.previousPrimaryFilter = filterBar.primaryFilter; + filterBar.previousPrimaryFilter = filterBar.primaryFilter_displayName; } onTextChanged: { @@ -709,8 +709,8 @@ Rectangle { currentItemType = "unknown"; } - if (filterBar.primaryFilter !== "" && - currentItemType.toLowerCase() !== filterBar.primaryFilter.toLowerCase()) { + if (filterBar.primaryFilter_filterName !== "" && + currentItemType.toLowerCase() !== filterBar.primaryFilter_filterName.toLowerCase()) { tempPurchasesModel.remove(i); i--; } else { @@ -785,7 +785,7 @@ Rectangle { function fromScript(message) { switch (message.method) { case 'updatePurchases': - referrerURL = message.referrerURL; + referrerURL = message.referrerURL || ""; titleBarContainer.referrerURL = message.referrerURL; filterBar.text = message.filterText ? message.filterText : ""; break; From 50e709c738da464cca4d9b962754e58f1a2cfef0 Mon Sep 17 00:00:00 2001 From: Zach Fox Date: Tue, 13 Mar 2018 12:13:14 -0700 Subject: [PATCH 16/32] Add updates; drop shadow --- interface/resources/qml/controls-uit/FilterBar.qml | 14 +++++++++++++- .../qml/hifi/commerce/purchases/Purchases.qml | 9 +++++++-- 2 files changed, 20 insertions(+), 3 deletions(-) diff --git a/interface/resources/qml/controls-uit/FilterBar.qml b/interface/resources/qml/controls-uit/FilterBar.qml index 05e9869e0a..7b2123c692 100644 --- a/interface/resources/qml/controls-uit/FilterBar.qml +++ b/interface/resources/qml/controls-uit/FilterBar.qml @@ -11,6 +11,7 @@ import QtQuick 2.5 import QtQuick.Controls 1.4 import QtQuick.Controls.Styles 1.4 +import QtGraphicalEffects 1.0 import "../styles-uit" import "../controls-uit" as HifiControls @@ -27,7 +28,7 @@ Item { property alias dropdownHeight: dropdownContainer.height; property alias text: textField.text; property alias primaryFilterChoices: filterBarModel; - property alias textFieldFocused: textField.focus; + property alias textFieldFocused: textField.activeFocus; property int primaryFilter_index: -1; property string primaryFilter_filterName: ""; property string primaryFilter_displayName: ""; @@ -290,4 +291,15 @@ Item { } } } + + DropShadow { + anchors.fill: dropdownContainer; + horizontalOffset: 0; + verticalOffset: 4; + radius: 4.0; + samples: 9 + color: Qt.rgba(0, 0, 0, 0.25); + source: dropdownContainer; + visible: dropdownContainer.visible; + } } diff --git a/interface/resources/qml/hifi/commerce/purchases/Purchases.qml b/interface/resources/qml/hifi/commerce/purchases/Purchases.qml index bfbe240aca..d3ab3f8d75 100644 --- a/interface/resources/qml/hifi/commerce/purchases/Purchases.qml +++ b/interface/resources/qml/hifi/commerce/purchases/Purchases.qml @@ -356,6 +356,10 @@ Rectangle { { "displayName": "Wearable", "filterName": "wearable" + }, + { + "displayName": "Updatable", + "filterName": "updatable" } ] filterBar.primaryFilterChoices.clear(); @@ -709,8 +713,9 @@ Rectangle { currentItemType = "unknown"; } - if (filterBar.primaryFilter_filterName !== "" && - currentItemType.toLowerCase() !== filterBar.primaryFilter_filterName.toLowerCase()) { + if (filterBar.primaryFilter_displayName !== "" && + (filterBar.primaryFilter_displayName.toLowerCase() !== currentItemType.toLowerCase())) { //|| UNCOMMENT WHEN UPGRADES ARE IN + //(filterBar.primaryFilter_displayName === "Updatable" && tempPurchasesModel.get(i).upgradeUrl !== "")) { tempPurchasesModel.remove(i); i--; } else { From 5563edd9578149db66c1e0ceee4f8d03f0785304 Mon Sep 17 00:00:00 2001 From: Zach Fox Date: Tue, 13 Mar 2018 13:11:59 -0700 Subject: [PATCH 17/32] Focus fix; Switch to QQC2 --- .../resources/qml/controls-uit/FilterBar.qml | 268 +++++++++--------- .../qml/hifi/commerce/purchases/Purchases.qml | 3 +- 2 files changed, 138 insertions(+), 133 deletions(-) diff --git a/interface/resources/qml/controls-uit/FilterBar.qml b/interface/resources/qml/controls-uit/FilterBar.qml index 7b2123c692..efca4d3949 100644 --- a/interface/resources/qml/controls-uit/FilterBar.qml +++ b/interface/resources/qml/controls-uit/FilterBar.qml @@ -8,9 +8,8 @@ // See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html // -import QtQuick 2.5 -import QtQuick.Controls 1.4 -import QtQuick.Controls.Styles 1.4 +import QtQuick 2.9 +import QtQuick.Controls 2.2 import QtGraphicalEffects 1.0 import "../styles-uit" @@ -28,7 +27,6 @@ Item { property alias dropdownHeight: dropdownContainer.height; property alias text: textField.text; property alias primaryFilterChoices: filterBarModel; - property alias textFieldFocused: textField.activeFocus; property int primaryFilter_index: -1; property string primaryFilter_filterName: ""; property string primaryFilter_displayName: ""; @@ -94,146 +92,152 @@ Item { } } - style: TextFieldStyle { - id: style; - textColor: { - if (isLightColorScheme) { - if (textField.activeFocus) { - hifi.colors.black - } else { - hifi.colors.lightGray - } - } else if (isFaintGrayColorScheme) { - if (textField.activeFocus) { - hifi.colors.black - } else { - hifi.colors.lightGray - } + color: { + if (isLightColorScheme) { + if (textField.activeFocus) { + hifi.colors.black } else { + hifi.colors.lightGray + } + } else if (isFaintGrayColorScheme) { + if (textField.activeFocus) { + hifi.colors.black + } else { + hifi.colors.lightGray + } + } else { + if (textField.activeFocus) { + hifi.colors.white + } else { + hifi.colors.lightGrayText + } + } + } + + background: Rectangle { + id: mainFilterBarRectangle; + + color: { + if (isLightColorScheme) { if (textField.activeFocus) { hifi.colors.white } else { - hifi.colors.lightGrayText + hifi.colors.textFieldLightBackground } - } - } - background: Rectangle { - id: mainFilterBarRectangle; - - color: { - if (isLightColorScheme) { - if (textField.activeFocus) { - hifi.colors.white - } else { - hifi.colors.textFieldLightBackground - } - } else if (isFaintGrayColorScheme) { - if (textField.activeFocus) { - hifi.colors.white - } else { - hifi.colors.faintGray50 - } + } else if (isFaintGrayColorScheme) { + if (textField.activeFocus) { + hifi.colors.white } else { - if (textField.activeFocus) { - hifi.colors.black - } else { - hifi.colors.baseGrayShadow - } + hifi.colors.faintGray50 } - } - - border.color: textField.error ? hifi.colors.redHighlight : - (textField.activeFocus ? hifi.colors.primaryHighlight : (isFaintGrayColorScheme ? hifi.colors.lightGrayText : hifi.colors.lightGray)) - border.width: 1 - radius: 4 - - Item { - id: searchButtonContainer; - anchors.left: parent.left; - anchors.verticalCenter: parent.verticalCenter; - height: parent.height; - width: 42; - - // Search icon - HiFiGlyphs { - id: searchIcon; - text: hifi.glyphs.search - color: textColor - size: 40; - anchors.left: parent.left - anchors.verticalCenter: parent.verticalCenter - width: paintedWidth; - } - - // Carat - HiFiGlyphs { - text: hifi.glyphs.caratDn; - color: textColor; - size: 40; - anchors.left: parent.left; - anchors.leftMargin: 15; - width: paintedWidth; - } - - MouseArea { - anchors.fill: parent; - onClicked: { - textField.forceActiveFocus(); - dropdownContainer.visible = !dropdownContainer.visible; - } - } - } - - Rectangle { - z: 999; - id: primaryFilterContainer; - color: hifi.colors.lightGray; - width: primaryFilterTextMetrics.tightBoundingRect.width + 14; - height: parent.height - 8; - anchors.verticalCenter: parent.verticalCenter; - anchors.left: searchButtonContainer.right; - anchors.leftMargin: 4; - visible: primaryFilterText.text !== ""; - - FiraSansRegular { - id: primaryFilterText; - text: root.primaryFilter_displayName; - anchors.fill: parent; - color: hifi.colors.white; - horizontalAlignment: Text.AlignHCenter; - verticalAlignment: Text.AlignVCenter; - size: hifi.fontSizes.textFieldInput; - } - } - - // "Clear" button - HiFiGlyphs { - text: hifi.glyphs.error - color: textColor - size: 40 - anchors.right: parent.right - anchors.rightMargin: hifi.dimensions.textPadding - 2 - anchors.verticalCenter: parent.verticalCenter - visible: root.text !== "" || root.primaryFilter_index !== -1; - - MouseArea { - anchors.fill: parent; - onClicked: { - root.text = ""; - root.primaryFilter_index = -1; - dropdownContainer.visible = false; - textField.forceActiveFocus(); - } + } else { + if (textField.activeFocus) { + hifi.colors.black + } else { + hifi.colors.baseGrayShadow } } } - placeholderTextColor: isFaintGrayColorScheme ? hifi.colors.lightGrayText : hifi.colors.lightGray - selectedTextColor: hifi.colors.black - selectionColor: hifi.colors.primaryHighlight - padding.left: 44 + (root.primaryFilter_index === -1 ? 0 : primaryFilterTextMetrics.tightBoundingRect.width + 24); - padding.right: 44 + border.color: textField.error ? hifi.colors.redHighlight : + (textField.activeFocus ? hifi.colors.primaryHighlight : (isFaintGrayColorScheme ? hifi.colors.lightGrayText : hifi.colors.lightGray)) + border.width: 1 + radius: 4 + + Item { + id: searchButtonContainer; + anchors.left: parent.left; + anchors.verticalCenter: parent.verticalCenter; + height: parent.height; + width: 42; + + // Search icon + HiFiGlyphs { + id: searchIcon; + text: hifi.glyphs.search + color: textColor + size: 40; + anchors.left: parent.left + anchors.verticalCenter: parent.verticalCenter + width: paintedWidth; + } + + // Carat + HiFiGlyphs { + text: hifi.glyphs.caratDn; + color: textColor; + size: 40; + anchors.left: parent.left; + anchors.leftMargin: 15; + width: paintedWidth; + } + + MouseArea { + anchors.fill: parent; + onClicked: { + textField.forceActiveFocus(); + dropdownContainer.visible = !dropdownContainer.visible; + } + } + } + + Rectangle { + z: 999; + id: primaryFilterContainer; + color: textField.activeFocus ? hifi.colors.blueHighlight : hifi.colors.lightGray; + width: primaryFilterTextMetrics.tightBoundingRect.width + 14; + height: parent.height - 8; + anchors.verticalCenter: parent.verticalCenter; + anchors.left: searchButtonContainer.right; + anchors.leftMargin: 4; + visible: primaryFilterText.text !== ""; + radius: 4; + + FiraSansRegular { + id: primaryFilterText; + text: root.primaryFilter_displayName; + anchors.fill: parent; + color: hifi.colors.white; + horizontalAlignment: Text.AlignHCenter; + verticalAlignment: Text.AlignVCenter; + size: hifi.fontSizes.textFieldInput; + } + + MouseArea { + anchors.fill: parent; + onClicked: { + textField.forceActiveFocus(); + } + } + } + + // "Clear" button + HiFiGlyphs { + text: hifi.glyphs.error + color: textColor + size: 40 + anchors.right: parent.right + anchors.rightMargin: hifi.dimensions.textPadding - 2 + anchors.verticalCenter: parent.verticalCenter + visible: root.text !== "" || root.primaryFilter_index !== -1; + + MouseArea { + anchors.fill: parent; + onClicked: { + root.text = ""; + root.primaryFilter_index = -1; + dropdownContainer.visible = false; + textField.forceActiveFocus(); + } + } + } } + + //placeholderTextColor: isFaintGrayColorScheme ? hifi.colors.lightGrayText : hifi.colors.lightGray + selectedTextColor: hifi.colors.black + selectionColor: hifi.colors.primaryHighlight + leftPadding: 44 + (root.primaryFilter_index === -1 ? 0 : primaryFilterTextMetrics.tightBoundingRect.width + 24); + rightPadding: 44; } Rectangle { diff --git a/interface/resources/qml/hifi/commerce/purchases/Purchases.qml b/interface/resources/qml/hifi/commerce/purchases/Purchases.qml index d3ab3f8d75..c12e398ce4 100644 --- a/interface/resources/qml/hifi/commerce/purchases/Purchases.qml +++ b/interface/resources/qml/hifi/commerce/purchases/Purchases.qml @@ -37,6 +37,7 @@ Rectangle { property bool isDebuggingFirstUseTutorial: false; property int pendingItemCount: 0; property string installedApps; + property bool keyboardRaised: false; // Style color: hifi.colors.white; Connections { @@ -610,7 +611,7 @@ Rectangle { HifiControlsUit.Keyboard { id: keyboard; - raised: HMD.mounted && filterBar.textFieldFocused; + raised: HMD.mounted && parent.keyboardRaised; numeric: parent.punctuationMode; anchors { bottom: parent.bottom; From a94920898855de6abc0ad054fe5caf323d2fdf41 Mon Sep 17 00:00:00 2001 From: Zach Fox Date: Tue, 13 Mar 2018 13:23:32 -0700 Subject: [PATCH 18/32] Design tweaks --- interface/resources/qml/controls-uit/FilterBar.qml | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/interface/resources/qml/controls-uit/FilterBar.qml b/interface/resources/qml/controls-uit/FilterBar.qml index efca4d3949..333ccc0e59 100644 --- a/interface/resources/qml/controls-uit/FilterBar.qml +++ b/interface/resources/qml/controls-uit/FilterBar.qml @@ -58,6 +58,7 @@ Item { id: primaryFilterTextMetrics; font.family: "FiraSans Regular"; font.pixelSize: hifi.fontSizes.textFieldInput; + font.capitalization: Font.AllUppercase; text: root.primaryFilter_displayName; } @@ -184,23 +185,24 @@ Item { Rectangle { z: 999; id: primaryFilterContainer; - color: textField.activeFocus ? hifi.colors.blueHighlight : hifi.colors.lightGray; + color: textField.activeFocus ? hifi.colors.faintGray : hifi.colors.white; width: primaryFilterTextMetrics.tightBoundingRect.width + 14; height: parent.height - 8; anchors.verticalCenter: parent.verticalCenter; anchors.left: searchButtonContainer.right; anchors.leftMargin: 4; visible: primaryFilterText.text !== ""; - radius: 4; + radius: height/2; FiraSansRegular { id: primaryFilterText; text: root.primaryFilter_displayName; anchors.fill: parent; - color: hifi.colors.white; + color: textField.activeFocus ? hifi.colors.black : hifi.colors.lightGray; horizontalAlignment: Text.AlignHCenter; verticalAlignment: Text.AlignVCenter; size: hifi.fontSizes.textFieldInput; + font.capitalization: Font.AllUppercase; } MouseArea { @@ -236,7 +238,7 @@ Item { //placeholderTextColor: isFaintGrayColorScheme ? hifi.colors.lightGrayText : hifi.colors.lightGray selectedTextColor: hifi.colors.black selectionColor: hifi.colors.primaryHighlight - leftPadding: 44 + (root.primaryFilter_index === -1 ? 0 : primaryFilterTextMetrics.tightBoundingRect.width + 24); + leftPadding: 44 + (root.primaryFilter_index === -1 ? 0 : primaryFilterTextMetrics.tightBoundingRect.width + 20); rightPadding: 44; } From 7b6b08fd26983c50c830780a0dab42c73e5bde48 Mon Sep 17 00:00:00 2001 From: Zach Fox Date: Tue, 13 Mar 2018 13:51:10 -0700 Subject: [PATCH 19/32] Upgrade specific edition --- .../qml/hifi/commerce/checkout/Checkout.qml | 5 +++++ scripts/system/html/js/marketplacesInject.js | 12 ++++++++---- 2 files changed, 13 insertions(+), 4 deletions(-) diff --git a/interface/resources/qml/hifi/commerce/checkout/Checkout.qml b/interface/resources/qml/hifi/commerce/checkout/Checkout.qml index fc47aea337..15a11cf0d7 100644 --- a/interface/resources/qml/hifi/commerce/checkout/Checkout.qml +++ b/interface/resources/qml/hifi/commerce/checkout/Checkout.qml @@ -35,6 +35,7 @@ Rectangle { property string itemId; property string itemHref; property string itemAuthor; + property int itemEdition: -1; property string certificateId; property double balanceAfterPurchase; property bool alreadyOwned: false; @@ -143,6 +144,9 @@ Rectangle { // OR the ID of an "updated" item, we're updating. if (root.itemId === result.data.updates[i].item_id || root.itemId === result.data.updates[i].updated_item_id) { + if (root.itemEdition !== -1 && root.itemEdition !== result.data.updates[i].edition_number) { + continue; + } root.isUpdating = true; // This CertID is the one corresponding to the base item CertID that the user already owns root.certificateId = result.data.updates[i].certificate_id; @@ -1059,6 +1063,7 @@ Rectangle { root.itemHref = message.params.itemHref; root.referrer = message.params.referrer; root.itemAuthor = message.params.itemAuthor; + root.itemEdition = message.params.itemEdition; refreshBuyUI(); break; default: diff --git a/scripts/system/html/js/marketplacesInject.js b/scripts/system/html/js/marketplacesInject.js index 46b0b6c971..2565a998f1 100644 --- a/scripts/system/html/js/marketplacesInject.js +++ b/scripts/system/html/js/marketplacesInject.js @@ -243,7 +243,7 @@ }); } - function buyButtonClicked(id, name, author, price, href, referrer) { + function buyButtonClicked(id, name, author, price, href, referrer, edition) { EventBridge.emitWebEvent(JSON.stringify({ type: "CHECKOUT", itemId: id, @@ -251,7 +251,8 @@ itemPrice: price ? parseInt(price, 10) : 0, itemHref: href, referrer: referrer, - itemAuthor: author + itemAuthor: author, + itemEdition: edition })); } @@ -319,7 +320,8 @@ $(this).closest('.grid-item').find('.creator').find('.value').text(), $(this).closest('.grid-item').find('.item-cost').text(), $(this).attr('data-href'), - "mainPage"); + "mainPage", + -1); }); } @@ -411,6 +413,7 @@ var cost = $('.item-cost').text(); var isUpdating = window.location.href.indexOf('edition=') > -1; + var urlParams = new URLSearchParams(window.location.search); if (isUpdating) { purchaseButton.html('UPDATE FOR FREE'); } else if (availability !== 'available') { @@ -427,7 +430,8 @@ $('#creator').find('.value').text(), cost, href, - "itemPage"); + "itemPage", + urlParams.get('edition')); } }); maybeAddPurchasesButton(); From 882e4d6b454bcde33ee23c5ccfd0b3dfb1c46eb6 Mon Sep 17 00:00:00 2001 From: Zach Fox Date: Tue, 13 Mar 2018 15:13:41 -0700 Subject: [PATCH 20/32] Parse the string that comes from the backend --- interface/resources/qml/hifi/commerce/checkout/Checkout.qml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/interface/resources/qml/hifi/commerce/checkout/Checkout.qml b/interface/resources/qml/hifi/commerce/checkout/Checkout.qml index 15a11cf0d7..463ba73071 100644 --- a/interface/resources/qml/hifi/commerce/checkout/Checkout.qml +++ b/interface/resources/qml/hifi/commerce/checkout/Checkout.qml @@ -144,7 +144,7 @@ Rectangle { // OR the ID of an "updated" item, we're updating. if (root.itemId === result.data.updates[i].item_id || root.itemId === result.data.updates[i].updated_item_id) { - if (root.itemEdition !== -1 && root.itemEdition !== result.data.updates[i].edition_number) { + if (root.itemEdition !== -1 && root.itemEdition !== parseInt(result.data.updates[i].edition_number)) { continue; } root.isUpdating = true; From e2326a37337f075c09654c522680da33891d7308 Mon Sep 17 00:00:00 2001 From: Zach Fox Date: Tue, 13 Mar 2018 17:05:16 -0700 Subject: [PATCH 21/32] Initial work for 'messages waiting' light --- .../icons/tablet-icons/market-msg-a.svg | 23 ++++++++++++++++++ .../icons/tablet-icons/market-msg-i.svg | 23 ++++++++++++++++++ .../common/EmulatedMarketplaceHeader.qml | 21 ++++++++++++++++ .../qml/hifi/commerce/purchases/Purchases.qml | 11 +++++++++ .../qml/hifi/commerce/wallet/WalletHome.qml | 9 +++++++ scripts/system/html/js/marketplacesInject.js | 8 ++++++- scripts/system/marketplaces/marketplaces.js | 24 ++++++++++++++++--- 7 files changed, 115 insertions(+), 4 deletions(-) create mode 100644 interface/resources/icons/tablet-icons/market-msg-a.svg create mode 100644 interface/resources/icons/tablet-icons/market-msg-i.svg diff --git a/interface/resources/icons/tablet-icons/market-msg-a.svg b/interface/resources/icons/tablet-icons/market-msg-a.svg new file mode 100644 index 0000000000..bf9aa9335f --- /dev/null +++ b/interface/resources/icons/tablet-icons/market-msg-a.svg @@ -0,0 +1,23 @@ + + + + + + + + + + + + + + diff --git a/interface/resources/icons/tablet-icons/market-msg-i.svg b/interface/resources/icons/tablet-icons/market-msg-i.svg new file mode 100644 index 0000000000..bf9aa9335f --- /dev/null +++ b/interface/resources/icons/tablet-icons/market-msg-i.svg @@ -0,0 +1,23 @@ + + + + + + + + + + + + + + diff --git a/interface/resources/qml/hifi/commerce/common/EmulatedMarketplaceHeader.qml b/interface/resources/qml/hifi/commerce/common/EmulatedMarketplaceHeader.qml index 8a7e809b3d..f6fb28b08a 100644 --- a/interface/resources/qml/hifi/commerce/common/EmulatedMarketplaceHeader.qml +++ b/interface/resources/qml/hifi/commerce/common/EmulatedMarketplaceHeader.qml @@ -28,6 +28,7 @@ Item { property string referrerURL: (Account.metaverseServerURL + "/marketplace?"); readonly property int additionalDropdownHeight: usernameDropdown.height - myUsernameButton.anchors.bottomMargin; property alias usernameDropdownVisible: usernameDropdown.visible; + property bool messagesWaiting: false; height: mainContainer.height + additionalDropdownHeight; @@ -38,6 +39,7 @@ Item { if (walletStatus === 0) { sendToParent({method: "needsLogIn"}); } else if (walletStatus === 5) { + Commerce.getAvailableUpdates(); Commerce.getSecurityImage(); } else if (walletStatus > 5) { console.log("ERROR in EmulatedMarketplaceHeader.qml: Unknown wallet status: " + walletStatus); @@ -58,6 +60,14 @@ Item { securityImage.source = "image://security/securityImage"; } } + + onAvailableUpdatesResult: { + if (result.status !== 'success') { + console.log("Failed to get Available Updates", result.data.message); + } else { + root.messagesWaiting = result.data.updates.length > 0; + } + } } Component.onCompleted: { @@ -109,6 +119,17 @@ Item { anchors.right: securityImage.left; anchors.rightMargin: 6; + Rectangle { + id: messagesWaitingLight; + visible: root.messagesWaiting; + anchors.right: myPurchasesLink.left; + anchors.rightMargin: 4; + anchors.verticalCenter: parent.verticalCenter; + height: 10; + width: height; + radius: height/2; + } + Rectangle { id: myPurchasesLink; anchors.right: myUsernameButton.left; diff --git a/interface/resources/qml/hifi/commerce/purchases/Purchases.qml b/interface/resources/qml/hifi/commerce/purchases/Purchases.qml index 8089818ba6..31c0ba73c0 100644 --- a/interface/resources/qml/hifi/commerce/purchases/Purchases.qml +++ b/interface/resources/qml/hifi/commerce/purchases/Purchases.qml @@ -64,6 +64,7 @@ Rectangle { root.activeView = "purchasesMain"; root.installedApps = Commerce.getInstalledApps(); Commerce.inventory(); + Commerce.getAvailableUpdates(); } } else { console.log("ERROR in Purchases.qml: Unknown wallet status: " + walletStatus); @@ -119,6 +120,14 @@ Rectangle { root.pendingInventoryReply = false; } + + onAvailableUpdatesResult: { + if (result.status !== 'success') { + console.log("Failed to get Available Updates", result.data.message); + } else { + sendToScript({method: 'purchases_availableUpdatesReceived', numUpdates: result.data.updates.length }); + } + } } Timer { @@ -273,6 +282,7 @@ Rectangle { root.activeView = "purchasesMain"; root.installedApps = Commerce.getInstalledApps(); Commerce.inventory(); + Commerce.getAvailableUpdates(); break; } } @@ -617,6 +627,7 @@ Rectangle { console.log("Refreshing Purchases..."); root.pendingInventoryReply = true; Commerce.inventory(); + Commerce.getAvailableUpdates(); } } } diff --git a/interface/resources/qml/hifi/commerce/wallet/WalletHome.qml b/interface/resources/qml/hifi/commerce/wallet/WalletHome.qml index 27660b5e9e..7a14ee060f 100644 --- a/interface/resources/qml/hifi/commerce/wallet/WalletHome.qml +++ b/interface/resources/qml/hifi/commerce/wallet/WalletHome.qml @@ -39,6 +39,7 @@ Item { root.noMoreHistoryData = false; root.historyRequestPending = true; Commerce.history(root.currentHistoryPage); + Commerce.getAvailableUpdates(); } else { refreshTimer.stop(); } @@ -133,6 +134,14 @@ Item { refreshTimer.start(); } } + + onAvailableUpdatesResult: { + if (result.status !== 'success') { + console.log("Failed to get Available Updates", result.data.message); + } else { + sendToScript({method: 'wallet_availableUpdatesReceived', numUpdates: result.data.updates.length }); + } + } } Connections { diff --git a/scripts/system/html/js/marketplacesInject.js b/scripts/system/html/js/marketplacesInject.js index 2565a998f1..9376ed9823 100644 --- a/scripts/system/html/js/marketplacesInject.js +++ b/scripts/system/html/js/marketplacesInject.js @@ -30,6 +30,7 @@ var userIsLoggedIn = false; var walletNeedsSetup = false; var marketplaceBaseURL = "https://highfidelity.com"; + var messagesWaiting = false; function injectCommonCode(isDirectoryPage) { @@ -205,7 +206,11 @@ purchasesElement.id = "purchasesButton"; purchasesElement.setAttribute('href', "#"); - purchasesElement.innerHTML = "My Purchases"; + purchasesElement.innerHTML = ""; + if (messagesWaiting) { + purchasesElement.innerHTML += " "; + } + purchasesElement.innerHTML += "My Purchases"; // FRONTEND WEBDEV RANT: The username dropdown should REALLY not be programmed to be on the same // line as the search bar, overlaid on top of the search bar, floated right, and then relatively bumped up using "top:-50px". purchasesElement.style = "height:100%;margin-top:18px;font-weight:bold;float:right;margin-right:" + (dropDownElement.offsetWidth + 30) + @@ -705,6 +710,7 @@ if (marketplaceBaseURL.indexOf('metaverse.') !== -1) { marketplaceBaseURL = marketplaceBaseURL.replace('metaverse.', ''); } + messagesWaiting = parsedJsonMessage.data.messagesWaiting; injectCode(); } } diff --git a/scripts/system/marketplaces/marketplaces.js b/scripts/system/marketplaces/marketplaces.js index d3620968c7..3d878ef948 100644 --- a/scripts/system/marketplaces/marketplaces.js +++ b/scripts/system/marketplaces/marketplaces.js @@ -86,13 +86,24 @@ var selectionDisplay = null; // for gridTool.js to ignore } var tablet = Tablet.getTablet("com.highfidelity.interface.tablet.system"); + var NORMAL_ICON = "icons/tablet-icons/market-i.svg"; + var NORMAL_ACTIVE = "icons/tablet-icons/market-a.svg"; + var WAITING_ICON = "icons/tablet-icons/market-msg-i.svg"; + var WAITING_ACTIVE = "icons/tablet-icons/market-msg-a.svg"; var marketplaceButton = tablet.addButton({ - icon: "icons/tablet-icons/market-i.svg", - activeIcon: "icons/tablet-icons/market-a.svg", + icon: NORMAL_ICON, + activeIcon: NORMAL_ACTIVE, text: "MARKET", sortOrder: 9 }); + function messagesWaiting(isWaiting) { + button.editProperties({ + icon: (isWaiting ? WAITING_ICON : NORMAL_ICON), + activeIcon: (isWaiting ? WAITING_ACTIVE : NORMAL_ACTIVE) + }); + } + function onCanWriteAssetsChanged() { var message = CAN_WRITE_ASSETS + " " + Entities.canWriteAssets(); tablet.emitScriptEvent(message); @@ -178,6 +189,7 @@ var selectionDisplay = null; // for gridTool.js to ignore } } + var userHasUpdates = false; function sendCommerceSettings() { tablet.emitScriptEvent(JSON.stringify({ type: "marketplaces", @@ -186,7 +198,8 @@ var selectionDisplay = null; // for gridTool.js to ignore commerceMode: Settings.getValue("commerce", true), userIsLoggedIn: Account.loggedIn, walletNeedsSetup: Wallet.walletStatus === 1, - metaverseServerURL: Account.metaverseServerURL + metaverseServerURL: Account.metaverseServerURL, + messagesWaiting: userHasUpdates } })); } @@ -619,6 +632,11 @@ var selectionDisplay = null; // for gridTool.js to ignore case 'sendMoney_sendPublicly': // NOP break; + case 'wallet_availableUpdatesReceived': + case 'purchases_availableUpdatesReceived': + userHasUpdates = message.numUpdates > 0; + messagesWaiting(userHasUpdates); + break; default: print('Unrecognized message from Checkout.qml or Purchases.qml: ' + JSON.stringify(message)); } From 98cdbbb3e62a935d3c84979bce4b33cae98aae53 Mon Sep 17 00:00:00 2001 From: Zach Fox Date: Wed, 14 Mar 2018 09:45:42 -0700 Subject: [PATCH 22/32] Small update to dot before merging in filterBar --- .../resources/qml/hifi/commerce/purchases/Purchases.qml | 3 +++ scripts/system/html/js/marketplacesInject.js | 4 +++- scripts/system/marketplaces/marketplaces.js | 6 ++++++ 3 files changed, 12 insertions(+), 1 deletion(-) diff --git a/interface/resources/qml/hifi/commerce/purchases/Purchases.qml b/interface/resources/qml/hifi/commerce/purchases/Purchases.qml index 31c0ba73c0..43669ed038 100644 --- a/interface/resources/qml/hifi/commerce/purchases/Purchases.qml +++ b/interface/resources/qml/hifi/commerce/purchases/Purchases.qml @@ -768,6 +768,9 @@ Rectangle { case 'purchases_showMyItems': root.isShowingMyItems = true; break; + case 'showUpdates': + //primaryFilter; + break; default: console.log('Unrecognized message from marketplaces.js:', JSON.stringify(message)); } diff --git a/scripts/system/html/js/marketplacesInject.js b/scripts/system/html/js/marketplacesInject.js index 9376ed9823..864c7d92b4 100644 --- a/scripts/system/html/js/marketplacesInject.js +++ b/scripts/system/html/js/marketplacesInject.js @@ -213,13 +213,15 @@ purchasesElement.innerHTML += "My Purchases"; // FRONTEND WEBDEV RANT: The username dropdown should REALLY not be programmed to be on the same // line as the search bar, overlaid on top of the search bar, floated right, and then relatively bumped up using "top:-50px". + $('.navbar-brand').css('margin-right', '10px'); purchasesElement.style = "height:100%;margin-top:18px;font-weight:bold;float:right;margin-right:" + (dropDownElement.offsetWidth + 30) + "px;position:relative;z-index:999;"; navbarBrandElement.parentNode.insertAdjacentElement('beforeend', purchasesElement); $('#purchasesButton').on('click', function () { EventBridge.emitWebEvent(JSON.stringify({ type: "PURCHASES", - referrerURL: window.location.href + referrerURL: window.location.href, + hasUpdates: messagesWaiting })); }); } diff --git a/scripts/system/marketplaces/marketplaces.js b/scripts/system/marketplaces/marketplaces.js index 3d878ef948..356707a7e5 100644 --- a/scripts/system/marketplaces/marketplaces.js +++ b/scripts/system/marketplaces/marketplaces.js @@ -445,6 +445,12 @@ var selectionDisplay = null; // for gridTool.js to ignore } else if (parsedJsonMessage.type === "PURCHASES") { referrerURL = parsedJsonMessage.referrerURL; filterText = ""; + if (parsedJsonMessage.hasUpdates) { + wireEventBridge(true); + tablet.sendToQml({ + method: 'showUpdates' + }); + } tablet.pushOntoStack(MARKETPLACE_PURCHASES_QML_PATH); } else if (parsedJsonMessage.type === "LOGIN") { openLoginWindow(); From 74e24ec2892c1e69aa56740997b230364fa412a5 Mon Sep 17 00:00:00 2001 From: Zach Fox Date: Wed, 14 Mar 2018 10:58:18 -0700 Subject: [PATCH 23/32] Final touches? --- .../icons/tablet-icons/market-a-msg.svg | 19 +++++ .../resources/icons/tablet-icons/market-a.svg | 79 ++++--------------- .../icons/tablet-icons/market-i-msg.svg | 21 +++++ .../resources/icons/tablet-icons/market-i.svg | 28 +++---- .../icons/tablet-icons/market-msg-a.svg | 23 ------ .../icons/tablet-icons/market-msg-i.svg | 23 ------ .../resources/qml/controls-uit/FilterBar.qml | 18 ++++- .../common/EmulatedMarketplaceHeader.qml | 4 +- .../qml/hifi/commerce/purchases/Purchases.qml | 18 +++-- scripts/system/commerce/wallet.js | 3 + scripts/system/marketplaces/marketplaces.js | 13 ++- 11 files changed, 109 insertions(+), 140 deletions(-) create mode 100644 interface/resources/icons/tablet-icons/market-a-msg.svg create mode 100644 interface/resources/icons/tablet-icons/market-i-msg.svg delete mode 100644 interface/resources/icons/tablet-icons/market-msg-a.svg delete mode 100644 interface/resources/icons/tablet-icons/market-msg-i.svg diff --git a/interface/resources/icons/tablet-icons/market-a-msg.svg b/interface/resources/icons/tablet-icons/market-a-msg.svg new file mode 100644 index 0000000000..0ab93f3cc8 --- /dev/null +++ b/interface/resources/icons/tablet-icons/market-a-msg.svg @@ -0,0 +1,19 @@ + + + + + + + + + + + diff --git a/interface/resources/icons/tablet-icons/market-a.svg b/interface/resources/icons/tablet-icons/market-a.svg index f8ba17301e..db2d948d7b 100644 --- a/interface/resources/icons/tablet-icons/market-a.svg +++ b/interface/resources/icons/tablet-icons/market-a.svg @@ -1,64 +1,15 @@ - - - -image/svg+xml \ No newline at end of file + + + + + + + + + diff --git a/interface/resources/icons/tablet-icons/market-i-msg.svg b/interface/resources/icons/tablet-icons/market-i-msg.svg new file mode 100644 index 0000000000..488c507c6e --- /dev/null +++ b/interface/resources/icons/tablet-icons/market-i-msg.svg @@ -0,0 +1,21 @@ + + + + + + + + + + + diff --git a/interface/resources/icons/tablet-icons/market-i.svg b/interface/resources/icons/tablet-icons/market-i.svg index bf9aa9335f..7d11507cdb 100644 --- a/interface/resources/icons/tablet-icons/market-i.svg +++ b/interface/resources/icons/tablet-icons/market-i.svg @@ -1,23 +1,19 @@ - - + - - - - - - - - + + + + diff --git a/interface/resources/icons/tablet-icons/market-msg-a.svg b/interface/resources/icons/tablet-icons/market-msg-a.svg deleted file mode 100644 index bf9aa9335f..0000000000 --- a/interface/resources/icons/tablet-icons/market-msg-a.svg +++ /dev/null @@ -1,23 +0,0 @@ - - - - - - - - - - - - - - diff --git a/interface/resources/icons/tablet-icons/market-msg-i.svg b/interface/resources/icons/tablet-icons/market-msg-i.svg deleted file mode 100644 index bf9aa9335f..0000000000 --- a/interface/resources/icons/tablet-icons/market-msg-i.svg +++ /dev/null @@ -1,23 +0,0 @@ - - - - - - - - - - - - - - diff --git a/interface/resources/qml/controls-uit/FilterBar.qml b/interface/resources/qml/controls-uit/FilterBar.qml index 333ccc0e59..ecae790b22 100644 --- a/interface/resources/qml/controls-uit/FilterBar.qml +++ b/interface/resources/qml/controls-uit/FilterBar.qml @@ -156,7 +156,7 @@ Item { HiFiGlyphs { id: searchIcon; text: hifi.glyphs.search - color: textColor + color: textField.color size: 40; anchors.left: parent.left anchors.verticalCenter: parent.verticalCenter @@ -166,7 +166,7 @@ Item { // Carat HiFiGlyphs { text: hifi.glyphs.caratDn; - color: textColor; + color: textField.color; size: 40; anchors.left: parent.left; anchors.leftMargin: 15; @@ -216,7 +216,7 @@ Item { // "Clear" button HiFiGlyphs { text: hifi.glyphs.error - color: textColor + color: textField.color size: 40 anchors.right: parent.right anchors.rightMargin: hifi.dimensions.textPadding - 2 @@ -235,7 +235,6 @@ Item { } } - //placeholderTextColor: isFaintGrayColorScheme ? hifi.colors.lightGrayText : hifi.colors.lightGray selectedTextColor: hifi.colors.black selectionColor: hifi.colors.primaryHighlight leftPadding: 44 + (root.primaryFilter_index === -1 ? 0 : primaryFilterTextMetrics.tightBoundingRect.width + 20); @@ -308,4 +307,15 @@ Item { source: dropdownContainer; visible: dropdownContainer.visible; } + + function changeFilterByDisplayName(name) { + for (var i = 0; i < filterBarModel.count; i++) { + if (filterBarModel.get(i).displayName === name) { + root.primaryFilter_index = i; + return; + } + } + + console.log("Passed displayName not found in filterBarModel! primaryFilter unchanged."); + } } diff --git a/interface/resources/qml/hifi/commerce/common/EmulatedMarketplaceHeader.qml b/interface/resources/qml/hifi/commerce/common/EmulatedMarketplaceHeader.qml index f6fb28b08a..54698c1b86 100644 --- a/interface/resources/qml/hifi/commerce/common/EmulatedMarketplaceHeader.qml +++ b/interface/resources/qml/hifi/commerce/common/EmulatedMarketplaceHeader.qml @@ -123,11 +123,11 @@ Item { id: messagesWaitingLight; visible: root.messagesWaiting; anchors.right: myPurchasesLink.left; - anchors.rightMargin: 4; anchors.verticalCenter: parent.verticalCenter; height: 10; width: height; radius: height/2; + color: "red"; } Rectangle { @@ -155,7 +155,7 @@ Item { anchors.fill: parent; hoverEnabled: enabled; onClicked: { - sendToParent({method: 'header_goToPurchases'}); + sendToParent({ method: 'header_goToPurchases', hasUpdates: root.messagesWaiting }); } onEntered: myPurchasesText.color = hifi.colors.blueHighlight; onExited: myPurchasesText.color = hifi.colors.blueAccent; diff --git a/interface/resources/qml/hifi/commerce/purchases/Purchases.qml b/interface/resources/qml/hifi/commerce/purchases/Purchases.qml index f67ebead44..463cc3873b 100644 --- a/interface/resources/qml/hifi/commerce/purchases/Purchases.qml +++ b/interface/resources/qml/hifi/commerce/purchases/Purchases.qml @@ -529,7 +529,11 @@ Rectangle { Item { id: noItemsAlertContainer; - visible: !purchasesContentsList.visible && root.purchasesReceived && root.isShowingMyItems && filterBar.text === ""; + visible: !purchasesContentsList.visible && + root.purchasesReceived && + root.isShowingMyItems && + filterBar.text === "" && + filterBar.primaryFilter_displayName === ""; anchors.top: filterBarContainer.bottom; anchors.topMargin: 12; anchors.left: parent.left; @@ -575,7 +579,11 @@ Rectangle { Item { id: noPurchasesAlertContainer; - visible: !purchasesContentsList.visible && root.purchasesReceived && !root.isShowingMyItems && filterBar.text === ""; + visible: !purchasesContentsList.visible && + root.purchasesReceived && + !root.isShowingMyItems && + filterBar.text === "" && + filterBar.primaryFilter_displayName === ""; anchors.top: filterBarContainer.bottom; anchors.topMargin: 12; anchors.left: parent.left; @@ -734,8 +742,8 @@ Rectangle { } if (filterBar.primaryFilter_displayName !== "" && - (filterBar.primaryFilter_displayName.toLowerCase() !== currentItemType.toLowerCase())) { //|| UNCOMMENT WHEN UPGRADES ARE IN - //(filterBar.primaryFilter_displayName === "Updatable" && tempPurchasesModel.get(i).upgradeUrl !== "")) { + ((filterBar.primaryFilter_displayName === "Updatable" && tempPurchasesModel.get(i).upgradeUrl === "") || + (filterBar.primaryFilter_displayName !== "Updatable" && filterBar.primaryFilter_displayName.toLowerCase() !== currentItemType.toLowerCase()))) { tempPurchasesModel.remove(i); i--; } else { @@ -824,7 +832,7 @@ Rectangle { root.isShowingMyItems = true; break; case 'showUpdates': - //primaryFilter; + filterBar.changeFilterByDisplayName("Updatable"); break; default: console.log('Unrecognized message from marketplaces.js:', JSON.stringify(message)); diff --git a/scripts/system/commerce/wallet.js b/scripts/system/commerce/wallet.js index 26ffb08796..d2e7d3ffc8 100644 --- a/scripts/system/commerce/wallet.js +++ b/scripts/system/commerce/wallet.js @@ -698,6 +698,9 @@ Window.location = "hifi://BankOfHighFidelity"; } break; + case 'wallet_availableUpdatesReceived': + // NOP + break; default: print('Unrecognized message from QML:', JSON.stringify(message)); } diff --git a/scripts/system/marketplaces/marketplaces.js b/scripts/system/marketplaces/marketplaces.js index 356707a7e5..4e235b00bf 100644 --- a/scripts/system/marketplaces/marketplaces.js +++ b/scripts/system/marketplaces/marketplaces.js @@ -88,8 +88,8 @@ var selectionDisplay = null; // for gridTool.js to ignore var tablet = Tablet.getTablet("com.highfidelity.interface.tablet.system"); var NORMAL_ICON = "icons/tablet-icons/market-i.svg"; var NORMAL_ACTIVE = "icons/tablet-icons/market-a.svg"; - var WAITING_ICON = "icons/tablet-icons/market-msg-i.svg"; - var WAITING_ACTIVE = "icons/tablet-icons/market-msg-a.svg"; + var WAITING_ICON = "icons/tablet-icons/market-i-msg.svg"; + var WAITING_ACTIVE = "icons/tablet-icons/market-a-msg.svg"; var marketplaceButton = tablet.addButton({ icon: NORMAL_ICON, activeIcon: NORMAL_ACTIVE, @@ -98,7 +98,7 @@ var selectionDisplay = null; // for gridTool.js to ignore }); function messagesWaiting(isWaiting) { - button.editProperties({ + marketplaceButton.editProperties({ icon: (isWaiting ? WAITING_ICON : NORMAL_ICON), activeIcon: (isWaiting ? WAITING_ACTIVE : NORMAL_ACTIVE) }); @@ -553,6 +553,13 @@ var selectionDisplay = null; // for gridTool.js to ignore //tablet.popFromStack(); break; case 'header_goToPurchases': + if (message.hasUpdates) { + wireEventBridge(true); + tablet.sendToQml({ + method: 'showUpdates' + }); + } + // Fall through. case 'checkout_goToPurchases': referrerURL = MARKETPLACE_URL_INITIAL; filterText = message.filterText; From 6647475f5cf1253687c77be2000af00d4a2e561c Mon Sep 17 00:00:00 2001 From: Zach Fox Date: Wed, 14 Mar 2018 11:19:55 -0700 Subject: [PATCH 24/32] Wrong sign --- interface/resources/qml/hifi/commerce/purchases/Purchases.qml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/interface/resources/qml/hifi/commerce/purchases/Purchases.qml b/interface/resources/qml/hifi/commerce/purchases/Purchases.qml index 463cc3873b..13ad81f5cb 100644 --- a/interface/resources/qml/hifi/commerce/purchases/Purchases.qml +++ b/interface/resources/qml/hifi/commerce/purchases/Purchases.qml @@ -742,7 +742,7 @@ Rectangle { } if (filterBar.primaryFilter_displayName !== "" && - ((filterBar.primaryFilter_displayName === "Updatable" && tempPurchasesModel.get(i).upgradeUrl === "") || + ((filterBar.primaryFilter_displayName === "Updatable" && tempPurchasesModel.get(i).upgradeUrl !== "") || (filterBar.primaryFilter_displayName !== "Updatable" && filterBar.primaryFilter_displayName.toLowerCase() !== currentItemType.toLowerCase()))) { tempPurchasesModel.remove(i); i--; From f3efdda6b020b916fcab90c25408e3977e3c2343 Mon Sep 17 00:00:00 2001 From: Zach Fox Date: Wed, 14 Mar 2018 14:34:15 -0700 Subject: [PATCH 25/32] Fix content set filter --- interface/resources/qml/hifi/commerce/purchases/Purchases.qml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/interface/resources/qml/hifi/commerce/purchases/Purchases.qml b/interface/resources/qml/hifi/commerce/purchases/Purchases.qml index 13ad81f5cb..88339e172b 100644 --- a/interface/resources/qml/hifi/commerce/purchases/Purchases.qml +++ b/interface/resources/qml/hifi/commerce/purchases/Purchases.qml @@ -743,7 +743,7 @@ Rectangle { if (filterBar.primaryFilter_displayName !== "" && ((filterBar.primaryFilter_displayName === "Updatable" && tempPurchasesModel.get(i).upgradeUrl !== "") || - (filterBar.primaryFilter_displayName !== "Updatable" && filterBar.primaryFilter_displayName.toLowerCase() !== currentItemType.toLowerCase()))) { + (filterBar.primaryFilter_displayName !== "Updatable" && filterBar.primaryFilter_filterName.toLowerCase() !== currentItemType.toLowerCase()))) { tempPurchasesModel.remove(i); i--; } else { From cc4726b24b89f7c53311b4e3ba9bc619b6aabff1 Mon Sep 17 00:00:00 2001 From: Zach Fox Date: Wed, 14 Mar 2018 15:47:19 -0700 Subject: [PATCH 26/32] Some bugfixes --- interface/resources/qml/hifi/commerce/checkout/Checkout.qml | 4 ++-- .../resources/qml/hifi/commerce/purchases/PurchasedItem.qml | 4 ++-- interface/resources/qml/hifi/commerce/purchases/Purchases.qml | 3 +-- 3 files changed, 5 insertions(+), 6 deletions(-) diff --git a/interface/resources/qml/hifi/commerce/checkout/Checkout.qml b/interface/resources/qml/hifi/commerce/checkout/Checkout.qml index 463ba73071..d64e31ddc9 100644 --- a/interface/resources/qml/hifi/commerce/checkout/Checkout.qml +++ b/interface/resources/qml/hifi/commerce/checkout/Checkout.qml @@ -588,7 +588,7 @@ Rectangle { HifiControlsUit.Button { id: buyButton; visible: !((root.itemType === "avatar" || root.itemType === "app") && viewInMyPurchasesButton.visible) - enabled: (root.balanceAfterPurchase >= 0 && ownershipStatusReceived && balanceReceived && availableUpdatesReceived) || (!root.isCertified); + enabled: (root.balanceAfterPurchase >= 0 && ownershipStatusReceived && balanceReceived && availableUpdatesReceived) || (!root.isCertified) || root.isUpdating; color: viewInMyPurchasesButton.visible ? hifi.buttons.white : hifi.buttons.blue; colorScheme: hifi.colorSchemes.light; anchors.top: viewInMyPurchasesButton.visible ? viewInMyPurchasesButton.bottom : @@ -1063,7 +1063,7 @@ Rectangle { root.itemHref = message.params.itemHref; root.referrer = message.params.referrer; root.itemAuthor = message.params.itemAuthor; - root.itemEdition = message.params.itemEdition; + root.itemEdition = message.params.itemEdition || -1; refreshBuyUI(); break; default: diff --git a/interface/resources/qml/hifi/commerce/purchases/PurchasedItem.qml b/interface/resources/qml/hifi/commerce/purchases/PurchasedItem.qml index 38b0e16846..fc2e3fa24c 100644 --- a/interface/resources/qml/hifi/commerce/purchases/PurchasedItem.qml +++ b/interface/resources/qml/hifi/commerce/purchases/PurchasedItem.qml @@ -368,7 +368,7 @@ Item { Item { id: statusContainer; - visible: root.purchaseStatus === "pending" || root.purchaseStatus === "invalidated" || root.purchaseStatusChanged; + visible: root.purchaseStatus === "pending" || root.purchaseStatus === "invalidated" || root.purchaseStatusChanged || root.numberSold > -1; anchors.left: itemName.left; anchors.top: certificateContainer.bottom; anchors.topMargin: 8; @@ -387,7 +387,7 @@ Item { "PENDING..." } else if (root.purchaseStatus === "invalidated") { "INVALIDATED" - } else if (root.numberSold !== -1) { + } else if (root.numberSold > -1) { ("Sales: " + root.numberSold + "/" + (root.limitedRun === -1 ? "\u221e" : root.limitedRun)) } else { "" diff --git a/interface/resources/qml/hifi/commerce/purchases/Purchases.qml b/interface/resources/qml/hifi/commerce/purchases/Purchases.qml index 88339e172b..3b03b41da9 100644 --- a/interface/resources/qml/hifi/commerce/purchases/Purchases.qml +++ b/interface/resources/qml/hifi/commerce/purchases/Purchases.qml @@ -740,9 +740,8 @@ Rectangle { } else { currentItemType = "unknown"; } - if (filterBar.primaryFilter_displayName !== "" && - ((filterBar.primaryFilter_displayName === "Updatable" && tempPurchasesModel.get(i).upgradeUrl !== "") || + ((filterBar.primaryFilter_displayName === "Updatable" && tempPurchasesModel.get(i).upgrade_url === "") || (filterBar.primaryFilter_displayName !== "Updatable" && filterBar.primaryFilter_filterName.toLowerCase() !== currentItemType.toLowerCase()))) { tempPurchasesModel.remove(i); i--; From 2d57c444b6d7dfa1e2b35ac948cb1a85ddef806e Mon Sep 17 00:00:00 2001 From: Zach Fox Date: Wed, 14 Mar 2018 16:02:33 -0700 Subject: [PATCH 27/32] Don't auto apply 'updatable' filter --- interface/resources/qml/hifi/commerce/purchases/Purchases.qml | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/interface/resources/qml/hifi/commerce/purchases/Purchases.qml b/interface/resources/qml/hifi/commerce/purchases/Purchases.qml index 3b03b41da9..6ba0a4ac41 100644 --- a/interface/resources/qml/hifi/commerce/purchases/Purchases.qml +++ b/interface/resources/qml/hifi/commerce/purchases/Purchases.qml @@ -831,7 +831,8 @@ Rectangle { root.isShowingMyItems = true; break; case 'showUpdates': - filterBar.changeFilterByDisplayName("Updatable"); + // Uncomment and/or change this once we figure out what the correct behavior is. + //filterBar.changeFilterByDisplayName("Updatable"); break; default: console.log('Unrecognized message from marketplaces.js:', JSON.stringify(message)); From eb2f8aa169226c97c6b0e2260896c3e9ffa9376e Mon Sep 17 00:00:00 2001 From: Zach Fox Date: Thu, 15 Mar 2018 16:03:49 -0700 Subject: [PATCH 28/32] New notification flow for item updates --- .../hifi/commerce/purchases/PurchasedItem.qml | 2 +- .../qml/hifi/commerce/purchases/Purchases.qml | 64 +++++++++++++++++-- scripts/system/marketplaces/marketplaces.js | 13 ---- 3 files changed, 61 insertions(+), 18 deletions(-) diff --git a/interface/resources/qml/hifi/commerce/purchases/PurchasedItem.qml b/interface/resources/qml/hifi/commerce/purchases/PurchasedItem.qml index fc2e3fa24c..4cfa61c9ed 100644 --- a/interface/resources/qml/hifi/commerce/purchases/PurchasedItem.qml +++ b/interface/resources/qml/hifi/commerce/purchases/PurchasedItem.qml @@ -655,7 +655,7 @@ Item { anchors.bottom: parent.bottom; anchors.left: parent.left; anchors.right: parent.right; - color: "#E7F8FB"; + color: "#B5EAFF"; RalewayRegular { id: updateAvailableText; diff --git a/interface/resources/qml/hifi/commerce/purchases/Purchases.qml b/interface/resources/qml/hifi/commerce/purchases/Purchases.qml index 6ba0a4ac41..7bb7461596 100644 --- a/interface/resources/qml/hifi/commerce/purchases/Purchases.qml +++ b/interface/resources/qml/hifi/commerce/purchases/Purchases.qml @@ -38,6 +38,7 @@ Rectangle { property int pendingItemCount: 0; property string installedApps; property bool keyboardRaised: false; + property int numUpdatesAvailable: 0; // Style color: hifi.colors.white; Connections { @@ -127,6 +128,7 @@ Rectangle { console.log("Failed to get Available Updates", result.data.message); } else { sendToScript({method: 'purchases_availableUpdatesReceived', numUpdates: result.data.updates.length }); + root.numUpdatesAvailable = result.data.updates.length; } } } @@ -527,6 +529,64 @@ Rectangle { } } + Rectangle { + id: updatesAvailableBanner; + visible: true; + anchors.bottom: parent.bottom; + anchors.left: parent.left; + anchors.right: parent.right; + height: 75; + color: "#B5EAFF"; + + Rectangle { + id: updatesAvailableGlyph; + anchors.verticalCenter: parent.verticalCenter; + anchors.left: parent.left; + anchors.leftMargin: 16; + // Size + width: 10; + height: width; + radius: width/2; + // Style + color: "red"; + } + + RalewaySemiBold { + text: "You have " + root.numUpdatesAvailable + " item updates available."; + // Text size + size: 18; + // Anchors + anchors.left: updatesAvailableGlyph.right; + anchors.leftMargin: 12; + height: parent.height; + width: paintedWidth; + // Style + color: hifi.colors.black; + // Alignment + verticalAlignment: Text.AlignVCenter; + } + + MouseArea { + anchors.fill: parent; + hoverEnabled: true; + propagateComposedEvents: false; + } + + HifiControlsUit.Button { + color: hifi.buttons.white; + colorScheme: hifi.colorSchemes.dark; + anchors.verticalCenter: parent.verticalCenter; + anchors.right: parent.right; + anchors.rightMargin: 12; + width: 100; + height: 40; + text: "SHOW ME"; + onClicked: { + filterBar.changeFilterByDisplayName("Updatable"); + } + } + } + Item { id: noItemsAlertContainer; visible: !purchasesContentsList.visible && @@ -830,10 +890,6 @@ Rectangle { case 'purchases_showMyItems': root.isShowingMyItems = true; break; - case 'showUpdates': - // Uncomment and/or change this once we figure out what the correct behavior is. - //filterBar.changeFilterByDisplayName("Updatable"); - break; default: console.log('Unrecognized message from marketplaces.js:', JSON.stringify(message)); } diff --git a/scripts/system/marketplaces/marketplaces.js b/scripts/system/marketplaces/marketplaces.js index 4e235b00bf..916892c0c2 100644 --- a/scripts/system/marketplaces/marketplaces.js +++ b/scripts/system/marketplaces/marketplaces.js @@ -445,12 +445,6 @@ var selectionDisplay = null; // for gridTool.js to ignore } else if (parsedJsonMessage.type === "PURCHASES") { referrerURL = parsedJsonMessage.referrerURL; filterText = ""; - if (parsedJsonMessage.hasUpdates) { - wireEventBridge(true); - tablet.sendToQml({ - method: 'showUpdates' - }); - } tablet.pushOntoStack(MARKETPLACE_PURCHASES_QML_PATH); } else if (parsedJsonMessage.type === "LOGIN") { openLoginWindow(); @@ -553,13 +547,6 @@ var selectionDisplay = null; // for gridTool.js to ignore //tablet.popFromStack(); break; case 'header_goToPurchases': - if (message.hasUpdates) { - wireEventBridge(true); - tablet.sendToQml({ - method: 'showUpdates' - }); - } - // Fall through. case 'checkout_goToPurchases': referrerURL = MARKETPLACE_URL_INITIAL; filterText = message.filterText; From ad33e775f13e475d2405a089f25f6e0cc6fa3d4a Mon Sep 17 00:00:00 2001 From: Zach Fox Date: Fri, 16 Mar 2018 11:28:03 -0700 Subject: [PATCH 29/32] Fix bottom anchor for listView when banner is showing --- interface/resources/qml/hifi/commerce/purchases/Purchases.qml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/interface/resources/qml/hifi/commerce/purchases/Purchases.qml b/interface/resources/qml/hifi/commerce/purchases/Purchases.qml index 7bb7461596..73dc1286b5 100644 --- a/interface/resources/qml/hifi/commerce/purchases/Purchases.qml +++ b/interface/resources/qml/hifi/commerce/purchases/Purchases.qml @@ -429,7 +429,7 @@ Rectangle { anchors.top: separator.bottom; anchors.topMargin: 12; anchors.left: parent.left; - anchors.bottom: parent.bottom; + anchors.bottom: updatesAvailableBanner.visible ? updatesAvailableBanner.top : parent.bottom; width: parent.width; delegate: PurchasedItem { itemName: title; From eb8cfcd8e98332e7bcdd7b547d0e207f4d8ab194 Mon Sep 17 00:00:00 2001 From: Zach Fox Date: Fri, 16 Mar 2018 13:13:38 -0700 Subject: [PATCH 30/32] Bugfix --- interface/resources/qml/hifi/commerce/purchases/Purchases.qml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/interface/resources/qml/hifi/commerce/purchases/Purchases.qml b/interface/resources/qml/hifi/commerce/purchases/Purchases.qml index 73dc1286b5..0789eae868 100644 --- a/interface/resources/qml/hifi/commerce/purchases/Purchases.qml +++ b/interface/resources/qml/hifi/commerce/purchases/Purchases.qml @@ -531,7 +531,7 @@ Rectangle { Rectangle { id: updatesAvailableBanner; - visible: true; + visible: root.numUpdatesAvailable > 0 && !root.isShowingMyItems; anchors.bottom: parent.bottom; anchors.left: parent.left; anchors.right: parent.right; From 18aa95e0bbb411a9bee6071d61cde24864354a02 Mon Sep 17 00:00:00 2001 From: Zach Fox Date: Mon, 19 Mar 2018 13:35:27 -0700 Subject: [PATCH 31/32] New Checkout flow for updating specific Editions --- .../qml/hifi/commerce/checkout/Checkout.qml | 99 +++++++++++++------ 1 file changed, 69 insertions(+), 30 deletions(-) diff --git a/interface/resources/qml/hifi/commerce/checkout/Checkout.qml b/interface/resources/qml/hifi/commerce/checkout/Checkout.qml index d64e31ddc9..ea73861c16 100644 --- a/interface/resources/qml/hifi/commerce/checkout/Checkout.qml +++ b/interface/resources/qml/hifi/commerce/checkout/Checkout.qml @@ -42,11 +42,11 @@ Rectangle { property int itemPrice: -1; 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 var itemTypesArray: ["entity", "wearable", "contentSet", "app", "avatar", "unknown"]; + property var itemTypesText: ["entity", "wearable", "content set", "app", "avatar", "item"]; + property var buttonTextNormal: ["REZ", "WEAR", "REPLACE CONTENT SET", "INSTALL", "WEAR", "REZ"]; + property var buttonTextClicked: ["REZZED!", "WORN!", "CONTENT SET REPLACED!", "INSTALLED!", "AVATAR CHANGED!", "REZZED!"] + property var buttonGlyph: [hifi.glyphs.wand, hifi.glyphs.hat, hifi.glyphs.globe, hifi.glyphs.install, hifi.glyphs.avatar, hifi.glyphs.wand]; property bool shouldBuyWithControlledFailure: false; property bool debugCheckoutSuccess: false; property bool canRezCertifiedItems: Entities.canRezCertified() || Entities.canRezTmpCertified(); @@ -1072,39 +1072,72 @@ Rectangle { } signal sendToScript(var message); + function canBuyAgain() { + return (root.itemType === "entity" || root.itemType === "wearable" || root.itemType === "contentSet" || root.itemType === "unknown"); + } + + function handleContentSets() { + 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; + } + } + + function handleBuyAgainLogic() { + // If you can buy this item again... + if (canBuyAgain()) { + // If you can't afford another copy of the item... + if (root.balanceAfterPurchase < 0) { + // If you already own the item... + if (root.alreadyOwned) { + buyText.text = "Your Wallet does not have sufficient funds to purchase this item again."; + // Else if you don't already own the item... + } else { + buyText.text = "Your Wallet does not have sufficient funds to purchase this item."; + } + buyTextContainer.color = "#FFC3CD"; + buyTextContainer.border.color = "#F3808F"; + buyGlyph.text = hifi.glyphs.alert; + buyGlyph.size = 54; + // If you CAN afford another copy of the item... + } else { + handleContentSets(); + } + } + } + function refreshBuyUI() { if (root.isCertified) { if (root.ownershipStatusReceived && root.balanceReceived && root.availableUpdatesReceived) { - if (root.balanceAfterPurchase < 0 && !root.isUpdating) { - if (root.alreadyOwned) { - 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."; - } - buyTextContainer.color = "#FFC3CD"; - buyTextContainer.border.color = "#F3808F"; - buyGlyph.text = hifi.glyphs.alert; - buyGlyph.size = 54; - } else { - if (root.alreadyOwned && !root.isUpdating) { - viewInMyPurchasesButton.visible = true; - } else { - buyText.text = ""; - } + buyText.text = ""; - if (root.isUpdating) { + // If the user IS on the checkout page for the updated version of an owned item... + if (root.isUpdating) { + // If the user HAS already selected a specific edition to update... + if (root.itemEdition > 0) { buyText.text = "By pressing \"Confirm Update\", you agree to trade in your old item for the updated item that replaces it."; buyTextContainer.color = "#FFFFFF"; buyTextContainer.border.color = "#FFFFFF"; - } else 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 if the user HAS NOT selected a specific edition to update... + } else { + viewInMyPurchasesButton.text = "UPDATE TO THIS ITEM FOR FREE"; + viewInMyPurchasesButton.visible = true; + + handleBuyAgainLogic(); + } + // If the user IS NOT on the checkout page for the updated verison of an owned item... + // (i.e. they are checking out an item "normally") + } else { + if (root.alreadyOwned) { + viewInMyPurchasesButton.text = "VIEW IN MY PURCHASES"; + viewInMyPurchasesButton.visible = true; } + + handleBuyAgainLogic(); } } else { buyText.text = ""; @@ -1123,6 +1156,12 @@ Rectangle { root.activeView = "checkoutMain"; } else { root.activeView = "checkoutSuccess"; + root.ownershipStatusReceived = false; + Commerce.alreadyOwned(root.itemId); + root.availableUpdatesReceived = false; + Commerce.getAvailableUpdates(root.itemId); + root.balanceReceived = false; + Commerce.balance(); } } From 0b810c3aa8984d2081c81cfd428ca37a4ce0793d Mon Sep 17 00:00:00 2001 From: Zach Fox Date: Mon, 19 Mar 2018 14:39:50 -0700 Subject: [PATCH 32/32] Various fixes --- .../qml/hifi/commerce/checkout/Checkout.qml | 26 ++++++++++++------- .../common/EmulatedMarketplaceHeader.qml | 23 ++++++++-------- .../qml/hifi/commerce/purchases/Purchases.qml | 1 + 3 files changed, 29 insertions(+), 21 deletions(-) diff --git a/interface/resources/qml/hifi/commerce/checkout/Checkout.qml b/interface/resources/qml/hifi/commerce/checkout/Checkout.qml index ea73861c16..9933953fe8 100644 --- a/interface/resources/qml/hifi/commerce/checkout/Checkout.qml +++ b/interface/resources/qml/hifi/commerce/checkout/Checkout.qml @@ -31,6 +31,7 @@ Rectangle { property bool ownershipStatusReceived: false; property bool balanceReceived: false; property bool availableUpdatesReceived: false; + property string baseItemName: ""; property string itemName; property string itemId; property string itemHref; @@ -148,6 +149,7 @@ Rectangle { continue; } root.isUpdating = true; + root.baseItemName = result.data.updates[i].base_item_title; // This CertID is the one corresponding to the base item CertID that the user already owns root.certificateId = result.data.updates[i].certificate_id; if (root.itemType === "app") { @@ -460,7 +462,7 @@ Rectangle { // "HFC" balance label HiFiGlyphs { id: itemPriceTextLabel; - visible: !root.isUpdating; + visible: !(root.isUpdating && root.itemEdition > 0); text: hifi.glyphs.hfc; // Size size: 30; @@ -476,9 +478,9 @@ Rectangle { } FiraSansSemiBold { id: itemPriceText; - text: root.isUpdating ? "FREE\nUPGRADE" : ((root.itemPrice === -1) ? "--" : root.itemPrice); + text: (root.isUpdating && root.itemEdition > 0) ? "FREE\nUPDATE" : ((root.itemPrice === -1) ? "--" : root.itemPrice); // Text size - size: root.isUpdating ? 20 : 26; + size: (root.isUpdating && root.itemEdition > 0) ? 20 : 26; // Anchors anchors.top: parent.top; anchors.right: parent.right; @@ -578,9 +580,13 @@ Rectangle { height: 50; anchors.left: parent.left; anchors.right: parent.right; - text: "VIEW THIS ITEM IN MY PURCHASES"; + text: root.isUpdating ? "UPDATE TO THIS ITEM FOR FREE" : "VIEW THIS ITEM IN MY PURCHASES"; onClicked: { - sendToScript({method: 'checkout_goToPurchases', filterText: root.itemName}); + if (root.isUpdating) { + sendToScript({method: 'checkout_goToPurchases', filterText: root.baseItemName}); + } else { + sendToScript({method: 'checkout_goToPurchases', filterText: root.itemName}); + } } } @@ -597,15 +603,17 @@ Rectangle { height: 50; anchors.left: parent.left; anchors.right: parent.right; - text: root.isUpdating ? "CONFIRM UPDATE" : (((root.isCertified) ? ((ownershipStatusReceived && balanceReceived && availableUpdatesReceived) ? - (viewInMyPurchasesButton.visible ? "Buy It Again" : "Confirm Purchase") : "--") : "Get Item")); + text: (root.isUpdating && root.itemEdition > 0) ? "CONFIRM UPDATE" : (((root.isCertified) ? ((ownershipStatusReceived && balanceReceived && availableUpdatesReceived) ? + ((viewInMyPurchasesButton.visible && !root.isUpdating) ? "Buy It Again" : "Confirm Purchase") : "--") : "Get Item")); onClicked: { - if (root.isUpdating) { + if (root.isUpdating && root.itemEdition > 0) { // If we're updating an app, the existing app needs to be uninstalled. // This call will fail/return `false` if the app isn't installed, but that's OK. if (root.itemType === "app") { Commerce.uninstallApp(root.baseAppURL); } + buyButton.enabled = false; + loading.visible = true; Commerce.updateItem(root.certificateId); } else if (root.isCertified) { if (!root.shouldBuyWithControlledFailure) { @@ -1124,7 +1132,6 @@ Rectangle { buyTextContainer.border.color = "#FFFFFF"; // Else if the user HAS NOT selected a specific edition to update... } else { - viewInMyPurchasesButton.text = "UPDATE TO THIS ITEM FOR FREE"; viewInMyPurchasesButton.visible = true; handleBuyAgainLogic(); @@ -1133,7 +1140,6 @@ Rectangle { // (i.e. they are checking out an item "normally") } else { if (root.alreadyOwned) { - viewInMyPurchasesButton.text = "VIEW IN MY PURCHASES"; viewInMyPurchasesButton.visible = true; } diff --git a/interface/resources/qml/hifi/commerce/common/EmulatedMarketplaceHeader.qml b/interface/resources/qml/hifi/commerce/common/EmulatedMarketplaceHeader.qml index 54698c1b86..8105688131 100644 --- a/interface/resources/qml/hifi/commerce/common/EmulatedMarketplaceHeader.qml +++ b/interface/resources/qml/hifi/commerce/common/EmulatedMarketplaceHeader.qml @@ -119,17 +119,6 @@ Item { anchors.right: securityImage.left; anchors.rightMargin: 6; - Rectangle { - id: messagesWaitingLight; - visible: root.messagesWaiting; - anchors.right: myPurchasesLink.left; - anchors.verticalCenter: parent.verticalCenter; - height: 10; - width: height; - radius: height/2; - color: "red"; - } - Rectangle { id: myPurchasesLink; anchors.right: myUsernameButton.left; @@ -162,6 +151,18 @@ Item { } } + Rectangle { + id: messagesWaitingLight; + visible: root.messagesWaiting; + anchors.right: myPurchasesLink.left; + anchors.rightMargin: -2; + anchors.verticalCenter: parent.verticalCenter; + height: 10; + width: height; + radius: height/2; + color: "red"; + } + TextMetrics { id: textMetrics; font.family: "Raleway" diff --git a/interface/resources/qml/hifi/commerce/purchases/Purchases.qml b/interface/resources/qml/hifi/commerce/purchases/Purchases.qml index 0789eae868..726e6bd338 100644 --- a/interface/resources/qml/hifi/commerce/purchases/Purchases.qml +++ b/interface/resources/qml/hifi/commerce/purchases/Purchases.qml @@ -582,6 +582,7 @@ Rectangle { height: 40; text: "SHOW ME"; onClicked: { + filterBar.text = ""; filterBar.changeFilterByDisplayName("Updatable"); } }