diff --git a/interface/resources/icons/tablet-icons/wallet-a.svg b/interface/resources/icons/tablet-icons/wallet-a.svg
new file mode 100644
index 0000000000..4a0bab4b33
--- /dev/null
+++ b/interface/resources/icons/tablet-icons/wallet-a.svg
@@ -0,0 +1,180 @@
+
+
diff --git a/interface/resources/icons/tablet-icons/wallet-i.svg b/interface/resources/icons/tablet-icons/wallet-i.svg
new file mode 100644
index 0000000000..2a16ecf973
--- /dev/null
+++ b/interface/resources/icons/tablet-icons/wallet-i.svg
@@ -0,0 +1,276 @@
+
+
+
+
diff --git a/interface/resources/qml/hifi/commerce/Checkout.qml b/interface/resources/qml/hifi/commerce/Checkout.qml
index 55bd6cb4c6..d9b0072917 100644
--- a/interface/resources/qml/hifi/commerce/Checkout.qml
+++ b/interface/resources/qml/hifi/commerce/Checkout.qml
@@ -17,6 +17,7 @@ import QtQuick.Controls 1.4
import "../../styles-uit"
import "../../controls-uit" as HifiControlsUit
import "../../controls" as HifiControls
+import "./wallet" as HifiWallet
// references XXX from root context
@@ -26,32 +27,33 @@ Rectangle {
id: checkoutRoot;
property bool inventoryReceived: false;
property bool balanceReceived: false;
- property string itemId: "";
+ property string itemId: "";
property string itemHref: "";
property int balanceAfterPurchase: 0;
property bool alreadyOwned: false;
+ property int itemPriceFull: 0;
// Style
color: hifi.colors.baseGray;
Hifi.QmlCommerce {
id: commerce;
onBuyResult: {
- if (result.status !== 'success') {
- buyButton.text = result.message;
- buyButton.enabled = false;
- } else {
- if (urlHandler.canHandleUrl(itemHref)) {
- urlHandler.handleUrl(itemHref);
- }
- sendToScript({method: 'checkout_buySuccess', itemId: itemId});
+ if (result.status !== 'success') {
+ buyButton.text = result.message;
+ buyButton.enabled = false;
+ } else {
+ if (urlHandler.canHandleUrl(itemHref)) {
+ urlHandler.handleUrl(itemHref);
}
+ sendToScript({method: 'checkout_buySuccess', itemId: itemId});
+ }
}
onBalanceResult: {
if (result.status !== 'success') {
console.log("Failed to get balance", result.message);
} else {
balanceReceived = true;
- hfcBalanceText.text = result.data.balance;
- balanceAfterPurchase = result.data.balance - parseInt(itemPriceText.text, 10);
+ hfcBalanceText.text = parseFloat(result.data.balance/100).toFixed(2);
+ balanceAfterPurchase = parseFloat(result.data.balance/100) - parseFloat(checkoutRoot.itemPriceFull/100).toFixed(2);
}
}
onInventoryResult: {
@@ -67,14 +69,6 @@ Rectangle {
}
}
}
- onSecurityImageResult: {
- securityImage.source = securityImageSelection.getImagePathFromImageID(imageID);
- }
- }
-
- SecurityImageSelection {
- id: securityImageSelection;
- referrerURL: checkoutRoot.itemHref;
}
//
@@ -89,20 +83,6 @@ Rectangle {
anchors.left: parent.left;
anchors.top: parent.top;
- // Security Image
- Image {
- id: securityImage;
- // Anchors
- anchors.top: parent.top;
- anchors.left: parent.left;
- anchors.leftMargin: 16;
- height: parent.height - 5;
- width: height;
- anchors.verticalCenter: parent.verticalCenter;
- fillMode: Image.PreserveAspectFit;
- mipmap: true;
- }
-
// Title Bar text
RalewaySemiBold {
id: titleBarText;
@@ -111,7 +91,7 @@ Rectangle {
size: hifi.fontSizes.overlayTitle;
// Anchors
anchors.top: parent.top;
- anchors.left: securityImage.right;
+ anchors.left: parent.left;
anchors.leftMargin: 16;
anchors.bottom: parent.bottom;
width: paintedWidth;
@@ -132,7 +112,7 @@ Rectangle {
//
// TITLE BAR END
//
-
+
//
// ITEM DESCRIPTION START
//
@@ -147,7 +127,7 @@ Rectangle {
// Item Name text
Item {
- id: itemNameContainer;
+ id: itemNameContainer;
// Anchors
anchors.top: parent.top;
anchors.topMargin: 4;
@@ -188,11 +168,11 @@ Rectangle {
verticalAlignment: Text.AlignVCenter;
}
}
-
-
+
+
// Item Author text
Item {
- id: itemAuthorContainer;
+ id: itemAuthorContainer;
// Anchors
anchors.top: itemNameContainer.bottom;
anchors.topMargin: 4;
@@ -233,10 +213,10 @@ Rectangle {
verticalAlignment: Text.AlignVCenter;
}
}
-
+
// HFC Balance text
Item {
- id: hfcBalanceContainer;
+ id: hfcBalanceContainer;
// Anchors
anchors.top: itemAuthorContainer.bottom;
anchors.topMargin: 16;
@@ -278,10 +258,10 @@ Rectangle {
verticalAlignment: Text.AlignVCenter;
}
}
-
+
// Item Price text
Item {
- id: itemPriceContainer;
+ id: itemPriceContainer;
// Anchors
anchors.top: hfcBalanceContainer.bottom;
anchors.topMargin: 4;
@@ -322,10 +302,10 @@ Rectangle {
verticalAlignment: Text.AlignVCenter;
}
}
-
+
// HFC "Balance After Purchase" text
Item {
- id: hfcBalanceAfterPurchaseContainer;
+ id: hfcBalanceAfterPurchaseContainer;
// Anchors
anchors.top: itemPriceContainer.bottom;
anchors.topMargin: 4;
@@ -372,7 +352,7 @@ Rectangle {
// ITEM DESCRIPTION END
//
-
+
//
// ACTION BUTTONS START
//
@@ -420,7 +400,7 @@ Rectangle {
text: (inventoryReceived && balanceReceived) ? (alreadyOwned ? "Already Owned: Get Item" : "Buy") : "--";
onClicked: {
if (!alreadyOwned) {
- commerce.buy(itemId, parseInt(itemPriceText.text));
+ commerce.buy(itemId, parseFloat(itemPriceText.text*100));
} else {
if (urlHandler.canHandleUrl(itemHref)) {
urlHandler.handleUrl(itemHref);
@@ -456,11 +436,11 @@ Rectangle {
itemId = message.params.itemId;
itemNameText.text = message.params.itemName;
itemAuthorText.text = message.params.itemAuthor;
- itemPriceText.text = message.params.itemPrice;
+ checkoutRoot.itemPriceFull = message.params.itemPrice;
+ itemPriceText.text = parseFloat(checkoutRoot.itemPriceFull/100).toFixed(2);
itemHref = message.params.itemHref;
commerce.balance();
commerce.inventory();
- commerce.getSecurityImage();
break;
default:
console.log('Unrecognized message from marketplaces.js:', JSON.stringify(message));
diff --git a/interface/resources/qml/hifi/commerce/Inventory.qml b/interface/resources/qml/hifi/commerce/Inventory.qml
index 8f22e7de0f..20458f9f16 100644
--- a/interface/resources/qml/hifi/commerce/Inventory.qml
+++ b/interface/resources/qml/hifi/commerce/Inventory.qml
@@ -17,6 +17,7 @@ import QtQuick.Controls 1.4
import "../../styles-uit"
import "../../controls-uit" as HifiControlsUit
import "../../controls" as HifiControls
+import "./wallet" as HifiWallet
// references XXX from root context
@@ -33,7 +34,7 @@ Rectangle {
if (result.status !== 'success') {
console.log("Failed to get balance", result.message);
} else {
- hfcBalanceText.text = result.data.balance;
+ hfcBalanceText.text = parseFloat(result.data.balance/100).toFixed(2);
}
}
onInventoryResult: {
@@ -43,14 +44,6 @@ Rectangle {
inventoryContentsList.model = result.data.assets;
}
}
- onSecurityImageResult: {
- securityImage.source = securityImageSelection.getImagePathFromImageID(imageID);
- }
- }
-
- SecurityImageSelection {
- id: securityImageSelection;
- referrerURL: inventoryRoot.referrerURL;
}
//
@@ -65,20 +58,6 @@ Rectangle {
anchors.left: parent.left;
anchors.top: parent.top;
- // Security Image
- Image {
- id: securityImage;
- // Anchors
- anchors.top: parent.top;
- anchors.left: parent.left;
- anchors.leftMargin: 16;
- height: parent.height - 5;
- width: height;
- anchors.verticalCenter: parent.verticalCenter;
- fillMode: Image.PreserveAspectFit;
- mipmap: true;
- }
-
// Title Bar text
RalewaySemiBold {
id: titleBarText;
@@ -87,7 +66,7 @@ Rectangle {
size: hifi.fontSizes.overlayTitle;
// Anchors
anchors.top: parent.top;
- anchors.left: securityImage.right;
+ anchors.left: parent.left;
anchors.leftMargin: 16;
anchors.bottom: parent.bottom;
width: paintedWidth;
@@ -98,25 +77,6 @@ Rectangle {
verticalAlignment: Text.AlignVCenter;
}
- // "Change Security Image" button
- HifiControlsUit.Button {
- id: changeSecurityImageButton;
- color: hifi.buttons.black;
- colorScheme: hifi.colorSchemes.dark;
- anchors.top: parent.top;
- anchors.topMargin: 3;
- anchors.bottom: parent.bottom;
- anchors.bottomMargin: 3;
- anchors.right: parent.right;
- anchors.rightMargin: 20;
- width: 200;
- text: "Change Security Image"
- onClicked: {
- securityImageSelection.isManuallyChangingSecurityImage = true;
- securityImageSelection.visible = true;
- }
- }
-
// Separator
HifiControlsUit.Separator {
anchors.left: parent.left;
@@ -132,7 +92,7 @@ Rectangle {
// HFC BALANCE START
//
Item {
- id: hfcBalanceContainer;
+ id: hfcBalanceContainer;
// Size
width: inventoryRoot.width;
height: childrenRect.height + 20;
@@ -177,7 +137,7 @@ Rectangle {
//
// HFC BALANCE END
//
-
+
//
// INVENTORY CONTENTS START
//
@@ -192,7 +152,7 @@ Rectangle {
anchors.topMargin: 8;
anchors.bottom: actionButtonsContainer.top;
anchors.bottomMargin: 8;
-
+
RalewaySemiBold {
id: inventoryContentsLabel;
text: "Inventory:";
@@ -249,7 +209,7 @@ Rectangle {
//
// INVENTORY CONTENTS END
//
-
+
//
// ACTION BUTTONS START
//
@@ -307,7 +267,6 @@ Rectangle {
referrerURL = message.referrerURL;
commerce.balance();
commerce.inventory();
- commerce.getSecurityImage();
break;
default:
console.log('Unrecognized message from marketplaces.js:', JSON.stringify(message));
diff --git a/interface/resources/qml/hifi/commerce/SecurityImageSelection.qml b/interface/resources/qml/hifi/commerce/SecurityImageSelection.qml
deleted file mode 100644
index 7775f1ff9c..0000000000
--- a/interface/resources/qml/hifi/commerce/SecurityImageSelection.qml
+++ /dev/null
@@ -1,271 +0,0 @@
-//
-// SecurityImageSelection.qml
-// qml/hifi/commerce
-//
-// SecurityImageSelection
-//
-// Created by Zach Fox on 2017-08-15
-// Copyright 2017 High Fidelity, Inc.
-//
-// Distributed under the Apache License, Version 2.0.
-// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
-//
-
-import Hifi 1.0 as Hifi
-import QtQuick 2.5
-import QtQuick.Controls 1.4
-import "../../styles-uit"
-import "../../controls-uit" as HifiControlsUit
-import "../../controls" as HifiControls
-
-// references XXX from root context
-
-Rectangle {
- HifiConstants { id: hifi; }
-
- id: securityImageSelectionRoot;
- property string referrerURL: "";
- property bool isManuallyChangingSecurityImage: false;
- anchors.fill: parent;
- // Style
- color: hifi.colors.baseGray;
- z:999; // On top of everything else
- visible: false;
-
- Hifi.QmlCommerce {
- id: commerce;
- onSecurityImageResult: {
- if (!isManuallyChangingSecurityImage) {
- securityImageSelectionRoot.visible = (imageID == 0);
- }
- if (imageID > 0) {
- for (var itr = 0; itr < gridModel.count; itr++) {
- var thisValue = gridModel.get(itr).securityImageEnumValue;
- if (thisValue === imageID) {
- securityImageGrid.currentIndex = itr;
- break;
- }
- }
- }
- }
- }
-
- Component.onCompleted: {
- commerce.getSecurityImage();
- }
-
- //
- // TITLE BAR START
- //
- Item {
- id: titleBarContainer;
- // Size
- width: securityImageSelectionRoot.width;
- height: 30;
- // Anchors
- anchors.left: parent.left;
- anchors.top: parent.top;
-
- // Title Bar text
- RalewaySemiBold {
- id: titleBarText;
- text: "Select a Security Image";
- // Text size
- size: hifi.fontSizes.overlayTitle;
- // Anchors
- anchors.fill: parent;
- anchors.leftMargin: 16;
- // Style
- color: hifi.colors.lightGrayText;
- // Alignment
- horizontalAlignment: Text.AlignHLeft;
- verticalAlignment: Text.AlignVCenter;
- }
-
- // Separator
- HifiControlsUit.Separator {
- anchors.left: parent.left;
- anchors.right: parent.right;
- anchors.bottom: parent.bottom;
- }
- }
- //
- // TITLE BAR END
- //
-
- //
- // EXPLANATION START
- //
- Item {
- id: explanationContainer;
- // Size
- width: securityImageSelectionRoot.width;
- height: 85;
- // Anchors
- anchors.top: titleBarContainer.bottom;
- anchors.left: parent.left;
- anchors.right: parent.right;
-
- RalewayRegular {
- id: explanationText;
- text: "This image will be displayed on secure Inventory and Marketplace Checkout dialogs.
If you don't see your selected image on these dialogs, do not use them!";
- // Text size
- size: 16;
- // Anchors
- anchors.top: parent.top;
- anchors.topMargin: 4;
- anchors.left: parent.left;
- anchors.leftMargin: 16;
- anchors.right: parent.right;
- anchors.rightMargin: 16;
- // Style
- color: hifi.colors.lightGrayText;
- wrapMode: Text.WordWrap;
- // Alignment
- horizontalAlignment: Text.AlignHLeft;
- verticalAlignment: Text.AlignVCenter;
- }
-
- // Separator
- HifiControlsUit.Separator {
- anchors.left: parent.left;
- anchors.right: parent.right;
- anchors.bottom: parent.bottom;
- }
- }
- //
- // EXPLANATION END
- //
-
- //
- // SECURITY IMAGE GRID START
- //
- Item {
- id: securityImageGridContainer;
- // Anchors
- anchors.left: parent.left;
- anchors.leftMargin: 8;
- anchors.right: parent.right;
- anchors.rightMargin: 8;
- anchors.top: explanationContainer.bottom;
- anchors.topMargin: 8;
- anchors.bottom: actionButtonsContainer.top;
- anchors.bottomMargin: 8;
-
- SecurityImageModel {
- id: gridModel;
- }
-
- GridView {
- id: securityImageGrid;
- clip: true;
- // Anchors
- anchors.fill: parent;
- currentIndex: -1;
- cellWidth: width / 2;
- cellHeight: height / 3;
- model: gridModel;
- delegate: Item {
- width: securityImageGrid.cellWidth;
- height: securityImageGrid.cellHeight;
- Item {
- anchors.fill: parent;
- Image {
- width: parent.width - 8;
- height: parent.height - 8;
- source: sourcePath;
- anchors.horizontalCenter: parent.horizontalCenter;
- anchors.verticalCenter: parent.verticalCenter;
- fillMode: Image.PreserveAspectFit;
- mipmap: true;
- }
- }
- MouseArea {
- anchors.fill: parent;
- onClicked: {
- securityImageGrid.currentIndex = index;
- }
- }
- }
- highlight: Rectangle {
- width: securityImageGrid.cellWidth;
- height: securityImageGrid.cellHeight;
- color: hifi.colors.blueHighlight;
- }
- }
- }
- //
- // SECURITY IMAGE GRID END
- //
-
-
- //
- // ACTION BUTTONS START
- //
- Item {
- id: actionButtonsContainer;
- // Size
- width: securityImageSelectionRoot.width;
- height: 40;
- // Anchors
- anchors.left: parent.left;
- anchors.bottom: parent.bottom;
- anchors.bottomMargin: 8;
-
- // "Cancel" button
- HifiControlsUit.Button {
- id: cancelButton;
- color: hifi.buttons.black;
- colorScheme: hifi.colorSchemes.dark;
- anchors.top: parent.top;
- anchors.topMargin: 3;
- anchors.bottom: parent.bottom;
- anchors.bottomMargin: 3;
- anchors.left: parent.left;
- anchors.leftMargin: 20;
- width: parent.width/2 - anchors.leftMargin*2;
- text: "Cancel"
- onClicked: {
- if (!securityImageSelectionRoot.isManuallyChangingSecurityImage) {
- sendToScript({method: 'securityImageSelection_cancelClicked', referrerURL: securityImageSelectionRoot.referrerURL});
- } else {
- securityImageSelectionRoot.visible = false;
- }
- }
- }
-
- // "Confirm" button
- HifiControlsUit.Button {
- id: confirmButton;
- color: hifi.buttons.black;
- colorScheme: hifi.colorSchemes.dark;
- anchors.top: parent.top;
- anchors.topMargin: 3;
- anchors.bottom: parent.bottom;
- anchors.bottomMargin: 3;
- anchors.right: parent.right;
- anchors.rightMargin: 20;
- width: parent.width/2 - anchors.rightMargin*2;
- text: "Confirm";
- onClicked: {
- securityImageSelectionRoot.isManuallyChangingSecurityImage = false;
- commerce.chooseSecurityImage(gridModel.get(securityImageGrid.currentIndex).securityImageEnumValue);
- }
- }
- }
- //
- // ACTION BUTTONS END
- //
-
- //
- // FUNCTION DEFINITIONS START
- //
- signal sendToScript(var message);
-
- function getImagePathFromImageID(imageID) {
- return (imageID ? gridModel.get(imageID - 1).sourcePath : "");
- }
- //
- // FUNCTION DEFINITIONS END
- //
-}
diff --git a/interface/resources/qml/hifi/commerce/wallet/Help.qml b/interface/resources/qml/hifi/commerce/wallet/Help.qml
new file mode 100644
index 0000000000..2252cbfb59
--- /dev/null
+++ b/interface/resources/qml/hifi/commerce/wallet/Help.qml
@@ -0,0 +1,73 @@
+//
+// SendMoney.qml
+// qml/hifi/commerce/wallet
+//
+// SendMoney
+//
+// Created by Zach Fox on 2017-08-18
+// Copyright 2017 High Fidelity, Inc.
+//
+// Distributed under the Apache License, Version 2.0.
+// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
+//
+
+import Hifi 1.0 as Hifi
+import QtQuick 2.5
+import QtQuick.Controls 1.4
+import "../../../styles-uit"
+import "../../../controls-uit" as HifiControlsUit
+import "../../../controls" as HifiControls
+
+// references XXX from root context
+
+Item {
+ HifiConstants { id: hifi; }
+
+ id: root;
+
+ Hifi.QmlCommerce {
+ id: commerce;
+ }
+
+ // "Unavailable"
+ RalewayRegular {
+ text: "Help me!";
+ // Anchors
+ anchors.fill: parent;
+ // Text size
+ size: 24;
+ // Style
+ color: hifi.colors.faintGray;
+ wrapMode: Text.WordWrap;
+ // Alignment
+ horizontalAlignment: Text.AlignHCenter;
+ verticalAlignment: Text.AlignVCenter;
+ }
+
+ //
+ // FUNCTION DEFINITIONS START
+ //
+ //
+ // Function Name: fromScript()
+ //
+ // Relevant Variables:
+ // None
+ //
+ // Arguments:
+ // message: The message sent from the JavaScript.
+ // Messages are in format "{method, params}", like json-rpc.
+ //
+ // Description:
+ // Called when a message is received from a script.
+ //
+ function fromScript(message) {
+ switch (message.method) {
+ default:
+ console.log('Unrecognized message from wallet.js:', JSON.stringify(message));
+ }
+ }
+ signal sendSignalToWallet(var msg);
+ //
+ // FUNCTION DEFINITIONS END
+ //
+}
diff --git a/interface/resources/qml/hifi/commerce/wallet/NotSetUp.qml b/interface/resources/qml/hifi/commerce/wallet/NotSetUp.qml
new file mode 100644
index 0000000000..3efb592ba1
--- /dev/null
+++ b/interface/resources/qml/hifi/commerce/wallet/NotSetUp.qml
@@ -0,0 +1,127 @@
+//
+// NotSetUp.qml
+// qml/hifi/commerce/wallet
+//
+// NotSetUp
+//
+// Created by Zach Fox on 2017-08-18
+// Copyright 2017 High Fidelity, Inc.
+//
+// Distributed under the Apache License, Version 2.0.
+// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
+//
+
+import Hifi 1.0 as Hifi
+import QtQuick 2.5
+import QtQuick.Controls 1.4
+import "../../../styles-uit"
+import "../../../controls-uit" as HifiControlsUit
+import "../../../controls" as HifiControls
+
+// references XXX from root context
+
+Item {
+ HifiConstants { id: hifi; }
+
+ id: root;
+ Hifi.QmlCommerce {
+ id: commerce;
+ }
+
+ //
+ // TAB CONTENTS START
+ //
+
+ // Text below title bar
+ RalewaySemiBold {
+ id: notSetUpText;
+ text: "Your Wallet Account Has Not Been Set Up";
+ // Text size
+ size: 22;
+ // Anchors
+ anchors.top: parent.top;
+ anchors.topMargin: 100;
+ anchors.left: parent.left;
+ anchors.leftMargin: 16;
+ anchors.right: parent.right;
+ anchors.rightMargin: 16;
+ height: 50;
+ width: paintedWidth;
+ // Style
+ color: hifi.colors.faintGray;
+ wrapMode: Text.WordWrap;
+ // Alignment
+ horizontalAlignment: Text.AlignHCenter;
+ verticalAlignment: Text.AlignVCenter;
+ }
+
+ // Explanitory text
+ RalewayRegular {
+ text: "To buy and sell items in High Fidelity Coin (HFC), you first need " +
+ "to set up your wallet.
You do not need to submit a credit card or personal information to set up your wallet.";
+ // Text size
+ size: 18;
+ // Anchors
+ anchors.top: notSetUpText.bottom;
+ anchors.topMargin: 16;
+ anchors.left: parent.left;
+ anchors.leftMargin: 30;
+ anchors.right: parent.right;
+ anchors.rightMargin: 30;
+ height: 100;
+ width: paintedWidth;
+ // Style
+ color: hifi.colors.faintGray;
+ wrapMode: Text.WordWrap;
+ // Alignment
+ horizontalAlignment: Text.AlignHCenter;
+ verticalAlignment: Text.AlignVCenter;
+ }
+
+ // "Set Up" button
+ HifiControlsUit.Button {
+ color: hifi.buttons.black;
+ colorScheme: hifi.colorSchemes.dark;
+ anchors.bottom: parent.bottom;
+ anchors.bottomMargin: 150;
+ anchors.horizontalCenter: parent.horizontalCenter;
+ width: parent.width/2;
+ height: 50;
+ text: "Set Up My Wallet";
+ onClicked: {
+ sendSignalToWallet({method: 'setUpClicked'});
+ }
+ }
+
+
+ //
+ // TAB CONTENTS END
+ //
+
+ //
+ // FUNCTION DEFINITIONS START
+ //
+ //
+ // Function Name: fromScript()
+ //
+ // Relevant Variables:
+ // None
+ //
+ // Arguments:
+ // message: The message sent from the JavaScript.
+ // Messages are in format "{method, params}", like json-rpc.
+ //
+ // Description:
+ // Called when a message is received from a script.
+ //
+ function fromScript(message) {
+ switch (message.method) {
+ default:
+ console.log('Unrecognized message from wallet.js:', JSON.stringify(message));
+ }
+ }
+ signal sendSignalToWallet(var msg);
+ //
+ // FUNCTION DEFINITIONS END
+ //
+}
diff --git a/interface/resources/qml/hifi/commerce/wallet/PassphraseSelection.qml b/interface/resources/qml/hifi/commerce/wallet/PassphraseSelection.qml
new file mode 100644
index 0000000000..89ef851b06
--- /dev/null
+++ b/interface/resources/qml/hifi/commerce/wallet/PassphraseSelection.qml
@@ -0,0 +1,232 @@
+//
+// PassphraseSelection.qml
+// qml/hifi/commerce/wallet
+//
+// PassphraseSelection
+//
+// Created by Zach Fox on 2017-08-18
+// Copyright 2017 High Fidelity, Inc.
+//
+// Distributed under the Apache License, Version 2.0.
+// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
+//
+
+import Hifi 1.0 as Hifi
+import QtQuick 2.5
+import QtQuick.Controls 1.4
+import "../../../styles-uit"
+import "../../../controls-uit" as HifiControlsUit
+import "../../../controls" as HifiControls
+
+// references XXX from root context
+
+Item {
+ HifiConstants { id: hifi; }
+
+ id: root;
+
+ // This object is always used in a popup.
+ // This MouseArea is used to prevent a user from being
+ // able to click on a button/mouseArea underneath the popup.
+ MouseArea {
+ anchors.fill: parent;
+ propagateComposedEvents: false;
+ }
+
+ Hifi.QmlCommerce {
+ id: commerce;
+ onSecurityImageResult: {
+ passphrasePageSecurityImage.source = "";
+ passphrasePageSecurityImage.source = "image://security/securityImage";
+ }
+
+ onPassphraseSetupStatusResult: {
+ sendMessageToLightbox({method: 'statusResult', status: passphraseIsSetup});
+ }
+ }
+
+ onVisibleChanged: {
+ if (visible) {
+ passphraseField.focus = true;
+ }
+ }
+
+ SecurityImageModel {
+ id: gridModel;
+ }
+
+ HifiControlsUit.TextField {
+ id: passphraseField;
+ anchors.top: parent.top;
+ anchors.topMargin: 30;
+ anchors.left: parent.left;
+ anchors.leftMargin: 16;
+ width: 280;
+ height: 50;
+ echoMode: TextInput.Password;
+ placeholderText: "passphrase";
+
+ onVisibleChanged: {
+ if (visible) {
+ text = "";
+ }
+ }
+ }
+ HifiControlsUit.TextField {
+ id: passphraseFieldAgain;
+ anchors.top: passphraseField.bottom;
+ anchors.topMargin: 10;
+ anchors.left: passphraseField.left;
+ anchors.right: passphraseField.right;
+ height: 50;
+ echoMode: TextInput.Password;
+ placeholderText: "re-enter passphrase";
+
+ onVisibleChanged: {
+ if (visible) {
+ text = "";
+ }
+ }
+ }
+
+ // Security Image
+ Item {
+ id: securityImageContainer;
+ // Anchors
+ anchors.top: passphraseField.top;
+ anchors.left: passphraseField.right;
+ anchors.leftMargin: 12;
+ anchors.right: parent.right;
+ Image {
+ id: passphrasePageSecurityImage;
+ anchors.top: parent.top;
+ anchors.horizontalCenter: parent.horizontalCenter;
+ height: 75;
+ width: height;
+ fillMode: Image.PreserveAspectFit;
+ mipmap: true;
+ source: "image://security/securityImage";
+ cache: false;
+ onVisibleChanged: {
+ commerce.getSecurityImage();
+ }
+ }
+ // "Security picture" text below pic
+ RalewayRegular {
+ text: "security picture";
+ // Text size
+ size: 12;
+ // Anchors
+ anchors.top: passphrasePageSecurityImage.bottom;
+ anchors.topMargin: 4;
+ anchors.left: securityImageContainer.left;
+ anchors.right: securityImageContainer.right;
+ height: paintedHeight;
+ // Style
+ color: hifi.colors.faintGray;
+ // Alignment
+ horizontalAlignment: Text.AlignHCenter;
+ verticalAlignment: Text.AlignVCenter;
+ }
+ }
+
+ // Error text below TextFields
+ RalewaySemiBold {
+ id: errorText;
+ text: "";
+ // Text size
+ size: 16;
+ // Anchors
+ anchors.top: passphraseFieldAgain.bottom;
+ anchors.topMargin: 0;
+ anchors.left: parent.left;
+ anchors.leftMargin: 16;
+ anchors.right: parent.right;
+ anchors.rightMargin: 16;
+ height: 30;
+ // Style
+ color: hifi.colors.redHighlight;
+ // Alignment
+ horizontalAlignment: Text.AlignHLeft;
+ verticalAlignment: Text.AlignVCenter;
+ }
+
+ // Text below TextFields
+ RalewaySemiBold {
+ id: passwordReqs;
+ text: "Passphrase must be at least 4 characters";
+ // Text size
+ size: 16;
+ // Anchors
+ anchors.top: passphraseFieldAgain.bottom;
+ anchors.topMargin: 16;
+ anchors.left: parent.left;
+ anchors.leftMargin: 16;
+ anchors.right: parent.right;
+ anchors.rightMargin: 16;
+ height: 30;
+ // Style
+ color: hifi.colors.faintGray;
+ // Alignment
+ horizontalAlignment: Text.AlignHLeft;
+ verticalAlignment: Text.AlignVCenter;
+ }
+
+ // Show passphrase text
+ HifiControlsUit.CheckBox {
+ id: showPassphrase;
+ colorScheme: hifi.colorSchemes.dark;
+ anchors.left: parent.left;
+ anchors.leftMargin: 16;
+ anchors.top: passwordReqs.bottom;
+ anchors.topMargin: 16;
+ height: 30;
+ text: "Show passphrase as plain text";
+ boxSize: 24;
+ onClicked: {
+ passphraseField.echoMode = checked ? TextInput.Normal : TextInput.Password;
+ passphraseFieldAgain.echoMode = checked ? TextInput.Normal : TextInput.Password;
+ }
+ }
+
+ // Text below checkbox
+ RalewayRegular {
+ text: "Your passphrase is used to encrypt your private keys. Please write it down. If it is lost, you will not be able to recover it.";
+ // Text size
+ size: 16;
+ // Anchors
+ anchors.top: showPassphrase.bottom;
+ anchors.topMargin: 16;
+ anchors.left: parent.left;
+ anchors.leftMargin: 16;
+ anchors.right: parent.right;
+ anchors.rightMargin: 16;
+ height: paintedHeight;
+ // Style
+ color: hifi.colors.faintGray;
+ wrapMode: Text.WordWrap;
+ // Alignment
+ horizontalAlignment: Text.AlignLeft;
+ verticalAlignment: Text.AlignVCenter;
+ }
+
+ function validateAndSubmitPassphrase() {
+ if (passphraseField.text.length < 4) {
+ setErrorText("Passphrase too short.");
+ return false;
+ } else if (passphraseField.text !== passphraseFieldAgain.text) {
+ setErrorText("Passphrases don't match.");
+ return false;
+ } else {
+ setErrorText("");
+ commerce.setPassphrase(passphraseField.text);
+ return true;
+ }
+ }
+
+ function setErrorText(text) {
+ errorText.text = text;
+ }
+
+ signal sendMessageToLightbox(var msg);
+}
diff --git a/interface/resources/qml/hifi/commerce/wallet/PassphraseSelectionLightbox.qml b/interface/resources/qml/hifi/commerce/wallet/PassphraseSelectionLightbox.qml
new file mode 100644
index 0000000000..862d1894db
--- /dev/null
+++ b/interface/resources/qml/hifi/commerce/wallet/PassphraseSelectionLightbox.qml
@@ -0,0 +1,175 @@
+//
+// PassphraseSelectionLightbox.qml
+// qml/hifi/commerce/wallet
+//
+// PassphraseSelectionLightbox
+//
+// Created by Zach Fox on 2017-08-18
+// Copyright 2017 High Fidelity, Inc.
+//
+// Distributed under the Apache License, Version 2.0.
+// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
+//
+
+import Hifi 1.0 as Hifi
+import QtQuick 2.5
+import QtQuick.Controls 1.4
+import "../../../styles-uit"
+import "../../../controls-uit" as HifiControlsUit
+import "../../../controls" as HifiControls
+
+// references XXX from root context
+
+Rectangle {
+ HifiConstants { id: hifi; }
+
+ id: root;
+ // Style
+ color: hifi.colors.baseGray;
+
+ onVisibleChanged: {
+ if (visible) {
+ root.resetSubmitButton();
+ }
+ }
+
+ Connections {
+ target: passphraseSelection;
+ onSendMessageToLightbox: {
+ if (msg.method === 'statusResult') {
+ if (msg.status) {
+ // Success submitting new passphrase
+ root.resetSubmitButton();
+ root.visible = false;
+ } else {
+ // Error submitting new passphrase
+ root.resetSubmitButton();
+ passphraseSelection.setErrorText("Backend error");
+ }
+ }
+ }
+ }
+
+ //
+ // SECURE PASSPHRASE SELECTION START
+ //
+ Item {
+ id: choosePassphraseContainer;
+ // Anchors
+ anchors.fill: parent;
+
+ Item {
+ id: passphraseTitle;
+ // Size
+ width: parent.width;
+ height: 50;
+ // Anchors
+ anchors.left: parent.left;
+ anchors.top: parent.top;
+
+ // Title Bar text
+ RalewaySemiBold {
+ text: "CHANGE PASSPHRASE";
+ // Text size
+ size: hifi.fontSizes.overlayTitle;
+ // Anchors
+ anchors.top: parent.top;
+ anchors.left: parent.left;
+ anchors.leftMargin: 16;
+ anchors.bottom: parent.bottom;
+ width: paintedWidth;
+ // Style
+ color: hifi.colors.faintGray;
+ // Alignment
+ horizontalAlignment: Text.AlignHLeft;
+ verticalAlignment: Text.AlignVCenter;
+ }
+ }
+
+ // Text below title bar
+ RalewaySemiBold {
+ id: passphraseTitleHelper;
+ text: "Choose a Secure Passphrase";
+ // Text size
+ size: 24;
+ // Anchors
+ anchors.top: passphraseTitle.bottom;
+ anchors.left: parent.left;
+ anchors.leftMargin: 16;
+ anchors.right: parent.right;
+ anchors.rightMargin: 16;
+ height: 50;
+ // Style
+ color: hifi.colors.faintGray;
+ // Alignment
+ horizontalAlignment: Text.AlignHLeft;
+ verticalAlignment: Text.AlignVCenter;
+ }
+
+ PassphraseSelection {
+ id: passphraseSelection;
+ anchors.top: passphraseTitleHelper.bottom;
+ anchors.topMargin: 30;
+ anchors.left: parent.left;
+ anchors.right: parent.right;
+ anchors.bottom: passphraseNavBar.top;
+ }
+
+ // Navigation Bar
+ Item {
+ id: passphraseNavBar;
+ // Size
+ width: parent.width;
+ height: 100;
+ // Anchors:
+ anchors.left: parent.left;
+ anchors.bottom: parent.bottom;
+
+ // "Cancel" button
+ HifiControlsUit.Button {
+ color: hifi.buttons.black;
+ colorScheme: hifi.colorSchemes.dark;
+ anchors.top: parent.top;
+ anchors.topMargin: 3;
+ anchors.bottom: parent.bottom;
+ anchors.bottomMargin: 3;
+ anchors.left: parent.left;
+ anchors.leftMargin: 20;
+ width: 100;
+ text: "Cancel"
+ onClicked: {
+ root.visible = false;
+ }
+ }
+
+ // "Submit" button
+ HifiControlsUit.Button {
+ id: passphraseSubmitButton;
+ color: hifi.buttons.black;
+ colorScheme: hifi.colorSchemes.dark;
+ anchors.top: parent.top;
+ anchors.topMargin: 3;
+ anchors.bottom: parent.bottom;
+ anchors.bottomMargin: 3;
+ anchors.right: parent.right;
+ anchors.rightMargin: 20;
+ width: 100;
+ text: "Submit";
+ onClicked: {
+ if (passphraseSelection.validateAndSubmitPassphrase()) {
+ passphraseSubmitButton.text = "Submitting...";
+ passphraseSubmitButton.enabled = false;
+ }
+ }
+ }
+ }
+ }
+ //
+ // SECURE PASSPHRASE SELECTION END
+ //
+
+ function resetSubmitButton() {
+ passphraseSubmitButton.enabled = true;
+ passphraseSubmitButton.text = "Submit";
+ }
+}
diff --git a/interface/resources/qml/hifi/commerce/wallet/Security.qml b/interface/resources/qml/hifi/commerce/wallet/Security.qml
new file mode 100644
index 0000000000..3f3d00b401
--- /dev/null
+++ b/interface/resources/qml/hifi/commerce/wallet/Security.qml
@@ -0,0 +1,322 @@
+//
+// Security.qml
+// qml/hifi/commerce/wallet
+//
+// Security
+//
+// Created by Zach Fox on 2017-08-18
+// Copyright 2017 High Fidelity, Inc.
+//
+// Distributed under the Apache License, Version 2.0.
+// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
+//
+
+import Hifi 1.0 as Hifi
+import QtQuick 2.5
+import QtQuick.Controls 1.4
+import "../../../styles-uit"
+import "../../../controls-uit" as HifiControlsUit
+import "../../../controls" as HifiControls
+
+// references XXX from root context
+
+Item {
+ HifiConstants { id: hifi; }
+
+ id: root;
+
+ Hifi.QmlCommerce {
+ id: commerce;
+
+ onSecurityImageResult: {
+ if (exists) { // "If security image is set up"
+ var path = "image://security/securityImage";
+ topSecurityImage.source = "";
+ topSecurityImage.source = path;
+ changeSecurityImageImage.source = "";
+ changeSecurityImageImage.source = path;
+ changePassphraseImage.source = "";
+ changePassphraseImage.source = path;
+ }
+ }
+
+ onKeyFilePathResult: {
+ if (path !== "") {
+ keyFilePath.text = path;
+ }
+ }
+ }
+
+ SecurityImageModel {
+ id: securityImageModel;
+ }
+
+ // Username Text
+ RalewayRegular {
+ id: usernameText;
+ text: Account.username;
+ // Text size
+ size: 24;
+ // Style
+ color: hifi.colors.faintGray;
+ elide: Text.ElideRight;
+ // Anchors
+ anchors.top: securityImageContainer.top;
+ anchors.bottom: securityImageContainer.bottom;
+ anchors.left: parent.left;
+ anchors.right: securityImageContainer.left;
+ }
+
+ // Security Image
+ Item {
+ id: securityImageContainer;
+ // Anchors
+ anchors.top: parent.top;
+ anchors.right: parent.right;
+ width: 75;
+ height: childrenRect.height;
+
+ onVisibleChanged: {
+ if (visible) {
+ commerce.getSecurityImage();
+ }
+ }
+
+ Image {
+ id: topSecurityImage;
+ // Anchors
+ anchors.top: parent.top;
+ anchors.horizontalCenter: parent.horizontalCenter;
+ height: parent.width - 10;
+ width: height;
+ fillMode: Image.PreserveAspectFit;
+ mipmap: true;
+ source: "image://security/securityImage";
+ cache: false;
+ }
+ // "Security picture" text below pic
+ RalewayRegular {
+ text: "security picture";
+ // Text size
+ size: 12;
+ // Anchors
+ anchors.top: topSecurityImage.bottom;
+ anchors.topMargin: 4;
+ anchors.left: securityImageContainer.left;
+ anchors.right: securityImageContainer.right;
+ height: paintedHeight;
+ // Style
+ color: hifi.colors.faintGray;
+ // Alignment
+ horizontalAlignment: Text.AlignHCenter;
+ verticalAlignment: Text.AlignVCenter;
+ }
+ }
+
+ Item {
+ id: securityContainer;
+ anchors.top: securityImageContainer.bottom;
+ anchors.topMargin: 20;
+ anchors.left: parent.left;
+ anchors.right: parent.right;
+ height: childrenRect.height;
+
+ RalewayRegular {
+ id: securityText;
+ text: "Security";
+ // Anchors
+ anchors.top: parent.top;
+ anchors.left: parent.left;
+ anchors.right: parent.right;
+ height: 30;
+ // Text size
+ size: 22;
+ // Style
+ color: hifi.colors.faintGray;
+ }
+
+ Item {
+ id: changePassphraseContainer;
+ anchors.top: securityText.bottom;
+ anchors.topMargin: 16;
+ anchors.left: parent.left;
+ anchors.right: parent.right;
+ height: 75;
+
+ Image {
+ id: changePassphraseImage;
+ // Anchors
+ anchors.top: parent.top;
+ anchors.left: parent.left;
+ height: parent.height;
+ width: height;
+ source: "image://security/securityImage";
+ fillMode: Image.PreserveAspectFit;
+ mipmap: true;
+ cache: false;
+ }
+ // "Change Passphrase" button
+ HifiControlsUit.Button {
+ id: changePassphraseButton;
+ color: hifi.buttons.black;
+ colorScheme: hifi.colorSchemes.dark;
+ anchors.verticalCenter: parent.verticalCenter;
+ anchors.left: changePassphraseImage.right;
+ anchors.leftMargin: 16;
+ width: 250;
+ height: 50;
+ text: "Change My Passphrase";
+ onClicked: {
+ sendSignalToWallet({method: 'walletSecurity_changePassphrase'});
+ }
+ }
+ }
+
+ Item {
+ id: changeSecurityImageContainer;
+ anchors.top: changePassphraseContainer.bottom;
+ anchors.topMargin: 8;
+ anchors.left: parent.left;
+ anchors.right: parent.right;
+ height: 75;
+
+ Image {
+ id: changeSecurityImageImage;
+ // Anchors
+ anchors.top: parent.top;
+ anchors.left: parent.left;
+ height: parent.height;
+ width: height;
+ fillMode: Image.PreserveAspectFit;
+ mipmap: true;
+ cache: false;
+ source: "image://security/securityImage";
+ }
+ // "Change Security Image" button
+ HifiControlsUit.Button {
+ id: changeSecurityImageButton;
+ color: hifi.buttons.black;
+ colorScheme: hifi.colorSchemes.dark;
+ anchors.verticalCenter: parent.verticalCenter;
+ anchors.left: changeSecurityImageImage.right;
+ anchors.leftMargin: 16;
+ width: 250;
+ height: 50;
+ text: "Change My Security Image";
+ onClicked: {
+ sendSignalToWallet({method: 'walletSecurity_changeSecurityImage'});
+ }
+ }
+ }
+ }
+
+ Item {
+ id: yourPrivateKeysContainer;
+ anchors.top: securityContainer.bottom;
+ anchors.topMargin: 20;
+ anchors.left: parent.left;
+ anchors.right: parent.right;
+ height: childrenRect.height;
+
+ RalewaySemiBold {
+ id: yourPrivateKeysText;
+ text: "Your Private Keys";
+ // Anchors
+ anchors.top: parent.top;
+ anchors.left: parent.left;
+ anchors.right: parent.right;
+ height: 30;
+ // Text size
+ size: 22;
+ // Style
+ color: hifi.colors.faintGray;
+ }
+
+ // Text below "your private keys"
+ RalewayRegular {
+ id: explanitoryText;
+ text: "Your money and purchases are secured with private keys that only you " +
+ "have access to. If they are lost, you will not be able to access your money or purchases. " +
+ "To safeguard your private keys, back up this file regularly:";
+ // Text size
+ size: 18;
+ // Anchors
+ anchors.top: yourPrivateKeysText.bottom;
+ anchors.topMargin: 10;
+ anchors.left: parent.left;
+ anchors.right: parent.right;
+ height: paintedHeight;
+ // Style
+ color: hifi.colors.faintGray;
+ wrapMode: Text.WordWrap;
+ // Alignment
+ horizontalAlignment: Text.AlignLeft;
+ verticalAlignment: Text.AlignVCenter;
+ }
+ HifiControlsUit.TextField {
+ id: keyFilePath;
+ anchors.top: explanitoryText.bottom;
+ anchors.topMargin: 10;
+ anchors.left: parent.left;
+ anchors.right: clipboardButton.left;
+ height: 40;
+ readOnly: true;
+
+ onVisibleChanged: {
+ if (visible) {
+ commerce.getKeyFilePath();
+ }
+ }
+ }
+ HifiControlsUit.Button {
+ id: clipboardButton;
+ color: hifi.buttons.black;
+ colorScheme: hifi.colorSchemes.dark;
+ anchors.right: parent.right;
+ anchors.top: keyFilePath.top;
+ anchors.bottom: keyFilePath.bottom;
+ width: height;
+ HiFiGlyphs {
+ text: hifi.glyphs.question;
+ // Size
+ size: parent.height*1.3;
+ // Anchors
+ anchors.fill: parent;
+ // Style
+ horizontalAlignment: Text.AlignHCenter;
+ color: enabled ? hifi.colors.white : hifi.colors.faintGray;
+ }
+
+ onClicked: {
+ Window.copyToClipboard(keyFilePath.text);
+ }
+ }
+ }
+
+ //
+ // FUNCTION DEFINITIONS START
+ //
+ //
+ // Function Name: fromScript()
+ //
+ // Relevant Variables:
+ // None
+ //
+ // Arguments:
+ // message: The message sent from the JavaScript.
+ // Messages are in format "{method, params}", like json-rpc.
+ //
+ // Description:
+ // Called when a message is received from a script.
+ //
+ function fromScript(message) {
+ switch (message.method) {
+ default:
+ console.log('Unrecognized message from wallet.js:', JSON.stringify(message));
+ }
+ }
+ signal sendSignalToWallet(var msg);
+ //
+ // FUNCTION DEFINITIONS END
+ //
+}
diff --git a/interface/resources/qml/hifi/commerce/SecurityImageModel.qml b/interface/resources/qml/hifi/commerce/wallet/SecurityImageModel.qml
similarity index 85%
rename from interface/resources/qml/hifi/commerce/SecurityImageModel.qml
rename to interface/resources/qml/hifi/commerce/wallet/SecurityImageModel.qml
index 2fbf28683f..b49f16857b 100644
--- a/interface/resources/qml/hifi/commerce/SecurityImageModel.qml
+++ b/interface/resources/qml/hifi/commerce/wallet/SecurityImageModel.qml
@@ -4,7 +4,7 @@
//
// SecurityImageModel
//
-// Created by Zach Fox on 2017-08-15
+// Created by Zach Fox on 2017-08-17
// Copyright 2017 High Fidelity, Inc.
//
// Distributed under the Apache License, Version 2.0.
@@ -39,4 +39,8 @@ ListModel {
sourcePath: "images/06gingerbread.jpg"
securityImageEnumValue: 6;
}
+
+ function getImagePathFromImageID(imageID) {
+ return (imageID ? root.get(imageID - 1).sourcePath : "");
+ }
}
diff --git a/interface/resources/qml/hifi/commerce/wallet/SecurityImageSelection.qml b/interface/resources/qml/hifi/commerce/wallet/SecurityImageSelection.qml
new file mode 100644
index 0000000000..7ab52b7551
--- /dev/null
+++ b/interface/resources/qml/hifi/commerce/wallet/SecurityImageSelection.qml
@@ -0,0 +1,98 @@
+//
+// SecurityImageSelection.qml
+// qml/hifi/commerce/wallet
+//
+// SecurityImageSelection
+//
+// Created by Zach Fox on 2017-08-17
+// Copyright 2017 High Fidelity, Inc.
+//
+// Distributed under the Apache License, Version 2.0.
+// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
+//
+
+import Hifi 1.0 as Hifi
+import QtQuick 2.5
+import QtQuick.Controls 1.4
+import "../../../styles-uit"
+import "../../../controls-uit" as HifiControlsUit
+import "../../../controls" as HifiControls
+
+// references XXX from root context
+
+Item {
+ HifiConstants { id: hifi; }
+
+ id: root;
+
+ Hifi.QmlCommerce {
+ id: commerce;
+ onSecurityImageResult: {
+ }
+ }
+
+ onVisibleChanged: {
+ if (visible) {
+ commerce.getSecurityImage();
+ }
+ }
+
+ SecurityImageModel {
+ id: gridModel;
+ }
+
+ GridView {
+ id: securityImageGrid;
+ clip: true;
+ // Anchors
+ anchors.fill: parent;
+ currentIndex: -1;
+ cellWidth: width / 3;
+ cellHeight: height / 2;
+ model: gridModel;
+ delegate: Item {
+ width: securityImageGrid.cellWidth;
+ height: securityImageGrid.cellHeight;
+ Item {
+ anchors.fill: parent;
+ Image {
+ width: parent.width - 8;
+ height: parent.height - 8;
+ source: sourcePath;
+ anchors.horizontalCenter: parent.horizontalCenter;
+ anchors.verticalCenter: parent.verticalCenter;
+ fillMode: Image.PreserveAspectFit;
+ mipmap: true;
+ }
+ }
+ MouseArea {
+ anchors.fill: parent;
+ propagateComposedEvents: false;
+ onClicked: {
+ securityImageGrid.currentIndex = index;
+ }
+ }
+ }
+ highlight: Rectangle {
+ width: securityImageGrid.cellWidth;
+ height: securityImageGrid.cellHeight;
+ color: hifi.colors.blueHighlight;
+ }
+ }
+
+ //
+ // FUNCTION DEFINITIONS START
+ //
+ signal sendToScript(var message);
+
+ function getImagePathFromImageID(imageID) {
+ return (imageID ? gridModel.getImagePathFromImageID(imageID) : "");
+ }
+
+ function getSelectedImageIndex() {
+ return gridModel.get(securityImageGrid.currentIndex).securityImageEnumValue;
+ }
+ //
+ // FUNCTION DEFINITIONS END
+ //
+}
diff --git a/interface/resources/qml/hifi/commerce/wallet/SecurityImageSelectionLightbox.qml b/interface/resources/qml/hifi/commerce/wallet/SecurityImageSelectionLightbox.qml
new file mode 100644
index 0000000000..d4b0b82ed3
--- /dev/null
+++ b/interface/resources/qml/hifi/commerce/wallet/SecurityImageSelectionLightbox.qml
@@ -0,0 +1,200 @@
+//
+// SecurityImageSelectionLightbox.qml
+// qml/hifi/commerce/wallet
+//
+// SecurityImageSelectionLightbox
+//
+// Created by Zach Fox on 2017-08-18
+// Copyright 2017 High Fidelity, Inc.
+//
+// Distributed under the Apache License, Version 2.0.
+// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
+//
+
+import Hifi 1.0 as Hifi
+import QtQuick 2.5
+import QtQuick.Controls 1.4
+import "../../../styles-uit"
+import "../../../controls-uit" as HifiControlsUit
+import "../../../controls" as HifiControls
+
+// references XXX from root context
+
+Rectangle {
+ HifiConstants { id: hifi; }
+
+ id: root;
+ property bool justSubmitted: false;
+ // Style
+ color: hifi.colors.baseGray;
+
+ onVisibleChanged: {
+ if (visible) {
+ root.resetSubmitButton();
+ }
+ }
+
+ Hifi.QmlCommerce {
+ id: commerce;
+
+ onSecurityImageResult: {
+ if (exists) { // Success submitting new security image
+ if (root.justSubmitted) {
+ root.resetSubmitButton();
+ root.visible = false;
+ root.justSubmitted = false;
+ }
+ } else if (root.justSubmitted) {
+ // Error submitting new security image.
+ root.resetSubmitButton();
+ root.justSubmitted = false;
+ }
+ }
+ }
+
+ //
+ // SECURITY IMAGE SELECTION START
+ //
+ Item {
+ id: securityImageContainer;
+ // Anchors
+ anchors.fill: parent;
+
+ Item {
+ id: securityImageTitle;
+ // Size
+ width: parent.width;
+ height: 50;
+ // Anchors
+ anchors.left: parent.left;
+ anchors.top: parent.top;
+
+ // Title Bar text
+ RalewaySemiBold {
+ text: "CHANGE SECURITY IMAGE";
+ // Text size
+ size: hifi.fontSizes.overlayTitle;
+ // Anchors
+ anchors.top: parent.top;
+ anchors.left: parent.left;
+ anchors.leftMargin: 16;
+ anchors.bottom: parent.bottom;
+ width: paintedWidth;
+ // Style
+ color: hifi.colors.faintGray;
+ // Alignment
+ horizontalAlignment: Text.AlignHLeft;
+ verticalAlignment: Text.AlignVCenter;
+ }
+ }
+
+ // Text below title bar
+ RalewaySemiBold {
+ id: securityImageTitleHelper;
+ text: "Choose a Security Picture:";
+ // Text size
+ size: 24;
+ // Anchors
+ anchors.top: securityImageTitle.bottom;
+ anchors.left: parent.left;
+ anchors.leftMargin: 16;
+ height: 50;
+ width: paintedWidth;
+ // Style
+ color: hifi.colors.faintGray;
+ // Alignment
+ horizontalAlignment: Text.AlignHLeft;
+ verticalAlignment: Text.AlignVCenter;
+ }
+
+ SecurityImageSelection {
+ id: securityImageSelection;
+ // Anchors
+ anchors.top: securityImageTitleHelper.bottom;
+ anchors.left: parent.left;
+ anchors.leftMargin: 16;
+ anchors.right: parent.right;
+ anchors.rightMargin: 16;
+ height: 280;
+ }
+
+ // Text below security images
+ RalewayRegular {
+ text: "Your security picture shows you that the service asking for your passphrase is authorized. You can change your secure picture at any time.";
+ // Text size
+ size: 18;
+ // Anchors
+ anchors.top: securityImageSelection.bottom;
+ anchors.topMargin: 40;
+ anchors.left: parent.left;
+ anchors.leftMargin: 16;
+ anchors.right: parent.right;
+ anchors.rightMargin: 16;
+ height: paintedHeight;
+ // Style
+ color: hifi.colors.faintGray;
+ wrapMode: Text.WordWrap;
+ // Alignment
+ horizontalAlignment: Text.AlignHLeft;
+ verticalAlignment: Text.AlignVCenter;
+ }
+
+ // Navigation Bar
+ Item {
+ id: securityImageNavBar;
+ // Size
+ width: parent.width;
+ height: 100;
+ // Anchors:
+ anchors.left: parent.left;
+ anchors.bottom: parent.bottom;
+
+ // "Cancel" button
+ HifiControlsUit.Button {
+ color: hifi.buttons.black;
+ colorScheme: hifi.colorSchemes.dark;
+ anchors.top: parent.top;
+ anchors.topMargin: 3;
+ anchors.bottom: parent.bottom;
+ anchors.bottomMargin: 3;
+ anchors.left: parent.left;
+ anchors.leftMargin: 20;
+ width: 100;
+ text: "Cancel"
+ onClicked: {
+ root.visible = false;
+ }
+ }
+
+ // "Submit" button
+ HifiControlsUit.Button {
+ id: securityImageSubmitButton;
+ color: hifi.buttons.black;
+ colorScheme: hifi.colorSchemes.dark;
+ anchors.top: parent.top;
+ anchors.topMargin: 3;
+ anchors.bottom: parent.bottom;
+ anchors.bottomMargin: 3;
+ anchors.right: parent.right;
+ anchors.rightMargin: 20;
+ width: 100;
+ text: "Submit";
+ onClicked: {
+ root.justSubmitted = true;
+ securityImageSubmitButton.text = "Submitting...";
+ securityImageSubmitButton.enabled = false;
+ var securityImagePath = securityImageSelection.getImagePathFromImageID(securityImageSelection.getSelectedImageIndex())
+ commerce.chooseSecurityImage(securityImagePath);
+ }
+ }
+ }
+ }
+ //
+ // SECURITY IMAGE SELECTION END
+ //
+
+ function resetSubmitButton() {
+ securityImageSubmitButton.enabled = true;
+ securityImageSubmitButton.text = "Submit";
+ }
+}
diff --git a/interface/resources/qml/hifi/commerce/wallet/SendMoney.qml b/interface/resources/qml/hifi/commerce/wallet/SendMoney.qml
new file mode 100644
index 0000000000..75334b1686
--- /dev/null
+++ b/interface/resources/qml/hifi/commerce/wallet/SendMoney.qml
@@ -0,0 +1,73 @@
+//
+// SendMoney.qml
+// qml/hifi/commerce/wallet
+//
+// SendMoney
+//
+// Created by Zach Fox on 2017-08-18
+// Copyright 2017 High Fidelity, Inc.
+//
+// Distributed under the Apache License, Version 2.0.
+// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
+//
+
+import Hifi 1.0 as Hifi
+import QtQuick 2.5
+import QtQuick.Controls 1.4
+import "../../../styles-uit"
+import "../../../controls-uit" as HifiControlsUit
+import "../../../controls" as HifiControls
+
+// references XXX from root context
+
+Item {
+ HifiConstants { id: hifi; }
+
+ id: root;
+
+ Hifi.QmlCommerce {
+ id: commerce;
+ }
+
+ // "Unavailable"
+ RalewayRegular {
+ text: "You currently cannot send money to other High Fidelity users.";
+ // Anchors
+ anchors.fill: parent;
+ // Text size
+ size: 24;
+ // Style
+ color: hifi.colors.faintGray;
+ wrapMode: Text.WordWrap;
+ // Alignment
+ horizontalAlignment: Text.AlignHCenter;
+ verticalAlignment: Text.AlignVCenter;
+ }
+
+ //
+ // FUNCTION DEFINITIONS START
+ //
+ //
+ // Function Name: fromScript()
+ //
+ // Relevant Variables:
+ // None
+ //
+ // Arguments:
+ // message: The message sent from the JavaScript.
+ // Messages are in format "{method, params}", like json-rpc.
+ //
+ // Description:
+ // Called when a message is received from a script.
+ //
+ function fromScript(message) {
+ switch (message.method) {
+ default:
+ console.log('Unrecognized message from wallet.js:', JSON.stringify(message));
+ }
+ }
+ signal sendSignalToWallet(var msg);
+ //
+ // FUNCTION DEFINITIONS END
+ //
+}
diff --git a/interface/resources/qml/hifi/commerce/wallet/Wallet.qml b/interface/resources/qml/hifi/commerce/wallet/Wallet.qml
new file mode 100644
index 0000000000..ac5d851bbc
--- /dev/null
+++ b/interface/resources/qml/hifi/commerce/wallet/Wallet.qml
@@ -0,0 +1,479 @@
+//
+// Wallet.qml
+// qml/hifi/commerce/wallet
+//
+// Wallet
+//
+// Created by Zach Fox on 2017-08-17
+// Copyright 2017 High Fidelity, Inc.
+//
+// Distributed under the Apache License, Version 2.0.
+// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
+//
+
+import Hifi 1.0 as Hifi
+import QtQuick 2.5
+import QtQuick.Controls 1.4
+import "../../../styles-uit"
+import "../../../controls-uit" as HifiControlsUit
+import "../../../controls" as HifiControls
+
+// references XXX from root context
+
+Rectangle {
+ HifiConstants { id: hifi; }
+
+ id: root;
+
+ property string activeView: "walletHome";
+
+ // Style
+ color: hifi.colors.baseGray;
+ Hifi.QmlCommerce {
+ id: commerce;
+
+ onSecurityImageResult: {
+ if (!exists) { // "If security image is not set up"
+ if (root.activeView !== "notSetUp") {
+ root.activeView = "notSetUp";
+ }
+ }
+ }
+
+ onKeyFilePathResult: {
+ if (path === "" && root.activeView !== "notSetUp") {
+ root.activeView = "notSetUp";
+ }
+ }
+ }
+
+ SecurityImageModel {
+ id: securityImageModel;
+ }
+
+ Connections {
+ target: walletSetupLightbox;
+ onSendSignalToWallet: {
+ if (msg.method === 'walletSetup_cancelClicked') {
+ walletSetupLightbox.visible = false;
+ } else if (msg.method === 'walletSetup_finished') {
+ root.activeView = "walletHome";
+ } else {
+ sendToScript(msg);
+ }
+ }
+ }
+ Connections {
+ target: notSetUp;
+ onSendSignalToWallet: {
+ if (msg.method === 'setUpClicked') {
+ walletSetupLightbox.visible = true;
+ }
+ }
+ }
+
+ Rectangle {
+ id: walletSetupLightboxContainer;
+ visible: walletSetupLightbox.visible || passphraseSelectionLightbox.visible || securityImageSelectionLightbox.visible;
+ z: 998;
+ anchors.fill: parent;
+ color: "black";
+ opacity: 0.5;
+ }
+ WalletSetupLightbox {
+ id: walletSetupLightbox;
+ visible: false;
+ z: 999;
+ anchors.centerIn: walletSetupLightboxContainer;
+ width: walletSetupLightboxContainer.width - 50;
+ height: walletSetupLightboxContainer.height - 50;
+ }
+ PassphraseSelectionLightbox {
+ id: passphraseSelectionLightbox;
+ visible: false;
+ z: 999;
+ anchors.centerIn: walletSetupLightboxContainer;
+ width: walletSetupLightboxContainer.width - 50;
+ height: walletSetupLightboxContainer.height - 50;
+ }
+ SecurityImageSelectionLightbox {
+ id: securityImageSelectionLightbox;
+ visible: false;
+ z: 999;
+ anchors.centerIn: walletSetupLightboxContainer;
+ width: walletSetupLightboxContainer.width - 50;
+ height: walletSetupLightboxContainer.height - 50;
+ }
+
+
+ //
+ // TITLE BAR START
+ //
+ Item {
+ id: titleBarContainer;
+ // Size
+ width: parent.width;
+ height: 50;
+ // Anchors
+ anchors.left: parent.left;
+ anchors.top: parent.top;
+
+ // Title Bar text
+ RalewaySemiBold {
+ id: titleBarText;
+ text: "WALLET";
+ // Text size
+ size: hifi.fontSizes.overlayTitle;
+ // Anchors
+ anchors.top: parent.top;
+ anchors.left: parent.left;
+ anchors.leftMargin: 16;
+ anchors.bottom: parent.bottom;
+ width: paintedWidth;
+ // Style
+ color: hifi.colors.faintGray;
+ // Alignment
+ horizontalAlignment: Text.AlignHLeft;
+ verticalAlignment: Text.AlignVCenter;
+ }
+
+ // Separator
+ HifiControlsUit.Separator {
+ anchors.left: parent.left;
+ anchors.right: parent.right;
+ anchors.bottom: parent.bottom;
+ }
+ }
+ //
+ // TITLE BAR END
+ //
+
+ //
+ // TAB CONTENTS START
+ //
+ NotSetUp {
+ id: notSetUp;
+ visible: root.activeView === "notSetUp";
+ anchors.top: titleBarContainer.bottom;
+ anchors.bottom: tabButtonsContainer.top;
+ anchors.left: parent.left;
+ anchors.right: parent.right;
+ }
+
+ WalletHome {
+ id: walletHome;
+ visible: root.activeView === "walletHome";
+ anchors.top: titleBarContainer.bottom;
+ anchors.topMargin: 16;
+ anchors.bottom: tabButtonsContainer.top;
+ anchors.bottomMargin: 16;
+ anchors.left: parent.left;
+ anchors.leftMargin: 16;
+ anchors.right: parent.right;
+ anchors.rightMargin: 16;
+ }
+
+ SendMoney {
+ id: sendMoney;
+ visible: root.activeView === "sendMoney";
+ anchors.top: titleBarContainer.bottom;
+ anchors.topMargin: 16;
+ anchors.bottom: tabButtonsContainer.top;
+ anchors.bottomMargin: 16;
+ anchors.left: parent.left;
+ anchors.leftMargin: 16;
+ anchors.right: parent.right;
+ anchors.rightMargin: 16;
+ }
+
+ Security {
+ id: security;
+ visible: root.activeView === "security";
+ anchors.top: titleBarContainer.bottom;
+ anchors.topMargin: 16;
+ anchors.bottom: tabButtonsContainer.top;
+ anchors.bottomMargin: 16;
+ anchors.left: parent.left;
+ anchors.leftMargin: 16;
+ anchors.right: parent.right;
+ anchors.rightMargin: 16;
+ }
+ Connections {
+ target: security;
+ onSendSignalToWallet: {
+ if (msg.method === 'walletSecurity_changePassphrase') {
+ passphraseSelectionLightbox.visible = true;
+ } else if (msg.method === 'walletSecurity_changeSecurityImage') {
+ securityImageSelectionLightbox.visible = true;
+ }
+ }
+ }
+
+ Help {
+ id: help;
+ visible: root.activeView === "help";
+ anchors.top: titleBarContainer.bottom;
+ anchors.topMargin: 16;
+ anchors.bottom: tabButtonsContainer.top;
+ anchors.bottomMargin: 16;
+ anchors.left: parent.left;
+ anchors.leftMargin: 16;
+ anchors.right: parent.right;
+ anchors.rightMargin: 16;
+ }
+
+
+ //
+ // TAB CONTENTS END
+ //
+
+ //
+ // TAB BUTTONS START
+ //
+ Item {
+ id: tabButtonsContainer;
+ property int numTabs: 5;
+ // Size
+ width: root.width;
+ height: 80;
+ // Anchors
+ anchors.left: parent.left;
+ anchors.bottom: parent.bottom;
+
+ // Separator
+ HifiControlsUit.Separator {
+ anchors.left: parent.left;
+ anchors.right: parent.right;
+ anchors.top: parent.top;
+ }
+
+ // "WALLET HOME" tab button
+ Rectangle {
+ id: walletHomeButtonContainer;
+ visible: !notSetUp.visible;
+ color: root.activeView === "walletHome" ? hifi.colors.blueAccent : hifi.colors.black;
+ anchors.top: parent.top;
+ anchors.left: parent.left;
+ anchors.bottom: parent.bottom;
+ width: parent.width / tabButtonsContainer.numTabs;
+
+ RalewaySemiBold {
+ text: "WALLET HOME";
+ // Text size
+ size: hifi.fontSizes.overlayTitle;
+ // Anchors
+ anchors.fill: parent;
+ anchors.leftMargin: 4;
+ anchors.rightMargin: 4;
+ // Style
+ color: hifi.colors.faintGray;
+ wrapMode: Text.WordWrap;
+ // Alignment
+ horizontalAlignment: Text.AlignHCenter;
+ verticalAlignment: Text.AlignVCenter;
+ }
+ MouseArea {
+ enabled: !walletSetupLightboxContainer.visible;
+ anchors.fill: parent;
+ hoverEnabled: enabled;
+ onClicked: {
+ root.activeView = "walletHome";
+ tabButtonsContainer.resetTabButtonColors();
+ }
+ onEntered: parent.color = hifi.colors.blueHighlight;
+ onExited: parent.color = root.activeView === "walletHome" ? hifi.colors.blueAccent : hifi.colors.black;
+ }
+
+ onVisibleChanged: {
+ if (visible) {
+ commerce.getSecurityImage();
+ commerce.balance();
+ }
+ }
+ }
+
+ // "SEND MONEY" tab button
+ Rectangle {
+ id: sendMoneyButtonContainer;
+ visible: !notSetUp.visible;
+ color: hifi.colors.black;
+ anchors.top: parent.top;
+ anchors.left: walletHomeButtonContainer.right;
+ anchors.bottom: parent.bottom;
+ width: parent.width / tabButtonsContainer.numTabs;
+
+ RalewaySemiBold {
+ text: "SEND MONEY";
+ // Text size
+ size: 14;
+ // Anchors
+ anchors.fill: parent;
+ anchors.leftMargin: 4;
+ anchors.rightMargin: 4;
+ // Style
+ color: hifi.colors.lightGray50;
+ wrapMode: Text.WordWrap;
+ // Alignment
+ horizontalAlignment: Text.AlignHCenter;
+ verticalAlignment: Text.AlignVCenter;
+ }
+ }
+
+ // "EXCHANGE MONEY" tab button
+ Rectangle {
+ id: exchangeMoneyButtonContainer;
+ visible: !notSetUp.visible;
+ color: hifi.colors.black;
+ anchors.top: parent.top;
+ anchors.left: sendMoneyButtonContainer.right;
+ anchors.bottom: parent.bottom;
+ width: parent.width / tabButtonsContainer.numTabs;
+
+ RalewaySemiBold {
+ text: "EXCHANGE MONEY";
+ // Text size
+ size: 14;
+ // Anchors
+ anchors.fill: parent;
+ anchors.leftMargin: 4;
+ anchors.rightMargin: 4;
+ // Style
+ color: hifi.colors.lightGray50;
+ wrapMode: Text.WordWrap;
+ // Alignment
+ horizontalAlignment: Text.AlignHCenter;
+ verticalAlignment: Text.AlignVCenter;
+ }
+ }
+
+ // "SECURITY" tab button
+ Rectangle {
+ id: securityButtonContainer;
+ visible: !notSetUp.visible;
+ color: root.activeView === "security" ? hifi.colors.blueAccent : hifi.colors.black;
+ anchors.top: parent.top;
+ anchors.left: exchangeMoneyButtonContainer.right;
+ anchors.bottom: parent.bottom;
+ width: parent.width / tabButtonsContainer.numTabs;
+
+ RalewaySemiBold {
+ text: "SECURITY";
+ // Text size
+ size: hifi.fontSizes.overlayTitle;
+ // Anchors
+ anchors.fill: parent;
+ anchors.leftMargin: 4;
+ anchors.rightMargin: 4;
+ // Style
+ color: hifi.colors.faintGray;
+ wrapMode: Text.WordWrap;
+ // Alignment
+ horizontalAlignment: Text.AlignHCenter;
+ verticalAlignment: Text.AlignVCenter;
+ }
+ MouseArea {
+ enabled: !walletSetupLightboxContainer.visible;
+ anchors.fill: parent;
+ hoverEnabled: enabled;
+ onClicked: {
+ root.activeView = "security";
+ tabButtonsContainer.resetTabButtonColors();
+ }
+ onEntered: parent.color = hifi.colors.blueHighlight;
+ onExited: parent.color = root.activeView === "security" ? hifi.colors.blueAccent : hifi.colors.black;
+ }
+
+ onVisibleChanged: {
+ if (visible) {
+ commerce.getSecurityImage();
+ commerce.getKeyFilePath();
+ }
+ }
+ }
+
+ // "HELP" tab button
+ Rectangle {
+ id: helpButtonContainer;
+ visible: !notSetUp.visible;
+ color: root.activeView === "help" ? hifi.colors.blueAccent : hifi.colors.black;
+ anchors.top: parent.top;
+ anchors.left: securityButtonContainer.right;
+ anchors.bottom: parent.bottom;
+ width: parent.width / tabButtonsContainer.numTabs;
+
+ RalewaySemiBold {
+ text: "HELP";
+ // Text size
+ size: hifi.fontSizes.overlayTitle;
+ // Anchors
+ anchors.fill: parent;
+ anchors.leftMargin: 4;
+ anchors.rightMargin: 4;
+ // Style
+ color: hifi.colors.faintGray;
+ wrapMode: Text.WordWrap;
+ // Alignment
+ horizontalAlignment: Text.AlignHCenter;
+ verticalAlignment: Text.AlignVCenter;
+ }
+ MouseArea {
+ enabled: !walletSetupLightboxContainer.visible;
+ anchors.fill: parent;
+ hoverEnabled: enabled;
+ onClicked: {
+ root.activeView = "help";
+ tabButtonsContainer.resetTabButtonColors();
+ }
+ onEntered: parent.color = hifi.colors.blueHighlight;
+ onExited: parent.color = root.activeView === "help" ? hifi.colors.blueAccent : hifi.colors.black;
+ }
+ }
+
+ function resetTabButtonColors() {
+ walletHomeButtonContainer.color = hifi.colors.black;
+ sendMoneyButtonContainer.color = hifi.colors.black;
+ securityButtonContainer.color = hifi.colors.black;
+ helpButtonContainer.color = hifi.colors.black;
+ if (root.activeView === "walletHome") {
+ walletHomeButtonContainer.color = hifi.colors.blueAccent;
+ } else if (root.activeView === "sendMoney") {
+ sendMoneyButtonContainer.color = hifi.colors.blueAccent;
+ } else if (root.activeView === "security") {
+ securityButtonContainer.color = hifi.colors.blueAccent;
+ } else if (root.activeView === "help") {
+ helpButtonContainer.color = hifi.colors.blueAccent;
+ }
+ }
+ }
+ //
+ // TAB BUTTONS END
+ //
+
+ //
+ // FUNCTION DEFINITIONS START
+ //
+ //
+ // Function Name: fromScript()
+ //
+ // Relevant Variables:
+ // None
+ //
+ // Arguments:
+ // message: The message sent from the JavaScript.
+ // Messages are in format "{method, params}", like json-rpc.
+ //
+ // Description:
+ // Called when a message is received from a script.
+ //
+ function fromScript(message) {
+ switch (message.method) {
+ default:
+ console.log('Unrecognized message from wallet.js:', JSON.stringify(message));
+ }
+ }
+ signal sendToScript(var message);
+
+ //
+ // FUNCTION DEFINITIONS END
+ //
+}
diff --git a/interface/resources/qml/hifi/commerce/wallet/WalletHome.qml b/interface/resources/qml/hifi/commerce/wallet/WalletHome.qml
new file mode 100644
index 0000000000..88f939d393
--- /dev/null
+++ b/interface/resources/qml/hifi/commerce/wallet/WalletHome.qml
@@ -0,0 +1,357 @@
+//
+// WalletHome.qml
+// qml/hifi/commerce/wallet
+//
+// WalletHome
+//
+// Created by Zach Fox on 2017-08-18
+// Copyright 2017 High Fidelity, Inc.
+//
+// Distributed under the Apache License, Version 2.0.
+// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
+//
+
+import Hifi 1.0 as Hifi
+import QtQuick 2.5
+import QtQuick.Controls 1.4
+import "../../../styles-uit"
+import "../../../controls-uit" as HifiControlsUit
+import "../../../controls" as HifiControls
+
+// references XXX from root context
+
+Item {
+ HifiConstants { id: hifi; }
+
+ id: root;
+
+ Hifi.QmlCommerce {
+ id: commerce;
+
+ onSecurityImageResult: {
+ if (exists) {
+ // just set the source again (to be sure the change was noticed)
+ securityImage.source = "";
+ securityImage.source = "image://security/securityImage";
+ }
+ }
+
+ onBalanceResult : {
+ balanceText.text = parseFloat(result.data.balance/100).toFixed(2);
+ }
+ }
+
+ SecurityImageModel {
+ id: securityImageModel;
+ }
+
+ Connections {
+ target: GlobalServices
+ onMyUsernameChanged: {
+ usernameText.text = Account.username;
+ }
+ }
+
+ // Username Text
+ RalewayRegular {
+ id: usernameText;
+ text: Account.username;
+ // Text size
+ size: 24;
+ // Style
+ color: hifi.colors.faintGray;
+ elide: Text.ElideRight;
+ // Anchors
+ anchors.top: securityImageContainer.top;
+ anchors.bottom: securityImageContainer.bottom;
+ anchors.left: parent.left;
+ anchors.right: hfcBalanceContainer.left;
+ }
+
+ // HFC Balance Container
+ Item {
+ id: hfcBalanceContainer;
+ // Anchors
+ anchors.top: securityImageContainer.top;
+ anchors.right: securityImageContainer.left;
+ anchors.rightMargin: 16;
+ width: 175;
+ height: 60;
+ Rectangle {
+ id: hfcBalanceField;
+ anchors.right: parent.right;
+ anchors.left: parent.left;
+ anchors.bottom: parent.bottom;
+ height: parent.height - 15;
+
+ // "HFC" balance label
+ RalewayRegular {
+ id: balanceLabel;
+ text: "HFC";
+ // Text size
+ size: 20;
+ // Anchors
+ anchors.top: parent.top;
+ anchors.bottom: parent.bottom;
+ anchors.right: hfcBalanceField.right;
+ anchors.rightMargin: 4;
+ width: paintedWidth;
+ // Style
+ color: hifi.colors.darkGray;
+ // Alignment
+ horizontalAlignment: Text.AlignRight;
+ verticalAlignment: Text.AlignVCenter;
+
+ onVisibleChanged: {
+ if (visible) {
+ commerce.balance();
+ }
+ }
+ }
+
+ // Balance Text
+ FiraSansRegular {
+ id: balanceText;
+ text: "--";
+ // Text size
+ size: 28;
+ // Anchors
+ anchors.top: parent.top;
+ anchors.bottom: parent.bottom;
+ anchors.left: parent.left;
+ anchors.right: balanceLabel.left;
+ anchors.rightMargin: 4;
+ // Style
+ color: hifi.colors.darkGray;
+ // Alignment
+ horizontalAlignment: Text.AlignRight;
+ verticalAlignment: Text.AlignVCenter;
+ }
+ }
+ // "balance" text above field
+ RalewayRegular {
+ text: "balance";
+ // Text size
+ size: 12;
+ // Anchors
+ anchors.top: parent.top;
+ anchors.bottom: hfcBalanceField.top;
+ anchors.bottomMargin: 4;
+ anchors.left: hfcBalanceField.left;
+ anchors.right: hfcBalanceField.right;
+ // Style
+ color: hifi.colors.faintGray;
+ // Alignment
+ horizontalAlignment: Text.AlignLeft;
+ verticalAlignment: Text.AlignVCenter;
+ }
+ }
+
+ // Security Image
+ Item {
+ id: securityImageContainer;
+ // Anchors
+ anchors.top: parent.top;
+ anchors.right: parent.right;
+ width: 75;
+ height: childrenRect.height;
+
+ onVisibleChanged: {
+ if (visible) {
+ commerce.getSecurityImage();
+ }
+ }
+
+ Image {
+ id: securityImage;
+ // Anchors
+ anchors.top: parent.top;
+ anchors.horizontalCenter: parent.horizontalCenter;
+ height: parent.width - 10;
+ width: height;
+ fillMode: Image.PreserveAspectFit;
+ mipmap: true;
+ cache: false;
+ source: "image://security/securityImage";
+ }
+ // "Security picture" text below pic
+ RalewayRegular {
+ text: "security picture";
+ // Text size
+ size: 12;
+ // Anchors
+ anchors.top: securityImage.bottom;
+ anchors.topMargin: 4;
+ anchors.left: securityImageContainer.left;
+ anchors.right: securityImageContainer.right;
+ height: paintedHeight;
+ // Style
+ color: hifi.colors.faintGray;
+ // Alignment
+ horizontalAlignment: Text.AlignHCenter;
+ verticalAlignment: Text.AlignVCenter;
+ }
+ }
+
+ // Recent Activity
+ Item {
+ id: recentActivityContainer;
+ anchors.top: securityImageContainer.bottom;
+ anchors.topMargin: 8;
+ anchors.left: parent.left;
+ anchors.right: parent.right;
+ anchors.bottom: homeMessage.visible ? homeMessage.top : root.bottom;
+ anchors.bottomMargin: 10;
+
+ RalewayRegular {
+ id: recentActivityText;
+ text: "Recent Activity";
+ // Anchors
+ anchors.top: parent.top;
+ anchors.left: parent.left;
+ anchors.right: parent.right;
+ height: 30;
+ // Text size
+ size: 22;
+ // Style
+ color: hifi.colors.faintGray;
+ }
+
+ Rectangle {
+ id: transactionHistory;
+ anchors.top: recentActivityText.bottom;
+ anchors.topMargin: 4;
+ anchors.bottom: toggleFullHistoryButton.top;
+ anchors.bottomMargin: 8;
+ anchors.left: parent.left;
+ anchors.right: parent.right;
+
+ // some placeholder stuff
+ RalewayRegular {
+ text: homeMessage.visible ? "you CANNOT scroll through this." : "you CAN scroll through this";
+ // Text size
+ size: 16;
+ // Anchors
+ anchors.fill: parent;
+ // Style
+ color: hifi.colors.darkGray;
+ // Alignment
+ horizontalAlignment: Text.AlignHCenter;
+ verticalAlignment: Text.AlignVCenter;
+ }
+ }
+
+ HifiControlsUit.Button {
+ id: toggleFullHistoryButton;
+ color: hifi.buttons.black;
+ colorScheme: hifi.colorSchemes.dark;
+ anchors.bottom: parent.bottom;
+ anchors.right: parent.right;
+ width: 250;
+ height: 40;
+ text: homeMessage.visible ? "See Full Transaction History" : "Collapse Transaction History";
+ onClicked: {
+ if (homeMessage.visible) {
+ homeMessage.visible = false;
+ } else {
+ homeMessage.visible = true;
+ }
+ }
+ }
+ }
+
+ // Item for "messages" - like "Welcome"
+ Item {
+ id: homeMessage;
+ anchors.bottom: parent.bottom;
+ anchors.left: parent.left;
+ anchors.leftMargin: 20;
+ anchors.right: parent.right;
+ anchors.rightMargin: 20;
+ height: childrenRect.height;
+
+ RalewayRegular {
+ id: messageText;
+ text: "Welcome! Let's get you some spending money.
" +
+ "Now that your account is all set up, click the button below to request your starter money. " +
+ "A robot will promptly review your request and put money into your account.";
+ // Text size
+ size: 16;
+ // Anchors
+ anchors.top: parent.top;
+ anchors.left: parent.left;
+ anchors.right: parent.right;
+ height: 130;
+ // Style
+ color: hifi.colors.faintGray;
+ wrapMode: Text.WordWrap;
+ // Alignment
+ horizontalAlignment: Text.AlignHLeft;
+ verticalAlignment: Text.AlignVCenter;
+ }
+
+ Item {
+ id: homeMessageButtons;
+ anchors.top: messageText.bottom;
+ anchors.topMargin: 4;
+ anchors.left: parent.left;
+ anchors.right: parent.right;
+ height: 40;
+ HifiControlsUit.Button {
+ id: noThanksButton;
+ color: hifi.buttons.black;
+ colorScheme: hifi.colorSchemes.dark;
+ anchors.top: parent.top;
+ anchors.bottom: parent.bottom;
+ anchors.left: parent.left;
+ width: 100;
+ text: "No Thanks"
+ onClicked: {
+ messageText.text = "Okay...weird. Who doesn't like free money? If you change your mind, too bad. Sorry."
+ homeMessageButtons.visible = false;
+ }
+ }
+ HifiControlsUit.Button {
+ id: freeMoneyButton;
+ color: hifi.buttons.black;
+ colorScheme: hifi.colorSchemes.dark;
+ anchors.top: parent.top;
+ anchors.bottom: parent.bottom;
+ anchors.right: parent.right;
+ width: 210;
+ text: "Free Money Please"
+ onClicked: {
+ messageText.text = "Go, MoneyRobots, Go!"
+ homeMessageButtons.visible = false;
+ }
+ }
+ }
+ }
+
+ //
+ // FUNCTION DEFINITIONS START
+ //
+ //
+ // Function Name: fromScript()
+ //
+ // Relevant Variables:
+ // None
+ //
+ // Arguments:
+ // message: The message sent from the JavaScript.
+ // Messages are in format "{method, params}", like json-rpc.
+ //
+ // Description:
+ // Called when a message is received from a script.
+ //
+ function fromScript(message) {
+ switch (message.method) {
+ default:
+ console.log('Unrecognized message from wallet.js:', JSON.stringify(message));
+ }
+ }
+ signal sendSignalToWallet(var msg);
+ //
+ // FUNCTION DEFINITIONS END
+ //
+}
diff --git a/interface/resources/qml/hifi/commerce/wallet/WalletSetupLightbox.qml b/interface/resources/qml/hifi/commerce/wallet/WalletSetupLightbox.qml
new file mode 100644
index 0000000000..474322062e
--- /dev/null
+++ b/interface/resources/qml/hifi/commerce/wallet/WalletSetupLightbox.qml
@@ -0,0 +1,633 @@
+//
+// WalletSetupLightbox.qml
+// qml/hifi/commerce/wallet
+//
+// WalletSetupLightbox
+//
+// Created by Zach Fox on 2017-08-17
+// Copyright 2017 High Fidelity, Inc.
+//
+// Distributed under the Apache License, Version 2.0.
+// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
+//
+
+import Hifi 1.0 as Hifi
+import QtQuick 2.5
+import QtQuick.Controls 1.4
+import "../../../styles-uit"
+import "../../../controls-uit" as HifiControlsUit
+import "../../../controls" as HifiControls
+
+// references XXX from root context
+
+Rectangle {
+ HifiConstants { id: hifi; }
+
+ id: root;
+ property string lastPage: "login";
+ // Style
+ color: hifi.colors.baseGray;
+
+ Hifi.QmlCommerce {
+ id: commerce;
+
+ onLoginStatusResult: {
+ if (isLoggedIn) {
+ securityImageContainer.visible = true;
+ } else {
+ loginPageContainer.visible = true;
+ }
+ }
+
+ onSecurityImageResult: {
+ if (!exists && root.lastPage === "securityImage") {
+ // ERROR! Invalid security image.
+ securityImageContainer.visible = true;
+ choosePassphraseContainer.visible = false;
+ }
+ }
+
+ onPassphraseSetupStatusResult: {
+ securityImageContainer.visible = false;
+ if (passphraseIsSetup) {
+ privateKeysReadyContainer.visible = true;
+ } else {
+ choosePassphraseContainer.visible = true;
+ }
+ }
+
+ onKeyFilePathResult: {
+ if (path !== "") {
+ keyFilePath.text = path;
+ }
+ }
+ }
+
+ //
+ // LOGIN PAGE START
+ //
+ Item {
+ id: loginPageContainer;
+ visible: false;
+ // Anchors
+ anchors.fill: parent;
+
+ Component.onCompleted: {
+ commerce.getLoginStatus();
+ }
+
+ Item {
+ id: loginTitle;
+ // Size
+ width: parent.width;
+ height: 50;
+ // Anchors
+ anchors.left: parent.left;
+ anchors.top: parent.top;
+
+ // Title Bar text
+ RalewaySemiBold {
+ text: "WALLET SETUP - LOGIN";
+ // Text size
+ size: hifi.fontSizes.overlayTitle;
+ // Anchors
+ anchors.top: parent.top;
+ anchors.left: parent.left;
+ anchors.leftMargin: 16;
+ anchors.bottom: parent.bottom;
+ width: paintedWidth;
+ // Style
+ color: hifi.colors.faintGray;
+ // Alignment
+ horizontalAlignment: Text.AlignHLeft;
+ verticalAlignment: Text.AlignVCenter;
+ }
+ }
+
+ // Text below title bar
+ RalewaySemiBold {
+ id: loginTitleHelper;
+ text: "Please Log In to High Fidelity";
+ // Text size
+ size: 24;
+ // Anchors
+ anchors.top: loginTitle.bottom;
+ anchors.left: parent.left;
+ anchors.leftMargin: 16;
+ anchors.right: parent.right;
+ anchors.rightMargin: 16;
+ height: 50;
+ // Style
+ color: hifi.colors.faintGray;
+ // Alignment
+ horizontalAlignment: Text.AlignHLeft;
+ verticalAlignment: Text.AlignVCenter;
+ }
+
+ // Text below helper text
+ RalewaySemiBold {
+ id: loginDetailText;
+ text: "To set up your wallet, you must first log in to High Fidelity.";
+ // Text size
+ size: 18;
+ // Anchors
+ anchors.top: loginTitleHelper.bottom;
+ anchors.topMargin: 25;
+ anchors.left: parent.left;
+ anchors.leftMargin: 16;
+ anchors.right: parent.right;
+ anchors.rightMargin: 16;
+ height: 50;
+ // Style
+ color: hifi.colors.faintGray;
+ wrapMode: Text.WordWrap;
+ // Alignment
+ horizontalAlignment: Text.AlignHLeft;
+ verticalAlignment: Text.AlignVCenter;
+ }
+
+ // "Cancel" button
+ HifiControlsUit.Button {
+ color: hifi.buttons.black;
+ colorScheme: hifi.colorSchemes.dark;
+ anchors.top: loginDetailText.bottom;
+ anchors.topMargin: 25;
+ anchors.left: parent.left;
+ anchors.leftMargin: 16;
+ width: 150;
+ height: 50;
+ text: "Log In"
+ onClicked: {
+ sendSignalToWallet({method: 'walletSetup_loginClicked'});
+ }
+ }
+
+ // Navigation Bar
+ Item {
+ // Size
+ width: parent.width;
+ height: 100;
+ // Anchors:
+ anchors.left: parent.left;
+ anchors.bottom: parent.bottom;
+
+ // "Cancel" button
+ HifiControlsUit.Button {
+ color: hifi.buttons.black;
+ colorScheme: hifi.colorSchemes.dark;
+ anchors.top: parent.top;
+ anchors.topMargin: 3;
+ anchors.bottom: parent.bottom;
+ anchors.bottomMargin: 3;
+ anchors.left: parent.left;
+ anchors.leftMargin: 20;
+ width: 100;
+ text: "Cancel"
+ onClicked: {
+ sendSignalToWallet({method: 'walletSetup_cancelClicked'});
+ }
+ }
+ }
+ }
+ //
+ // LOGIN PAGE END
+ //
+
+ //
+ // SECURITY IMAGE SELECTION START
+ //
+ Item {
+ id: securityImageContainer;
+ visible: false;
+ // Anchors
+ anchors.fill: parent;
+
+ onVisibleChanged: {
+ if (visible) {
+ commerce.getSecurityImage();
+ }
+ }
+
+ Item {
+ id: securityImageTitle;
+ // Size
+ width: parent.width;
+ height: 50;
+ // Anchors
+ anchors.left: parent.left;
+ anchors.top: parent.top;
+
+ // Title Bar text
+ RalewaySemiBold {
+ text: "WALLET SETUP - STEP 1 OF 3";
+ // Text size
+ size: hifi.fontSizes.overlayTitle;
+ // Anchors
+ anchors.top: parent.top;
+ anchors.left: parent.left;
+ anchors.leftMargin: 16;
+ anchors.bottom: parent.bottom;
+ width: paintedWidth;
+ // Style
+ color: hifi.colors.faintGray;
+ // Alignment
+ horizontalAlignment: Text.AlignHLeft;
+ verticalAlignment: Text.AlignVCenter;
+ }
+ }
+
+ // Text below title bar
+ RalewaySemiBold {
+ id: securityImageTitleHelper;
+ text: "Choose a Security Picture:";
+ // Text size
+ size: 24;
+ // Anchors
+ anchors.top: securityImageTitle.bottom;
+ anchors.left: parent.left;
+ anchors.leftMargin: 16;
+ height: 50;
+ width: paintedWidth;
+ // Style
+ color: hifi.colors.faintGray;
+ // Alignment
+ horizontalAlignment: Text.AlignHLeft;
+ verticalAlignment: Text.AlignVCenter;
+ }
+
+ SecurityImageSelection {
+ id: securityImageSelection;
+ // Anchors
+ anchors.top: securityImageTitleHelper.bottom;
+ anchors.left: parent.left;
+ anchors.leftMargin: 16;
+ anchors.right: parent.right;
+ anchors.rightMargin: 16;
+ height: 280;
+ }
+
+ // Text below security images
+ RalewayRegular {
+ text: "Your security picture shows you that the service asking for your passphrase is authorized. You can change your secure picture at any time.";
+ // Text size
+ size: 18;
+ // Anchors
+ anchors.top: securityImageSelection.bottom;
+ anchors.topMargin: 40;
+ anchors.left: parent.left;
+ anchors.leftMargin: 16;
+ anchors.right: parent.right;
+ anchors.rightMargin: 16;
+ height: paintedHeight;
+ // Style
+ color: hifi.colors.faintGray;
+ wrapMode: Text.WordWrap;
+ // Alignment
+ horizontalAlignment: Text.AlignHLeft;
+ verticalAlignment: Text.AlignVCenter;
+ }
+
+ // Navigation Bar
+ Item {
+ // Size
+ width: parent.width;
+ height: 100;
+ // Anchors:
+ anchors.left: parent.left;
+ anchors.bottom: parent.bottom;
+
+ // "Cancel" button
+ HifiControlsUit.Button {
+ color: hifi.buttons.black;
+ colorScheme: hifi.colorSchemes.dark;
+ anchors.top: parent.top;
+ anchors.topMargin: 3;
+ anchors.bottom: parent.bottom;
+ anchors.bottomMargin: 3;
+ anchors.left: parent.left;
+ anchors.leftMargin: 20;
+ width: 100;
+ text: "Cancel"
+ onClicked: {
+ sendSignalToWallet({method: 'walletSetup_cancelClicked'});
+ }
+ }
+
+ // "Next" button
+ HifiControlsUit.Button {
+ color: hifi.buttons.black;
+ colorScheme: hifi.colorSchemes.dark;
+ anchors.top: parent.top;
+ anchors.topMargin: 3;
+ anchors.bottom: parent.bottom;
+ anchors.bottomMargin: 3;
+ anchors.right: parent.right;
+ anchors.rightMargin: 20;
+ width: 100;
+ text: "Next";
+ onClicked: {
+ root.lastPage = "securityImage";
+ var securityImagePath = securityImageSelection.getImagePathFromImageID(securityImageSelection.getSelectedImageIndex())
+ commerce.chooseSecurityImage(securityImagePath);
+ securityImageContainer.visible = false;
+ choosePassphraseContainer.visible = true;
+ }
+ }
+ }
+ }
+ //
+ // SECURITY IMAGE SELECTION END
+ //
+
+ //
+ // SECURE PASSPHRASE SELECTION START
+ //
+ Item {
+ id: choosePassphraseContainer;
+ visible: false;
+ // Anchors
+ anchors.fill: parent;
+
+ onVisibleChanged: {
+ if (visible) {
+ commerce.getPassphraseSetupStatus();
+ }
+ }
+
+ Item {
+ id: passphraseTitle;
+ // Size
+ width: parent.width;
+ height: 50;
+ // Anchors
+ anchors.left: parent.left;
+ anchors.top: parent.top;
+
+ // Title Bar text
+ RalewaySemiBold {
+ text: "WALLET SETUP - STEP 2 OF 3";
+ // Text size
+ size: hifi.fontSizes.overlayTitle;
+ // Anchors
+ anchors.top: parent.top;
+ anchors.left: parent.left;
+ anchors.leftMargin: 16;
+ anchors.bottom: parent.bottom;
+ width: paintedWidth;
+ // Style
+ color: hifi.colors.faintGray;
+ // Alignment
+ horizontalAlignment: Text.AlignHLeft;
+ verticalAlignment: Text.AlignVCenter;
+ }
+ }
+
+ // Text below title bar
+ RalewaySemiBold {
+ id: passphraseTitleHelper;
+ text: "Choose a Secure Passphrase";
+ // Text size
+ size: 24;
+ // Anchors
+ anchors.top: passphraseTitle.bottom;
+ anchors.left: parent.left;
+ anchors.leftMargin: 16;
+ anchors.right: parent.right;
+ anchors.rightMargin: 16;
+ height: 50;
+ // Style
+ color: hifi.colors.faintGray;
+ // Alignment
+ horizontalAlignment: Text.AlignHLeft;
+ verticalAlignment: Text.AlignVCenter;
+ }
+
+ PassphraseSelection {
+ id: passphraseSelection;
+ anchors.top: passphraseTitleHelper.bottom;
+ anchors.topMargin: 30;
+ anchors.left: parent.left;
+ anchors.right: parent.right;
+ anchors.bottom: passphraseNavBar.top;
+ }
+
+ // Navigation Bar
+ Item {
+ id: passphraseNavBar;
+ // Size
+ width: parent.width;
+ height: 100;
+ // Anchors:
+ anchors.left: parent.left;
+ anchors.bottom: parent.bottom;
+
+ // "Back" button
+ HifiControlsUit.Button {
+ color: hifi.buttons.black;
+ colorScheme: hifi.colorSchemes.dark;
+ anchors.top: parent.top;
+ anchors.topMargin: 3;
+ anchors.bottom: parent.bottom;
+ anchors.bottomMargin: 3;
+ anchors.left: parent.left;
+ anchors.leftMargin: 20;
+ width: 100;
+ text: "Back"
+ onClicked: {
+ root.lastPage = "choosePassphrase";
+ choosePassphraseContainer.visible = false;
+ securityImageContainer.visible = true;
+ }
+ }
+
+ // "Next" button
+ HifiControlsUit.Button {
+ id: passphrasePageNextButton;
+ color: hifi.buttons.black;
+ colorScheme: hifi.colorSchemes.dark;
+ anchors.top: parent.top;
+ anchors.topMargin: 3;
+ anchors.bottom: parent.bottom;
+ anchors.bottomMargin: 3;
+ anchors.right: parent.right;
+ anchors.rightMargin: 20;
+ width: 100;
+ text: "Next";
+ onClicked: {
+ if (passphraseSelection.validateAndSubmitPassphrase()) {
+ root.lastPage = "passphrase";
+ choosePassphraseContainer.visible = false;
+ privateKeysReadyContainer.visible = true;
+ commerce.balance(); // Do this here so that keys are generated. Order might change as backend changes?
+ }
+ }
+ }
+ }
+ }
+ //
+ // SECURE PASSPHRASE SELECTION END
+ //
+
+ //
+ // PRIVATE KEYS READY START
+ //
+ Item {
+ id: privateKeysReadyContainer;
+ visible: false;
+ // Anchors
+ anchors.fill: parent;
+
+ Item {
+ id: keysReadyTitle;
+ // Size
+ width: parent.width;
+ height: 50;
+ // Anchors
+ anchors.left: parent.left;
+ anchors.top: parent.top;
+
+ // Title Bar text
+ RalewaySemiBold {
+ text: "WALLET SETUP - STEP 3 OF 3";
+ // Text size
+ size: hifi.fontSizes.overlayTitle;
+ // Anchors
+ anchors.top: parent.top;
+ anchors.left: parent.left;
+ anchors.leftMargin: 16;
+ anchors.bottom: parent.bottom;
+ width: paintedWidth;
+ // Style
+ color: hifi.colors.faintGray;
+ // Alignment
+ horizontalAlignment: Text.AlignHLeft;
+ verticalAlignment: Text.AlignVCenter;
+ }
+ }
+
+ // Text below title bar
+ RalewaySemiBold {
+ id: keysReadyTitleHelper;
+ text: "Your Private Keys are Ready";
+ // Text size
+ size: 24;
+ // Anchors
+ anchors.top: keysReadyTitle.bottom;
+ anchors.left: parent.left;
+ anchors.leftMargin: 16;
+ anchors.right: parent.right;
+ anchors.rightMargin: 16;
+ height: 50;
+ // Style
+ color: hifi.colors.faintGray;
+ // Alignment
+ horizontalAlignment: Text.AlignHLeft;
+ verticalAlignment: Text.AlignVCenter;
+ }
+
+ // Text below checkbox
+ RalewayRegular {
+ id: explanationText;
+ text: "Your money and purchases are secured with private keys that only you have access to. " +
+ "If they are lost, you will not be able to access your money or purchases.
" +
+ "To protect your privacy, High Fidelity has no access to your private keys and cannot " +
+ "recover them for any reason.
To safeguard your private keys, backup this file on a regular basis:";
+ // Text size
+ size: 16;
+ // Anchors
+ anchors.top: keysReadyTitleHelper.bottom;
+ anchors.topMargin: 16;
+ anchors.left: parent.left;
+ anchors.leftMargin: 16;
+ anchors.right: parent.right;
+ anchors.rightMargin: 16;
+ height: paintedHeight;
+ // Style
+ color: hifi.colors.faintGray;
+ wrapMode: Text.WordWrap;
+ // Alignment
+ horizontalAlignment: Text.AlignHLeft;
+ verticalAlignment: Text.AlignVCenter;
+ }
+
+ HifiControlsUit.TextField {
+ id: keyFilePath;
+ anchors.top: explanationText.bottom;
+ anchors.topMargin: 10;
+ anchors.left: parent.left;
+ anchors.leftMargin: 16;
+ anchors.right: clipboardButton.left;
+ height: 40;
+ readOnly: true;
+
+ onVisibleChanged: {
+ if (visible) {
+ commerce.getKeyFilePath();
+ }
+ }
+ }
+ HifiControlsUit.Button {
+ id: clipboardButton;
+ color: hifi.buttons.black;
+ colorScheme: hifi.colorSchemes.dark;
+ anchors.right: parent.right;
+ anchors.rightMargin: 16;
+ anchors.top: keyFilePath.top;
+ anchors.bottom: keyFilePath.bottom;
+ width: height;
+ HiFiGlyphs {
+ text: hifi.glyphs.question;
+ // Size
+ size: parent.height*1.3;
+ // Anchors
+ anchors.fill: parent;
+ // Style
+ horizontalAlignment: Text.AlignHCenter;
+ color: enabled ? hifi.colors.white : hifi.colors.faintGray;
+ }
+
+ onClicked: {
+ Window.copyToClipboard(keyFilePath.text);
+ }
+ }
+
+ // Navigation Bar
+ Item {
+ // Size
+ width: parent.width;
+ height: 100;
+ // Anchors:
+ anchors.left: parent.left;
+ anchors.bottom: parent.bottom;
+ // "Next" button
+ HifiControlsUit.Button {
+ id: keysReadyPageNextButton;
+ color: hifi.buttons.black;
+ colorScheme: hifi.colorSchemes.dark;
+ anchors.top: parent.top;
+ anchors.topMargin: 3;
+ anchors.bottom: parent.bottom;
+ anchors.bottomMargin: 3;
+ anchors.right: parent.right;
+ anchors.rightMargin: 20;
+ width: 100;
+ text: "Finish";
+ onClicked: {
+ root.visible = false;
+ sendSignalToWallet({method: 'walletSetup_finished'});
+ }
+ }
+ }
+ }
+ //
+ // PRIVATE KEYS READY END
+ //
+
+ //
+ // FUNCTION DEFINITIONS START
+ //
+ signal sendSignalToWallet(var msg);
+ //
+ // FUNCTION DEFINITIONS END
+ //
+}
diff --git a/interface/resources/qml/hifi/commerce/wallet/images/01cat.jpg b/interface/resources/qml/hifi/commerce/wallet/images/01cat.jpg
new file mode 100644
index 0000000000..6e7897cb82
Binary files /dev/null and b/interface/resources/qml/hifi/commerce/wallet/images/01cat.jpg differ
diff --git a/interface/resources/qml/hifi/commerce/wallet/images/02car.jpg b/interface/resources/qml/hifi/commerce/wallet/images/02car.jpg
new file mode 100644
index 0000000000..5dd8091e57
Binary files /dev/null and b/interface/resources/qml/hifi/commerce/wallet/images/02car.jpg differ
diff --git a/interface/resources/qml/hifi/commerce/wallet/images/03dog.jpg b/interface/resources/qml/hifi/commerce/wallet/images/03dog.jpg
new file mode 100644
index 0000000000..4a85b80c0c
Binary files /dev/null and b/interface/resources/qml/hifi/commerce/wallet/images/03dog.jpg differ
diff --git a/interface/resources/qml/hifi/commerce/wallet/images/04stars.jpg b/interface/resources/qml/hifi/commerce/wallet/images/04stars.jpg
new file mode 100644
index 0000000000..8f2bf62f83
Binary files /dev/null and b/interface/resources/qml/hifi/commerce/wallet/images/04stars.jpg differ
diff --git a/interface/resources/qml/hifi/commerce/wallet/images/05plane.jpg b/interface/resources/qml/hifi/commerce/wallet/images/05plane.jpg
new file mode 100644
index 0000000000..6504459d8b
Binary files /dev/null and b/interface/resources/qml/hifi/commerce/wallet/images/05plane.jpg differ
diff --git a/interface/resources/qml/hifi/commerce/wallet/images/06gingerbread.jpg b/interface/resources/qml/hifi/commerce/wallet/images/06gingerbread.jpg
new file mode 100644
index 0000000000..54c37faa2f
Binary files /dev/null and b/interface/resources/qml/hifi/commerce/wallet/images/06gingerbread.jpg differ
diff --git a/interface/src/Application.cpp b/interface/src/Application.cpp
index d3b547a54c..affbf83081 100644
--- a/interface/src/Application.cpp
+++ b/interface/src/Application.cpp
@@ -183,6 +183,7 @@
#include "ui/UpdateDialog.h"
#include "ui/overlays/Overlays.h"
#include "ui/DomainConnectionModel.h"
+#include "ui/ImageProvider.h"
#include "Util.h"
#include "InterfaceParentFinder.h"
#include "ui/OctreeStatsProvider.h"
@@ -2165,6 +2166,9 @@ void Application::initializeUi() {
qApp->quit();
});
+ // register the pixmap image provider (used only for security image, for now)
+ engine->addImageProvider(ImageProvider::PROVIDER_NAME, new ImageProvider());
+
setupPreferences();
// For some reason there is already an "Application" object in the QML context,
@@ -3757,7 +3761,7 @@ bool Application::shouldPaint() {
(float)paintDelaySamples / paintDelayUsecs << "us";
}
#endif
-
+
// Throttle if requested
if (displayPlugin->isThrottled() && (_lastTimeUpdated.elapsed() < THROTTLED_SIM_FRAME_PERIOD_MS)) {
return false;
@@ -6297,7 +6301,7 @@ bool Application::askToReplaceDomainContent(const QString& url) {
OffscreenUi::warning("Unable to replace content", "You do not have permissions to replace domain content",
QMessageBox::Ok, QMessageBox::Ok);
}
- QJsonObject messageProperties = {
+ QJsonObject messageProperties = {
{ "status", methodDetails },
{ "content_set_url", url }
};
@@ -6380,7 +6384,7 @@ void Application::showAssetServerWidget(QString filePath) {
void Application::addAssetToWorldFromURL(QString url) {
qInfo(interfaceapp) << "Download model and add to world from" << url;
-
+
QString filename;
if (url.contains("filename")) {
filename = url.section("filename=", 1, 1); // Filename is in "?filename=" parameter at end of URL.
diff --git a/interface/src/commerce/QmlCommerce.cpp b/interface/src/commerce/QmlCommerce.cpp
index b3d6a5e67a..f1e9fa139e 100644
--- a/interface/src/commerce/QmlCommerce.cpp
+++ b/interface/src/commerce/QmlCommerce.cpp
@@ -14,6 +14,7 @@
#include "DependencyManager.h"
#include "Ledger.h"
#include "Wallet.h"
+#include
HIFI_QML_DEF(QmlCommerce)
@@ -24,6 +25,7 @@ QmlCommerce::QmlCommerce(QQuickItem* parent) : OffscreenQmlDialog(parent) {
connect(ledger.data(), &Ledger::balanceResult, this, &QmlCommerce::balanceResult);
connect(ledger.data(), &Ledger::inventoryResult, this, &QmlCommerce::inventoryResult);
connect(wallet.data(), &Wallet::securityImageResult, this, &QmlCommerce::securityImageResult);
+ connect(wallet.data(), &Wallet::keyFilePathResult, this, &QmlCommerce::keyFilePathResult);
}
void QmlCommerce::buy(const QString& assetId, int cost, const QString& buyerUsername) {
@@ -50,11 +52,24 @@ void QmlCommerce::inventory() {
ledger->inventory(wallet->listPublicKeys());
}
-void QmlCommerce::chooseSecurityImage(uint imageID) {
+void QmlCommerce::chooseSecurityImage(const QString& imageFile) {
auto wallet = DependencyManager::get();
- wallet->chooseSecurityImage(imageID);
+ wallet->chooseSecurityImage(imageFile);
}
void QmlCommerce::getSecurityImage() {
auto wallet = DependencyManager::get();
wallet->getSecurityImage();
-}
\ No newline at end of file
+}
+void QmlCommerce::getLoginStatus() {
+ emit loginStatusResult(DependencyManager::get()->isLoggedIn());
+}
+void QmlCommerce::setPassphrase(const QString& passphrase) {
+ emit passphraseSetupStatusResult(true);
+}
+void QmlCommerce::getPassphraseSetupStatus() {
+ emit passphraseSetupStatusResult(false);
+}
+void QmlCommerce::getKeyFilePath() {
+ auto wallet = DependencyManager::get();
+ wallet->getKeyFilePath();
+}
diff --git a/interface/src/commerce/QmlCommerce.h b/interface/src/commerce/QmlCommerce.h
index 4112ab7177..d7a892cf1f 100644
--- a/interface/src/commerce/QmlCommerce.h
+++ b/interface/src/commerce/QmlCommerce.h
@@ -18,6 +18,8 @@
#include
#include
+#include
+
class QmlCommerce : public OffscreenQmlDialog {
Q_OBJECT
HIFI_QML_DECL
@@ -27,18 +29,25 @@ public:
signals:
void buyResult(QJsonObject result);
- // Balance and Inventory are NOT properties, because QML can't change them (without risk of failure), and
+ // Balance and Inventory are NOT properties, because QML can't change them (without risk of failure), and
// because we can't scalably know of out-of-band changes (e.g., another machine interacting with the block chain).
void balanceResult(QJsonObject result);
void inventoryResult(QJsonObject result);
- void securityImageResult(uint imageID);
+ void securityImageResult(bool exists);
+ void loginStatusResult(bool isLoggedIn);
+ void passphraseSetupStatusResult(bool passphraseIsSetup);
+ void keyFilePathResult(const QString& path);
protected:
Q_INVOKABLE void buy(const QString& assetId, int cost, const QString& buyerUsername = "");
Q_INVOKABLE void balance();
Q_INVOKABLE void inventory();
- Q_INVOKABLE void chooseSecurityImage(uint imageID);
+ Q_INVOKABLE void chooseSecurityImage(const QString& imageFile);
Q_INVOKABLE void getSecurityImage();
+ Q_INVOKABLE void getLoginStatus();
+ Q_INVOKABLE void setPassphrase(const QString& passphrase);
+ Q_INVOKABLE void getPassphraseSetupStatus();
+ Q_INVOKABLE void getKeyFilePath();
};
#endif // hifi_QmlCommerce_h
diff --git a/interface/src/commerce/Wallet.cpp b/interface/src/commerce/Wallet.cpp
index 05ccea3a59..5fefd0c43b 100644
--- a/interface/src/commerce/Wallet.cpp
+++ b/interface/src/commerce/Wallet.cpp
@@ -12,10 +12,15 @@
#include "CommerceLogging.h"
#include "Ledger.h"
#include "Wallet.h"
+#include "Application.h"
+#include "ui/ImageProvider.h"
#include
+#include
+#include
#include
+#include
#include
#include
@@ -23,8 +28,10 @@
#include
#include
#include
+#include
static const char* KEY_FILE = "hifikey";
+static const char* IMAGE_FILE = "hifi_image"; // eventually this will live in keyfile
void initialize() {
static bool initialized = false;
@@ -40,18 +47,30 @@ QString keyFilePath() {
return PathUtils::getAppDataFilePath(KEY_FILE);
}
-// for now the callback function just returns the same string. Later we can hook
-// this to the gui (some thought required)
+QString imageFilePath() {
+ return PathUtils::getAppDataFilePath(IMAGE_FILE);
+}
+
+// use the cached _passphrase if it exists, otherwise we need to prompt
int passwordCallback(char* password, int maxPasswordSize, int rwFlag, void* u) {
// just return a hardcoded pwd for now
- static const char* pwd = "pwd";
- strcpy(password, pwd);
- return static_cast(strlen(pwd));
+ auto passphrase = DependencyManager::get()->getPassphrase();
+ if (passphrase) {
+ strcpy(password, passphrase->toLocal8Bit().constData());
+ return static_cast(passphrase->size());
+ } else {
+ // ok gotta bring up modal dialog... But right now lets just
+ // just keep it empty
+ return 0;
+ }
}
// BEGIN copied code - this will be removed/changed at some point soon
// copied (without emits for various signals) from libraries/networking/src/RSAKeypairGenerator.cpp.
// We will have a different implementation in practice, but this gives us a start for now
+//
+// NOTE: we don't really use the private keys returned - we can see how this evolves, but probably
+// we should just return a list of public keys?
QPair generateRSAKeypair() {
RSA* keyPair = RSA_new();
@@ -106,9 +125,11 @@ QPair generateRSAKeypair() {
// now lets persist them to files
- // TODO: figure out a scheme for multiple keys, etc...
+ // FIXME: for now I'm appending to the file if it exists. As long as we always put
+ // the keys in the same order, this works fine. TODO: verify this will skip over
+ // anything else (like an embedded image)
FILE* fp;
- if ((fp = fopen(keyFilePath().toStdString().c_str(), "wt"))) {
+ if ((fp = fopen(keyFilePath().toStdString().c_str(), "at"))) {
if (!PEM_write_RSAPublicKey(fp, keyPair)) {
fclose(fp);
qCDebug(commerce) << "failed to write public key";
@@ -125,7 +146,9 @@ QPair generateRSAKeypair() {
RSA_free(keyPair);
- // prepare the return values
+ // prepare the return values. TODO: Fix this - we probably don't really even want the
+ // private key at all (better to read it when we need it?). Or maybe we do, when we have
+ // multiple keys?
retval.first = new QByteArray(reinterpret_cast(publicKeyDER), publicKeyLength ),
retval.second = new QByteArray(reinterpret_cast(privateKeyDER), privateKeyLength );
@@ -192,6 +215,126 @@ RSA* readPrivateKey(const char* filename) {
return key;
}
+static const unsigned char IVEC[16] = "IAmAnIVecYay123";
+
+void initializeAESKeys(unsigned char* ivec, unsigned char* ckey, const QByteArray& salt) {
+ // first ivec
+ memcpy(ivec, IVEC, 16);
+ auto hash = QCryptographicHash::hash(salt, QCryptographicHash::Md5);
+ memcpy(ckey, hash.data(), 16);
+}
+
+void Wallet::setPassphrase(const QString& passphrase) {
+ if (_passphrase) {
+ delete _passphrase;
+ }
+ _passphrase = new QString(passphrase);
+}
+
+// encrypt some stuff
+bool Wallet::encryptFile(const QString& inputFilePath, const QString& outputFilePath) {
+ // aes requires a couple 128-bit keys (ckey and ivec). For now, I'll just
+ // use the md5 of the salt as the ckey (md5 is 128-bit), and ivec will be
+ // a constant. We can review this later - there are ways to generate keys
+ // from a password that may be better.
+ unsigned char ivec[16];
+ unsigned char ckey[16];
+
+ initializeAESKeys(ivec, ckey, _salt);
+
+ int tempSize, outSize;
+
+ // read entire unencrypted file into memory
+ QFile inputFile(inputFilePath);
+ if (!inputFile.exists()) {
+ qCDebug(commerce) << "cannot encrypt" << inputFilePath << "file doesn't exist";
+ return false;
+ }
+ inputFile.open(QIODevice::ReadOnly);
+ QByteArray inputFileBuffer = inputFile.readAll();
+ inputFile.close();
+
+ // reserve enough capacity for encrypted bytes
+ unsigned char* outputFileBuffer = new unsigned char[inputFileBuffer.size() + AES_BLOCK_SIZE];
+
+ EVP_CIPHER_CTX* ctx = EVP_CIPHER_CTX_new();
+
+ // TODO: add error handling!!!
+ if (!EVP_EncryptInit_ex(ctx, EVP_des_ede3_cbc(), NULL, ckey, ivec)) {
+ qCDebug(commerce) << "encrypt init failure";
+ delete[] outputFileBuffer;
+ return false;
+ }
+ if (!EVP_EncryptUpdate(ctx, outputFileBuffer, &tempSize, (unsigned char*)inputFileBuffer.data(), inputFileBuffer.size())) {
+ qCDebug(commerce) << "encrypt update failure";
+ delete[] outputFileBuffer;
+ return false;
+ }
+ outSize = tempSize;
+ if (!EVP_EncryptFinal_ex(ctx, outputFileBuffer + outSize, &tempSize)) {
+ qCDebug(commerce) << "encrypt final failure";
+ delete[] outputFileBuffer;
+ return false;
+ }
+
+ outSize += tempSize;
+ EVP_CIPHER_CTX_free(ctx);
+ qCDebug(commerce) << "encrypted buffer size" << outSize;
+ QByteArray output((const char*)outputFileBuffer, outSize);
+ QFile outputFile(outputFilePath);
+ outputFile.open(QIODevice::WriteOnly);
+ outputFile.write(output);
+ outputFile.close();
+
+ delete[] outputFileBuffer;
+ return true;
+}
+
+bool Wallet::decryptFile(const QString& inputFilePath, unsigned char** outputBufferPtr, int* outputBufferSize) {
+ unsigned char ivec[16];
+ unsigned char ckey[16];
+ initializeAESKeys(ivec, ckey, _salt);
+
+ // read encrypted file
+ QFile inputFile(inputFilePath);
+ if (!inputFile.exists()) {
+ qCDebug(commerce) << "cannot decrypt file" << inputFilePath << "it doesn't exist";
+ return false;
+ }
+ inputFile.open(QIODevice::ReadOnly);
+ QByteArray encryptedBuffer = inputFile.readAll();
+ inputFile.close();
+
+ // setup decrypted buffer
+ unsigned char* outputBuffer = new unsigned char[encryptedBuffer.size()];
+ int tempSize;
+
+ // TODO: add error handling
+ EVP_CIPHER_CTX* ctx = EVP_CIPHER_CTX_new();
+ if (!EVP_DecryptInit_ex(ctx, EVP_des_ede3_cbc(), NULL, ckey, ivec)) {
+ qCDebug(commerce) << "decrypt init failure";
+ delete[] outputBuffer;
+ return false;
+ }
+ if (!EVP_DecryptUpdate(ctx, outputBuffer, &tempSize, (unsigned char*)encryptedBuffer.data(), encryptedBuffer.size())) {
+ qCDebug(commerce) << "decrypt update failure";
+ delete[] outputBuffer;
+ return false;
+ }
+ *outputBufferSize = tempSize;
+ if (!EVP_DecryptFinal_ex(ctx, outputBuffer + tempSize, &tempSize)) {
+ qCDebug(commerce) << "decrypt final failure";
+ delete[] outputBuffer;
+ return false;
+ }
+ EVP_CIPHER_CTX_free(ctx);
+ *outputBufferSize += tempSize;
+ *outputBufferPtr = outputBuffer;
+ qCDebug(commerce) << "decrypted buffer size" << *outputBufferSize;
+ delete[] outputBuffer;
+ return true;
+}
+
bool Wallet::createIfNeeded() {
if (_publicKeys.count() > 0) return false;
@@ -205,7 +348,7 @@ bool Wallet::createIfNeeded() {
qCDebug(commerce) << "read private key";
RSA_free(key);
// K -- add the public key since we have a legit private key associated with it
- _publicKeys.push_back(publicKey.toBase64());
+ _publicKeys.push_back(QUrl::toPercentEncoding(publicKey.toBase64()));
return false;
}
}
@@ -228,6 +371,7 @@ bool Wallet::generateKeyPair() {
auto ledger = DependencyManager::get();
return ledger->receiveAt(key, oldKey);
}
+
QStringList Wallet::listPublicKeys() {
qCInfo(commerce) << "Enumerating public keys.";
createIfNeeded();
@@ -268,10 +412,65 @@ QString Wallet::signWithKey(const QByteArray& text, const QString& key) {
}
-void Wallet::chooseSecurityImage(uint imageID) {
- _chosenSecurityImage = (SecurityImage)imageID;
- emit securityImageResult(imageID);
+void Wallet::chooseSecurityImage(const QString& filename) {
+
+ if (_securityImage) {
+ delete _securityImage;
+ }
+ // temporary...
+ QString path = qApp->applicationDirPath();
+ path.append("/resources/qml/hifi/commerce/");
+ path.append(filename);
+ // now create a new security image pixmap
+ _securityImage = new QPixmap();
+
+ qCDebug(commerce) << "loading data for pixmap from" << path;
+ _securityImage->load(path);
+
+ // encrypt it and save.
+ if (encryptFile(path, imageFilePath())) {
+ qCDebug(commerce) << "emitting pixmap";
+
+ // inform the image provider
+ auto engine = DependencyManager::get()->getSurfaceContext()->engine();
+ auto imageProvider = reinterpret_cast(engine->imageProvider(ImageProvider::PROVIDER_NAME));
+ imageProvider->setSecurityImage(_securityImage);
+
+ emit securityImageResult(true);
+ } else {
+ qCDebug(commerce) << "failed to encrypt security image";
+ emit securityImageResult(false);
+ }
}
+
void Wallet::getSecurityImage() {
- emit securityImageResult(_chosenSecurityImage);
+ unsigned char* data;
+ int dataLen;
+
+ // if already decrypted, don't do it again
+ if (_securityImage) {
+ emit securityImageResult(true);
+ return;
+ }
+
+ // decrypt and return
+ if (decryptFile(imageFilePath(), &data, &dataLen)) {
+ // create the pixmap
+ _securityImage = new QPixmap();
+ _securityImage->loadFromData(data, dataLen, "jpg");
+ qCDebug(commerce) << "created pixmap from encrypted file";
+
+ // inform the image provider
+ auto engine = DependencyManager::get()->getSurfaceContext()->engine();
+ auto imageProvider = reinterpret_cast(engine->imageProvider(ImageProvider::PROVIDER_NAME));
+ imageProvider->setSecurityImage(_securityImage);
+
+ emit securityImageResult(true);
+ } else {
+ qCDebug(commerce) << "failed to decrypt security image (maybe none saved yet?)";
+ emit securityImageResult(false);
+ }
+}
+void Wallet::getKeyFilePath() {
+ emit keyFilePathResult(keyFilePath());
}
diff --git a/interface/src/commerce/Wallet.h b/interface/src/commerce/Wallet.h
index a1c7c7752b..4c0d7190bb 100644
--- a/interface/src/commerce/Wallet.h
+++ b/interface/src/commerce/Wallet.h
@@ -16,6 +16,8 @@
#include
+#include
+
class Wallet : public QObject, public Dependency {
Q_OBJECT
SINGLETON_DEPENDENCY
@@ -26,16 +28,21 @@ public:
bool generateKeyPair();
QStringList listPublicKeys();
QString signWithKey(const QByteArray& text, const QString& key);
- void chooseSecurityImage(uint imageID);
+ void chooseSecurityImage(const QString& imageFile);
void getSecurityImage();
+ void getKeyFilePath();
+
+ void setSalt(const QByteArray& salt) { _salt = salt; }
+ QByteArray getSalt() { return _salt; }
+
+ void setPassphrase(const QString& passphrase);
+ QString* getPassphrase() { return _passphrase; }
signals:
- void securityImageResult(uint imageID);
+ void securityImageResult(bool exists) ;
+ void keyFilePathResult(const QString& path);
protected:
- // ALWAYS add SecurityImage enum values to the END of the enum.
- // They must be in the same order as the images are listed in
- // SecurityImageSelection.qml
enum SecurityImage {
NONE = 0,
Cat,
@@ -48,7 +55,12 @@ protected:
private:
QStringList _publicKeys{};
- SecurityImage _chosenSecurityImage = SecurityImage::NONE;
+ QPixmap* _securityImage { nullptr };
+ QByteArray _salt {"iamsalt!"};
+ QString* _passphrase { new QString("pwd") };
+
+ bool encryptFile(const QString& inputFilePath, const QString& outputFilePath);
+ bool decryptFile(const QString& inputFilePath, unsigned char** outputBufferPtr, int* outputBufferLen);
};
#endif // hifi_Wallet_h
diff --git a/interface/src/ui/ImageProvider.cpp b/interface/src/ui/ImageProvider.cpp
new file mode 100644
index 0000000000..4925cdf1e9
--- /dev/null
+++ b/interface/src/ui/ImageProvider.cpp
@@ -0,0 +1,29 @@
+//
+// ImageProvider.cpp
+// interface/src/ui
+//
+// Created by David Kelly on 8/23/2017.
+// Copyright 2017 High Fidelity, Inc.
+//
+// Distributed under the Apache License, Version 2.0.
+// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
+//
+
+#include "ImageProvider.h"
+#include
+
+const QString ImageProvider::PROVIDER_NAME = "security";
+
+QPixmap ImageProvider::requestPixmap(const QString& id, QSize* size, const QSize& requestedSize) {
+
+ // adjust the internal pixmap to have the requested size
+ if (id == "securityImage" && _securityImage) {
+ *size = _securityImage->size();
+ if (requestedSize.width() > 0 && requestedSize.height() > 0) {
+ return _securityImage->scaled(requestedSize.width(), requestedSize.height(), Qt::KeepAspectRatio);
+ } else {
+ return _securityImage->copy();
+ }
+ }
+ return QPixmap();
+}
diff --git a/interface/src/ui/ImageProvider.h b/interface/src/ui/ImageProvider.h
new file mode 100644
index 0000000000..c0b620585a
--- /dev/null
+++ b/interface/src/ui/ImageProvider.h
@@ -0,0 +1,33 @@
+//
+// ImageProvider.h
+//
+// Created by David Kelly on 2017/08/23
+// Copyright 2017 High Fidelity, Inc.
+
+// Distributed under the Apache License, Version 2.0
+// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
+//
+
+#pragma once
+#ifndef hifi_ImageProvider_h
+#define hifi_ImageProvider_h
+
+#include
+
+class ImageProvider: public QQuickImageProvider {
+public:
+ static const QString PROVIDER_NAME;
+
+ ImageProvider() : QQuickImageProvider(QQuickImageProvider::Pixmap) {}
+
+ QPixmap requestPixmap(const QString& id, QSize* size, const QSize& requestedSize) override;
+
+ void setSecurityImage(QPixmap* pixmap) { _securityImage = pixmap; }
+
+protected:
+ QPixmap* _securityImage { nullptr };
+
+};
+
+#endif //hifi_ImageProvider_h
+
diff --git a/scripts/defaultScripts.js b/scripts/defaultScripts.js
index 2270118861..2285337f41 100644
--- a/scripts/defaultScripts.js
+++ b/scripts/defaultScripts.js
@@ -28,7 +28,8 @@ var DEFAULT_SCRIPTS_COMBINED = [
"system/notifications.js",
"system/dialTone.js",
"system/firstPersonHMD.js",
- "system/tablet-ui/tabletUI.js"
+ "system/tablet-ui/tabletUI.js",
+ "system/commerce/wallet.js"
];
var DEFAULT_SCRIPTS_SEPARATE = [
"system/controllers/controllerScripts.js",
diff --git a/scripts/system/commerce/wallet.js b/scripts/system/commerce/wallet.js
new file mode 100644
index 0000000000..14954e5df6
--- /dev/null
+++ b/scripts/system/commerce/wallet.js
@@ -0,0 +1,152 @@
+"use strict";
+/*jslint vars:true, plusplus:true, forin:true*/
+/* eslint indent: ["error", 4, { "outerIIFEBody": 0 }] */
+//
+// wallet.js
+//
+// Created by Zach Fox on 2017-08-17
+// Copyright 2017 High Fidelity, Inc
+//
+// Distributed under the Apache License, Version 2.0
+// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
+//
+
+/*global XXX */
+
+(function () { // BEGIN LOCAL_SCOPE
+
+
+ // Function Name: onButtonClicked()
+ //
+ // Description:
+ // -Fired when the app button is pressed.
+ //
+ // Relevant Variables:
+ // -WALLET_QML_SOURCE: The path to the Wallet QML
+ // -onWalletScreen: true/false depending on whether we're looking at the app.
+ var WALLET_QML_SOURCE = Script.resourcesPath() + "qml/hifi/commerce/wallet/Wallet.qml";
+ var onWalletScreen = false;
+ function onButtonClicked() {
+ if (!tablet) {
+ print("Warning in buttonClicked(): 'tablet' undefined!");
+ return;
+ }
+ if (onWalletScreen) {
+ // for toolbar-mode: go back to home screen, this will close the window.
+ tablet.gotoHomeScreen();
+ } else {
+ tablet.loadQMLSource(WALLET_QML_SOURCE);
+ }
+ }
+
+ // Function Name: sendToQml()
+ //
+ // Description:
+ // -Use this function to send a message to the QML (i.e. to change appearances). The "message" argument is what is sent to
+ // the QML in the format "{method, params}", like json-rpc. See also fromQml().
+ function sendToQml(message) {
+ tablet.sendToQml(message);
+ }
+
+ // Function Name: fromQml()
+ //
+ // Description:
+ // -Called when a message is received from SpectatorCamera.qml. The "message" argument is what is sent from the QML
+ // in the format "{method, params}", like json-rpc. See also sendToQml().
+ function fromQml(message) {
+ switch (message.method) {
+ case 'walletSetup_cancelClicked':
+ tablet.gotoHomeScreen();
+ break;
+ case 'walletSetup_loginClicked':
+ if ((HMD.active && Settings.getValue("hmdTabletBecomesToolbar", false))
+ || (!HMD.active && Settings.getValue("desktopTabletBecomesToolbar", true))) {
+ Menu.triggerOption("Login / Sign Up");
+ tablet.gotoHomeScreen();
+ } else {
+ tablet.loadQMLOnTop("../../../dialogs/TabletLoginDialog.qml");
+ }
+ break;
+ default:
+ print('Unrecognized message from QML:', JSON.stringify(message));
+ }
+ }
+
+ // Function Name: wireEventBridge()
+ //
+ // Description:
+ // -Used to connect/disconnect the script's response to the tablet's "fromQml" signal. Set the "on" argument to enable or
+ // disable to event bridge.
+ //
+ // Relevant Variables:
+ // -hasEventBridge: true/false depending on whether we've already connected the event bridge.
+ var hasEventBridge = false;
+ function wireEventBridge(on) {
+ if (!tablet) {
+ print("Warning in wireEventBridge(): 'tablet' undefined!");
+ return;
+ }
+ if (on) {
+ if (!hasEventBridge) {
+ tablet.fromQml.connect(fromQml);
+ hasEventBridge = true;
+ }
+ } else {
+ if (hasEventBridge) {
+ tablet.fromQml.disconnect(fromQml);
+ hasEventBridge = false;
+ }
+ }
+ }
+
+ // Function Name: onTabletScreenChanged()
+ //
+ // Description:
+ // -Called when the TabletScriptingInterface::screenChanged() signal is emitted. The "type" argument can be either the string
+ // value of "Home", "Web", "Menu", "QML", or "Closed". The "url" argument is only valid for Web and QML.
+ function onTabletScreenChanged(type, url) {
+ onWalletScreen = (type === "QML" && url === WALLET_QML_SOURCE);
+ wireEventBridge(onWalletScreen);
+ // Change button to active when window is first openend, false otherwise.
+ if (button) {
+ button.editProperties({ isActive: onWalletScreen });
+ }
+ }
+
+ //
+ // Manage the connection between the button and the window.
+ //
+ var button;
+ var buttonName = "WALLET";
+ var tablet = null;
+ var walletEnabled = Settings.getValue("inspectionMode", false);
+ function startup() {
+ if (walletEnabled) {
+ tablet = Tablet.getTablet("com.highfidelity.interface.tablet.system");
+ button = tablet.addButton({
+ text: buttonName,
+ icon: "icons/tablet-icons/wallet-i.svg",
+ activeIcon: "icons/tablet-icons/wallet-a.svg"
+ });
+ button.clicked.connect(onButtonClicked);
+ tablet.screenChanged.connect(onTabletScreenChanged);
+ }
+ }
+ function shutdown() {
+ button.clicked.disconnect(onButtonClicked);
+ tablet.removeButton(button);
+ if (tablet) {
+ tablet.screenChanged.disconnect(onTabletScreenChanged);
+ if (onWalletScreen) {
+ tablet.gotoHomeScreen();
+ }
+ }
+ }
+
+ //
+ // Run the functions.
+ //
+ startup();
+ Script.scriptEnding.connect(shutdown);
+
+}()); // END LOCAL_SCOPE
diff --git a/scripts/system/html/js/marketplacesInject.js b/scripts/system/html/js/marketplacesInject.js
index 77be746bf4..2889a1514a 100644
--- a/scripts/system/html/js/marketplacesInject.js
+++ b/scripts/system/html/js/marketplacesInject.js
@@ -115,7 +115,7 @@
itemId: id,
itemName: name,
itemAuthor: author,
- itemPrice: price ? parseInt(price, 10) : Math.round(Math.random() * 50),
+ itemPrice: price ? parseInt(price, 10) : 0,
itemHref: href
}));
}