diff --git a/android/AndroidManifest.xml b/android/AndroidManifest.xml index fa8a2154c6bb2efea0948737b79378de87364115..cc63b5227e9c58f89c87d64c2190c6f09adb1550 100644 --- a/android/AndroidManifest.xml +++ b/android/AndroidManifest.xml @@ -1,6 +1,6 @@ <?xml version="1.0"?> <manifest xmlns:android="http://schemas.android.com/apk/res/android" package="io.qt.qtdesignviewer" - android:installLocation="auto" android:versionCode="19" android:versionName="1.2"> + android:installLocation="auto" android:versionCode="20" android:versionName="1.2"> <!-- %%INSERT_PERMISSIONS --> <!-- %%INSERT_FEATURES --> <supports-screens android:anyDensity="true" android:largeScreens="true" diff --git a/src/backend.cpp b/src/backend.cpp index 5562cc9cf4594b3e1ffc502f2dd5711dbcb30fa1..b198893489d8cee2aa20635cca32734b87f6b4ad 100644 --- a/src/backend.cpp +++ b/src/backend.cpp @@ -26,8 +26,6 @@ #include "backend.h" #include "qapplication.h" -#if !defined(Q_OS_WASM) - #include <QDesktopServices> #include <QFileInfo> #include <QJsonArray> @@ -40,7 +38,7 @@ Backend::Backend(QObject *parent) : QObject(parent) { // This will allow us to open the app with the QR code - QDesktopServices::setUrlHandler("qtdesignviewer", this, "registerUser"); + QDesktopServices::setUrlHandler("qtdesignviewer", this, "parseDesignViewerUrl"); } void Backend::initialize() @@ -74,11 +72,6 @@ void Backend::initialize() updateUserProjectList(); } - connect(&m_serviceConnector, - &ServiceConnector::downloadProgress, - this, - &Backend::downloadProgress); - qDebug("Initialization complete"); } @@ -94,15 +87,17 @@ void Backend::initializeProjectManager() if (m_projectManager) return; - qDebug() << "Initializing Project Manager"; m_projectManager.reset(new ProjectManager); connect(m_projectManager.data(), &ProjectManager::closingProject, this, [&] { emit popupClose(); - qDebug() << "Project Manager is closing"; m_projectManager.reset(); }); - qDebug() << "Project Manager is initialized"; + m_serviceConnector.reset(new ServiceConnector); + connect(m_serviceConnector.data(), + &ServiceConnector::downloadProgress, + this, + &Backend::downloadProgress); } bool Backend::connectDesignStudio() @@ -204,7 +199,7 @@ void Backend::runDemoProject(const QString &projectName) updatePopup("Downloading demo project...", false); const QString url = "https://designviewer.qt.io/qmlprojects/" + projectName + ".qmlrc"; - QByteArray project = m_serviceConnector.fetchProject(url); + QByteArray project = m_serviceConnector->fetchProject(url); qDebug() << "Demo project is not cached. Trying to download from " << url << " ..."; updatePopup("Caching demo project..."); @@ -239,15 +234,17 @@ void Backend::clearDemoCaches() emit popupClose(); } -void Backend::runUserProject(const QString &url) +void Backend::runUserProject(const QString &projectName) { initializeProjectManager(); updatePopup("Running user project"); emit popupOpen(); + qDebug() << "Running user project:" << projectName; + qDebug("Checking if project is cached. Getting list of available projects..."); - const QJsonArray projectList = m_serviceConnector.fetchUserProjectList(m_userHash); - const QString projectName = QFileInfo(url).baseName(); + const QJsonArray projectList = m_serviceConnector->fetchUserProjectList(m_userHash); + QString projectLastModified; QString projectId; QJsonObject projectInfo; @@ -263,7 +260,7 @@ void Backend::runUserProject(const QString &url) if (!projectCached) { qDebug("Project is not cached. Downloading..."); updatePopup("Project is not cached. Downloading...", false); - QByteArray projectData = m_serviceConnector.fetchProject(url); + QByteArray projectData = m_serviceConnector->fetchUserProject(m_userHash, projectName); updatePopup("Caching user project..."); if (!m_projectManager->cacheProject(projectData, projectInfo)) { @@ -285,6 +282,30 @@ void Backend::runUserProject(const QString &url) emit popupClose(); } +void Backend::runOnlineProject(const QString &url) +{ + initializeProjectManager(); + emit popupOpen(); + updatePopup("Downloading...", false); + QByteArray projectData = m_serviceConnector->fetchProject(url); + if (projectData.isEmpty()) { + qCritical() << "Could not download project. Please check the logs for more information."; + return; + } + + updatePopup("Unpacking project..."); + QString projectPath = m_projectManager->unpackProject(projectData); + updatePopup("Running project..."); + + if (!m_projectManager->runProject(projectPath)) + qCritical() << "Could not run project. Please check the logs for more information."; + else { + m_projectManager->showAppWindow(); + } + + emit popupClose(); +} + void Backend::registerUser(const QUrl &url) { const QString userHash = url.toString().remove("qtdesignviewer://"); @@ -298,7 +319,8 @@ void Backend::registerUser(const QUrl &url) void Backend::updateUserProjectList() { qDebug() << "Fetching available project list for user: " << m_userHash; - QJsonArray projectList = m_serviceConnector.fetchUserProjectList(m_userHash); + m_serviceConnector.reset(new ServiceConnector); + QJsonArray projectList = m_serviceConnector->fetchUserProjectList(m_userHash); m_projectList.clear(); qDebug("List of available projects fetched:"); @@ -310,4 +332,23 @@ void Backend::updateUserProjectList() emit projectListChanged(); } -#endif // !defined(Q_OS_WASM) +void Backend::parseDesignViewerUrl(const QUrl &url) +{ + QString urlData = url.toString().remove("qtdesignviewer://"); + // urlData could be either a direct url to the project or a user hash + // If it is a user hash, we register the user and fetch the project list + // If it is a project url, we submit the url to the text field + // sample url: qtdesignviewer://https://<url>/<project_name>.qmlrc + // sample user hash: qtdesignviewer://17e8907b3b84029384hs8djshdu38476 + if (urlData.isEmpty()) + return; + else if (urlData.startsWith("https//")) { + urlData.replace("https//", "https://"); + emit urlUpdated(urlData); + } else if (urlData.startsWith("https://")) { + emit urlUpdated(urlData); + } else { + qDebug() << "Registering user from QR code"; + registerUser(url); + } +} diff --git a/src/backend.h b/src/backend.h index 13aa6a5d71d7c126c680eb5643b9447a110652cf..d3e99d3d60f37e01e546a854e121422cb30b6d0b 100644 --- a/src/backend.h +++ b/src/backend.h @@ -57,7 +57,7 @@ private: // Other members QString m_userHash; - ServiceConnector m_serviceConnector; + QScopedPointer<ServiceConnector> m_serviceConnector; QScopedPointer<ProjectManager> m_projectManager; QScopedPointer<DesignStudioConnector> m_designStudioConnector; QThread m_dsConnectorThread; @@ -78,15 +78,19 @@ signals: void popupClose(); void userRegistered(); void networkUpdated(QString); + void urlUpdated(QString); public slots: - void runUserProject(const QString &url); + void runOnlineProject(const QString &url); + void runUserProject(const QString &projectName); void runDemoProject(const QString &projectName); void clearDemoCaches(); - void registerUser(const QUrl &url); + bool connectDesignStudio(); void disconnectDesignStudio(); + void parseDesignViewerUrl(const QUrl &url); + void registerUser(const QUrl &url); private slots: void initializeProjectManager(); }; diff --git a/src/main.cpp b/src/main.cpp index 678f21455ad3514463ae6cc022809371cc1659ec..24974a06aeafbd3f0cec030801f785659fceaf6d 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -35,13 +35,13 @@ static QString appLogs; void messageHandler(QtMsgType type, const QMessageLogContext &context, const QString &msg) { + QString logPrefix, logSuffix, newLog; + QByteArray localMsg = msg.toLocal8Bit(); const char *file = context.file ? context.file : ""; const char *function = context.function ? context.function : ""; - QString logPrefix, logSuffix, newLog; - QMessageBox msgBox(QMessageBox::Critical, "Critical:", msg, QMessageBox::Ok); - switch (type) - { + + switch (type) { case QtDebugMsg: logPrefix = QStringLiteral("Debug: "); break; @@ -53,7 +53,6 @@ void messageHandler(QtMsgType type, const QMessageLogContext &context, const QSt break; case QtCriticalMsg: logPrefix = QStringLiteral("Critical: "); - msgBox.exec(); break; case QtFatalMsg: logPrefix = QStringLiteral("Fatal: "); @@ -64,16 +63,13 @@ void messageHandler(QtMsgType type, const QMessageLogContext &context, const QSt newLog += logPrefix + localMsg + logSuffix + "\n"; __android_log_print(ANDROID_LOG_DEBUG, "Qt_UI_Viewer", "%s", qPrintable(newLog)); - static int logCounter = 0; - if (logCounter++ == 100) { - // remove the first line - int index = appLogs.indexOf('\n'); - if (index > 0) - appLogs.remove(0, index + 1); - logCounter = 99; + // only show critical and fatal messages in the UI + if (type == QtCriticalMsg || type == QtFatalMsg) { + QMessageBox msgBox{QMessageBox::Critical, "Critical:", msg, QMessageBox::Ok}; + msgBox.exec(); + if (backend) + backend->setLogs(appLogs += newLog); } - if (backend) - backend->setLogs(appLogs += newLog); } int main(int argc, char *argv[]) diff --git a/src/projectManager.cpp b/src/projectManager.cpp index 090b8de4489d09e233cae2f01eddd95031f1803e..1501a2f7fa40c2d479aa4f7311a3e90a964cd671 100644 --- a/src/projectManager.cpp +++ b/src/projectManager.cpp @@ -62,6 +62,25 @@ ProjectManager::ProjectManager(QObject *parent) } } +ProjectManager::~ProjectManager() +{ + cleanupResources(); + qDebug() << "ProjectManager destroyed."; +} + +void ProjectManager::cleanupResources() +{ + const uchar *data; + if (m_projectData.size()) { + qDebug() << "Unregistering the previous data from QRC system. Path: " << m_projectPath + << " Size: " << m_projectData.size() << " bytes."; + data = reinterpret_cast<const uchar *>(m_projectData.data()); + if (!QResource::unregisterResource(data, m_projectPath)) { + qCritical("Cannot unregister the previous resource data."); + } + } +} + QString ProjectManager::unpackProject(const QByteArray &project, bool extractZip) { QTemporaryDir tempDir("qmlprojector"); @@ -85,17 +104,10 @@ QString ProjectManager::unpackProject(const QByteArray &project, bool extractZip if (extractZip) qDebug("File could not be extracted. Trying to open it as a resource file."); - const uchar *data; - if (m_projectData.size()) { - qDebug() << "Unregistering the previous data from QRC system. Path: " << m_projectPath - << " Size: " << m_projectData.size() << " bytes."; - data = reinterpret_cast<const uchar *>(m_projectData.data()); - if (!QResource::unregisterResource(data, m_projectPath)) { - qCritical("Cannot unregister the previous resource data."); - } - } + cleanupResources(); m_projectData = project; + const uchar *data; data = reinterpret_cast<const uchar *>(m_projectData.data()); qDebug() << "Registering resource data. Size: " << m_projectData.size(); diff --git a/src/projectManager.h b/src/projectManager.h index 6bfbf141bfb047531abd498fb4d4be181ed678a6..b49a275e7453d72021a02fa4d6cba4ce9371bb4d 100644 --- a/src/projectManager.h +++ b/src/projectManager.h @@ -38,6 +38,7 @@ class ProjectManager : public QObject Q_OBJECT public: explicit ProjectManager(QObject *parent = nullptr); + ~ProjectManager(); QString unpackProject(const QByteArray &project, bool extractZip = false); bool runProject(const QString &projectPath); @@ -78,6 +79,7 @@ private: void parseQmlProjectFile(const QString &fileName, QString *mainFile, QStringList *importPaths); void cacheProject(const QString &projectName, const QString &projectPath); + void cleanupResources(); signals: void closingProject(); diff --git a/src/serviceConnector.cpp b/src/serviceConnector.cpp index fe95e0565a49a3085998a4d08c60c63ee55f6419..eb7e4aaaa6c65d880c3ab658636af1395e696c5e 100644 --- a/src/serviceConnector.cpp +++ b/src/serviceConnector.cpp @@ -31,7 +31,7 @@ #include <QJsonDocument> #include <QJsonObject> -QSharedPointer<QNetworkReply> ServiceConnector::fetchResource(const QString &url) +QByteArray ServiceConnector::fetchResource(const QString &url) { qDebug() << "Fetching resource from" << url; @@ -64,7 +64,7 @@ QSharedPointer<QNetworkReply> ServiceConnector::fetchResource(const QString &url else qDebug() << "Resource fetched successfully"; - return reply; + return reply->readAll(); } QByteArray ServiceConnector::fetchProject(const QString &url) @@ -76,26 +76,25 @@ QByteArray ServiceConnector::fetchProject(const QString &url) projectUrl.prepend("https://designviewer.qt.io/qmlprojects/"); } - auto reply = fetchResource(projectUrl); - if (reply->error() != QNetworkReply::NoError) - { - qCritical("Could not fetch project"); - return QByteArray(); - } + return fetchResource(projectUrl); +} - return reply->readAll(); +QByteArray ServiceConnector::fetchUserProject(const QString &userHash, const QString &projectName) +{ + const QString projectUrl = "https://designviewer.qt.io/qmlprojects/" + userHash + "/" + + projectName + ".qmlrc"; + return fetchResource(projectUrl); } QJsonArray ServiceConnector::fetchUserProjectList(const QString &userHash) { auto reply = fetchResource("https://designviewer.qt.io/api/v1/qmlrc/list/" + userHash + "?key=818815"); - if (reply->error() != QNetworkReply::NoError) - { + if (reply.size() == 0) { qCritical("Could not fetch available project list"); return QJsonArray(); } - QJsonArray projectList = QJsonDocument::fromJson(reply->readAll()) + QJsonArray projectList = QJsonDocument::fromJson(reply) .object() .value("data") .toObject() diff --git a/src/serviceConnector.h b/src/serviceConnector.h index b552806d758fb9651861c24f7a671f45a3349b95..cd9482329aee1d397445d90489a58cd329ab291a 100644 --- a/src/serviceConnector.h +++ b/src/serviceConnector.h @@ -34,11 +34,12 @@ class ServiceConnector : public QObject Q_OBJECT public: QByteArray fetchProject(const QString &url); + QByteArray fetchUserProject(const QString &userHash, const QString &projectName); QJsonArray fetchUserProjectList(const QString &userHash); private: QNetworkAccessManager m_manager; - QSharedPointer<QNetworkReply> fetchResource(const QString &url); + QByteArray fetchResource(const QString &url); signals: void downloadProgress(float percentage); diff --git a/ui/HomePage.qml b/ui/HomePage.qml index b57576675cb2a560e723fff8f821c93622b37387..4ed0c0264c06913eb29206fa48367886d0433700 100644 --- a/ui/HomePage.qml +++ b/ui/HomePage.qml @@ -47,32 +47,64 @@ Item { Layout.fillWidth: true } - ComboBox { - id: projectList + ColumnLayout { + id: column2 Layout.fillWidth: true - onCurrentIndexChanged: { - urlTextField.text = "https://designviewer.qt.io/qmlprojects/" - + backend.userHash + "/" - + textAt(currentIndex) - + ".qmlrc" - displayText = textAt(currentIndex) - } - onModelChanged: { - if (model.count > 0) { - currentIndex = 0 + ComboBox { + id: projectList + Layout.fillWidth: true + onCurrentIndexChanged: { + displayText = textAt(currentIndex) + downloadUserProject.enabled = true } + onModelChanged: { + if (model.count > 0) { + currentIndex = 0 + } + } + model: backend.projectList + down: false + displayText: "Select project..." + currentIndex: -1 + Layout.preferredHeight: 50 + } + + Button { + id: downloadUserProject + text: qsTr("Run Project") + onClicked: backend.runUserProject(projectList.currentText) + enabled: backend.userHash !== '' + Layout.fillWidth: true + Layout.alignment: Qt.AlignHCenter | Qt.AlignVCenter } - model: backend.projectList - down: false - displayText: "Select project..." - currentIndex: -1 - Layout.preferredHeight: 50 } + + Item { + id: item3 + Layout.fillHeight: true + Layout.fillWidth: true + Layout.preferredHeight: 10 + Layout.preferredWidth: 10 + } + + + ColumnLayout { id: column Layout.fillWidth: true + + Text { + id: downloadInstructions + text: qsTr("Enter project URL and then click the button below") + font.pixelSize: 12 + horizontalAlignment: Text.AlignHCenter + verticalAlignment: Text.AlignVCenter + wrapMode: Text.WordWrap + Layout.fillWidth: true + } + TextField { id: urlTextField horizontalAlignment: Text.AlignHCenter @@ -85,26 +117,20 @@ Item { id: rowLayout Button { id: downloadButton - text: qsTr("Download and Run") - onClicked: backend.runUserProject(urlTextField.text) + text: qsTr("Download and Run Project") + onClicked: backend.runOnlineProject(urlTextField.text) enabled: urlTextField.text !== '' Layout.fillWidth: true Layout.alignment: Qt.AlignHCenter | Qt.AlignVCenter } } - } - - Item { - id: item1 - Layout.fillHeight: true - Layout.fillWidth: true - Layout.preferredHeight: 10 - Layout.preferredWidth: 10 + Connections { + target: backend + function onUrlUpdated(newUrl){ + urlTextField.text = newUrl; + } + } } - - - - } }