Merge pull request #12480 from zfox23/commerce_extendedCerts_apps

Commerce: Extended Certificates - Apps!
This commit is contained in:
Zach Fox 2018-02-26 08:24:53 -08:00 committed by GitHub
commit b5c42bfeca
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
8 changed files with 245 additions and 12 deletions

View file

@ -48,6 +48,7 @@ Rectangle {
property bool debugCheckoutSuccess: false;
property bool canRezCertifiedItems: Entities.canRezCertified() || Entities.canRezTmpCertified();
property string referrer;
property bool isInstalled;
// Style
color: hifi.colors.white;
Connections {
@ -122,6 +123,12 @@ Rectangle {
root.refreshBuyUI();
}
}
onAppInstalled: {
if (appHref === root.itemHref) {
root.isInstalled = true;
}
}
}
onItemIdChanged: {
@ -146,7 +153,8 @@ Rectangle {
}
onItemTypeChanged: {
if (root.itemType === "entity" || root.itemType === "wearable" || root.itemType === "contentSet" || root.itemType === "avatar") {
if (root.itemType === "entity" || root.itemType === "wearable" ||
root.itemType === "contentSet" || root.itemType === "avatar" || root.itemType === "app") {
root.isCertified = true;
} else {
root.isCertified = false;
@ -311,7 +319,7 @@ Rectangle {
z: 997;
visible: !root.ownershipStatusReceived || !root.balanceReceived;
anchors.fill: parent;
color: Qt.rgba(0.0, 0.0, 0.0, 0.7);
color: hifi.colors.white;
// This object is always used in a popup.
// This MouseArea is used to prevent a user from being
@ -323,8 +331,9 @@ Rectangle {
}
AnimatedImage {
source: "../common/images/loader.gif"
width: 96;
id: loadingImage;
source: "../common/images/loader-blue.gif"
width: 74;
height: width;
anchors.verticalCenter: parent.verticalCenter;
anchors.horizontalCenter: parent.horizontalCenter;
@ -679,7 +688,7 @@ Rectangle {
id: rezNowButton;
enabled: (root.itemType === "entity" && root.canRezCertifiedItems) ||
(root.itemType === "contentSet" && Entities.canReplaceContent()) ||
root.itemType === "wearable" || root.itemType === "avatar";
root.itemType === "wearable" || root.itemType === "avatar" || root.itemType === "app";
buttonGlyph: (root.buttonGlyph)[itemTypesArray.indexOf(root.itemType)];
color: hifi.buttons.red;
colorScheme: hifi.colorSchemes.light;
@ -688,7 +697,7 @@ Rectangle {
height: 50;
anchors.left: parent.left;
anchors.right: parent.right;
text: (root.buttonTextNormal)[itemTypesArray.indexOf(root.itemType)];
text: root.itemType === "app" && root.isInstalled ? "OPEN APP" : (root.buttonTextNormal)[itemTypesArray.indexOf(root.itemType)];
onClicked: {
if (root.itemType === "contentSet") {
lightboxPopup.titleText = "Replace Content";
@ -712,6 +721,12 @@ Rectangle {
lightboxPopup.button2text = "CONFIRM";
lightboxPopup.button2method = "MyAvatar.useFullAvatarURL('" + root.itemHref + "'); root.visible = false;";
lightboxPopup.visible = true;
} else if (root.itemType === "app") {
if (root.isInstalled) {
Commerce.openApp(root.itemHref);
} else {
Commerce.installApp(root.itemHref);
}
} else {
sendToScript({method: 'checkout_rezClicked', itemHref: root.itemHref, itemType: root.itemType});
rezzedNotifContainer.visible = true;

Binary file not shown.

After

Width:  |  Height:  |  Size: 58 KiB

View file

@ -47,6 +47,7 @@ Item {
property bool showConfirmation: false;
property bool hasPermissionToRezThis;
property bool permissionExplanationCardVisible;
property bool isInstalled;
property string originalStatusText;
property string originalStatusColor;
@ -62,6 +63,18 @@ Item {
showConfirmation = true;
}
}
onAppInstalled: {
if (appHref === root.itemHref) {
root.isInstalled = true;
}
}
onAppUninstalled: {
if (appHref === root.itemHref) {
root.isInstalled = false;
}
}
}
Connections {
@ -472,6 +485,43 @@ Item {
}
}
Rectangle {
id: appButtonContainer;
color: hifi.colors.white;
z: 994;
visible: root.isInstalled;
anchors.fill: buttonContainer;
HifiControlsUit.Button {
id: openAppButton;
color: hifi.buttons.blue;
colorScheme: hifi.colorSchemes.light;
anchors.top: parent.top;
anchors.right: parent.right;
anchors.left: parent.left;
width: 92;
height: 44;
text: "OPEN"
onClicked: {
Commerce.openApp(root.itemHref);
}
}
HifiControlsUit.Button {
id: uninstallAppButton;
color: hifi.buttons.noneBorderless;
colorScheme: hifi.colorSchemes.light;
anchors.bottom: parent.bottom;
anchors.right: parent.right;
anchors.left: parent.left;
height: 44;
text: "UNINSTALL"
onClicked: {
Commerce.uninstallApp(root.itemHref);
}
}
}
Button {
id: buttonContainer;
property int color: hifi.buttons.blue;
@ -506,6 +556,9 @@ Item {
sendToPurchases({method: 'showReplaceContentLightbox', itemHref: root.itemHref});
} else if (root.itemType === "avatar") {
sendToPurchases({method: 'showChangeAvatarLightbox', itemName: root.itemName, itemHref: root.itemHref});
} else if (root.itemType === "app") {
// "Run" and "Uninstall" buttons are separate.
Commerce.installApp(root.itemHref);
} else {
sendToPurchases({method: 'purchases_rezClicked', itemHref: root.itemHref, itemType: root.itemType});
root.showConfirmation = true;

View file

@ -36,6 +36,7 @@ Rectangle {
property bool isShowingMyItems: false;
property bool isDebuggingFirstUseTutorial: false;
property int pendingItemCount: 0;
property string installedApps;
// Style
color: hifi.colors.white;
Connections {
@ -61,6 +62,7 @@ Rectangle {
root.activeView = "firstUseTutorial";
} else if (!Settings.getValue("isFirstUseOfPurchases", true) && root.activeView === "initialize") {
root.activeView = "purchasesMain";
root.installedApps = Commerce.getInstalledApps();
Commerce.inventory();
}
} else {
@ -269,6 +271,7 @@ Rectangle {
case 'tutorial_finished':
Settings.setValue("isFirstUseOfPurchases", false);
root.activeView = "purchasesMain";
root.installedApps = Commerce.getInstalledApps();
Commerce.inventory();
break;
}
@ -394,6 +397,7 @@ Rectangle {
limitedRun: model.limited_run;
displayedItemCount: model.displayedItemCount;
permissionExplanationCardVisible: model.permissionExplanationCardVisible;
isInstalled: model.isInstalled;
itemType: {
if (model.root_file_url.indexOf(".fst") > -1) {
"avatar";
@ -680,9 +684,13 @@ Rectangle {
if (sameItemCount !== tempPurchasesModel.count || filterBar.text !== filterBar.previousText) {
filteredPurchasesModel.clear();
var currentId;
for (var i = 0; i < tempPurchasesModel.count; i++) {
currentId = tempPurchasesModel.get(i).id;
filteredPurchasesModel.append(tempPurchasesModel.get(i));
filteredPurchasesModel.setProperty(i, 'permissionExplanationCardVisible', false);
filteredPurchasesModel.setProperty(i, 'isInstalled', ((root.installedApps).indexOf(currentId) > -1));
}
populateDisplayedItemCounts();

View file

@ -10,6 +10,7 @@
//
#include "QmlCommerce.h"
#include "CommerceLogging.h"
#include "Application.h"
#include "DependencyManager.h"
#include "Ledger.h"
@ -17,6 +18,9 @@
#include <AccountManager.h>
#include <Application.h>
#include <UserActivityLogger.h>
#include <ScriptEngines.h>
#include <ui/TabletScriptingInterface.h>
#include "scripting/HMDScriptingInterface.h"
QmlCommerce::QmlCommerce() {
auto ledger = DependencyManager::get<Ledger>();
@ -40,6 +44,8 @@ QmlCommerce::QmlCommerce() {
connect(accountManager.data(), &AccountManager::usernameChanged, this, [&]() {
setPassphrase("");
});
_appsPath = PathUtils::getAppDataPath() + "Apps/";
}
void QmlCommerce::getWalletStatus() {
@ -183,3 +189,148 @@ void QmlCommerce::alreadyOwned(const QString& marketplaceId) {
auto ledger = DependencyManager::get<Ledger>();
ledger->alreadyOwned(marketplaceId);
}
QString QmlCommerce::getInstalledApps() {
QString installedAppsFromMarketplace;
QStringList runningScripts = DependencyManager::get<ScriptEngines>()->getRunningScripts();
QDir directory(_appsPath);
QStringList apps = directory.entryList(QStringList("*.app.json"));
foreach(QString appFileName, apps) {
installedAppsFromMarketplace += appFileName;
installedAppsFromMarketplace += ",";
QFile appFile(_appsPath + appFileName);
if (appFile.open(QIODevice::ReadOnly)) {
QJsonDocument appFileJsonDocument = QJsonDocument::fromJson(appFile.readAll());
appFile.close();
QJsonObject appFileJsonObject = appFileJsonDocument.object();
QString scriptURL = appFileJsonObject["scriptURL"].toString();
// If the script .app.json is on the user's local disk but the associated script isn't running
// for some reason, start that script again.
if (!runningScripts.contains(scriptURL)) {
if ((DependencyManager::get<ScriptEngines>()->loadScript(scriptURL.trimmed())).isNull()) {
qCDebug(commerce) << "Couldn't start script while checking installed apps.";
}
}
} else {
qCDebug(commerce) << "Couldn't open local .app.json file for reading.";
}
}
return installedAppsFromMarketplace;
}
bool QmlCommerce::installApp(const QString& itemHref) {
if (!QDir(_appsPath).exists()) {
if (!QDir().mkdir(_appsPath)) {
qCDebug(commerce) << "Couldn't make _appsPath directory.";
return false;
}
}
QUrl appHref(itemHref);
auto request = DependencyManager::get<ResourceManager>()->createResourceRequest(this, appHref);
if (!request) {
qCDebug(commerce) << "Couldn't create resource request for app.";
return false;
}
connect(request, &ResourceRequest::finished, this, [=]() {
if (request->getResult() != ResourceRequest::Success) {
qCDebug(commerce) << "Failed to get .app.json file from remote.";
return false;
}
// Copy the .app.json to the apps directory inside %AppData%/High Fidelity/Interface
auto requestData = request->getData();
QFile appFile(_appsPath + "/" + appHref.fileName());
if (!appFile.open(QIODevice::WriteOnly)) {
qCDebug(commerce) << "Couldn't open local .app.json file for creation.";
return false;
}
if (appFile.write(requestData) == -1) {
qCDebug(commerce) << "Couldn't write to local .app.json file.";
return false;
}
// Close the file
appFile.close();
// Read from the returned datastream to know what .js to add to Running Scripts
QJsonDocument appFileJsonDocument = QJsonDocument::fromJson(requestData);
QJsonObject appFileJsonObject = appFileJsonDocument.object();
QString scriptUrl = appFileJsonObject["scriptURL"].toString();
if ((DependencyManager::get<ScriptEngines>()->loadScript(scriptUrl.trimmed())).isNull()) {
qCDebug(commerce) << "Couldn't load script.";
return false;
}
emit appInstalled(itemHref);
return true;
});
request->send();
return true;
}
bool QmlCommerce::uninstallApp(const QString& itemHref) {
QUrl appHref(itemHref);
// Read from the file to know what .js script to stop
QFile appFile(_appsPath + "/" + appHref.fileName());
if (!appFile.open(QIODevice::ReadOnly)) {
qCDebug(commerce) << "Couldn't open local .app.json file for deletion.";
return false;
}
QJsonDocument appFileJsonDocument = QJsonDocument::fromJson(appFile.readAll());
QJsonObject appFileJsonObject = appFileJsonDocument.object();
QString scriptUrl = appFileJsonObject["scriptURL"].toString();
if (!DependencyManager::get<ScriptEngines>()->stopScript(scriptUrl.trimmed(), false)) {
qCDebug(commerce) << "Couldn't stop script.";
return false;
}
// Delete the .app.json from the filesystem
// remove() closes the file first.
if (!appFile.remove()) {
qCDebug(commerce) << "Couldn't delete local .app.json file.";
return false;
}
emit appUninstalled(itemHref);
return true;
}
bool QmlCommerce::openApp(const QString& itemHref) {
QUrl appHref(itemHref);
// Read from the file to know what .html or .qml document to open
QFile appFile(_appsPath + "/" + appHref.fileName());
if (!appFile.open(QIODevice::ReadOnly)) {
qCDebug(commerce) << "Couldn't open local .app.json file.";
return false;
}
QJsonDocument appFileJsonDocument = QJsonDocument::fromJson(appFile.readAll());
QJsonObject appFileJsonObject = appFileJsonDocument.object();
QString homeUrl = appFileJsonObject["homeURL"].toString();
auto tabletScriptingInterface = DependencyManager::get<TabletScriptingInterface>();
auto tablet = dynamic_cast<TabletProxy*>(tabletScriptingInterface->getTablet("com.highfidelity.interface.tablet.system"));
if (homeUrl.contains(".qml", Qt::CaseInsensitive)) {
tablet->loadQMLSource(homeUrl);
} else if (homeUrl.contains(".html", Qt::CaseInsensitive)) {
tablet->gotoWebScreen(homeUrl);
} else {
qCDebug(commerce) << "Attempted to open unknown type of homeURL!";
return false;
}
DependencyManager::get<HMDScriptingInterface>()->openTablet();
return true;
}

View file

@ -51,6 +51,9 @@ signals:
void contentSetChanged(const QString& contentSetHref);
void appInstalled(const QString& appHref);
void appUninstalled(const QString& appHref);
protected:
Q_INVOKABLE void getWalletStatus();
@ -76,8 +79,15 @@ protected:
Q_INVOKABLE void transferHfcToNode(const QString& nodeID, const int& amount, const QString& optionalMessage);
Q_INVOKABLE void transferHfcToUsername(const QString& username, const int& amount, const QString& optionalMessage);
Q_INVOKABLE void replaceContentSet(const QString& itemHref);
Q_INVOKABLE QString getInstalledApps();
Q_INVOKABLE bool installApp(const QString& appHref);
Q_INVOKABLE bool uninstallApp(const QString& appHref);
Q_INVOKABLE bool openApp(const QString& appHref);
private:
QString _appsPath;
};
#endif // hifi_QmlCommerce_h

View file

@ -551,11 +551,7 @@ var selectionDisplay = null; // for gridTool.js to ignore
break;
case 'checkout_rezClicked':
case 'purchases_rezClicked':
if (message.itemType === "app") {
console.log("How did you get here? You can't buy apps yet!");
} else {
rezEntity(message.itemHref, message.itemType);
}
rezEntity(message.itemHref, message.itemType);
break;
case 'header_marketplaceImageClicked':
case 'purchases_backClicked':