From bf94b2185528307a93734bf498129684a92c03f5 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Burak=20Han=C3=A7erli?= <burak.hancerli@qt.io>
Date: Mon, 16 Oct 2023 12:33:23 +0000
Subject: [PATCH] QDS-10485 Implement QR code integration

---
 CMakeLists.txt              |  1 +
 android/AndroidManifest.xml | 54 +++++++++----------------------
 src/backend.cpp             | 63 +++++++++++++++++++++++++++++++++++--
 src/backend.h               |  9 ++++++
 src/importdummy.qml         | 35 +++++++++++++++++++++
 src/main.cpp                | 13 ++------
 ui/main.qml                 | 63 +++++++++++++++++++++++++++----------
 7 files changed, 170 insertions(+), 68 deletions(-)
 create mode 100644 src/importdummy.qml

diff --git a/CMakeLists.txt b/CMakeLists.txt
index 1df7557..16ba4fe 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -29,6 +29,7 @@ if(QT_VERSION VERSION_LESS QT_MINIMUM_VERSION)
 endif()
 
 qt_add_executable(${PROJECT_NAME}
+    src/importdummy.qml
     src/main.cpp
     src/backend.cpp src/backend.h
     ui/main.qml
diff --git a/android/AndroidManifest.xml b/android/AndroidManifest.xml
index f4f4c4b..ea54fe1 100644
--- a/android/AndroidManifest.xml
+++ b/android/AndroidManifest.xml
@@ -1,47 +1,23 @@
 <?xml version="1.0"?>
-<manifest xmlns:android="http://schemas.android.com/apk/res/android"
-    package="io.qt.qtdesignviewer"
-    android:installLocation="auto"
-    android:versionCode="1"
-    android:versionName="1.2">
+<manifest xmlns:android="http://schemas.android.com/apk/res/android" package="io.qt.qtdesignviewer" android:installLocation="auto" android:versionCode="1" android:versionName="1.2">
     <!-- %%INSERT_PERMISSIONS -->
     <!-- %%INSERT_FEATURES -->
-    <supports-screens
-        android:anyDensity="true"
-        android:largeScreens="true"
-        android:normalScreens="true"
-        android:smallScreens="true" />
-    <application
-        android:name="org.qtproject.qt.android.bindings.QtApplication"
-        android:extractNativeLibs="true"
-        android:hardwareAccelerated="true"
-        android:label="Qt Design Viewer"
-        android:icon="@mipmap/app_icon"
-        android:requestLegacyExternalStorage="true"
-        android:allowNativeHeapPointerTagging="false">
-        <activity
-            android:name="org.qtproject.qt.android.bindings.QtActivity"
-            android:configChanges="orientation|uiMode|screenLayout|screenSize|smallestScreenSize|layoutDirection|locale|fontScale|keyboard|keyboardHidden|navigation|mcc|mnc|density"
-            android:label="Qt Design Viewer"
-            android:launchMode="singleTop"
-            android:screenOrientation="unspecified"
-            android:exported="true">
+    <supports-screens android:anyDensity="true" android:largeScreens="true" android:normalScreens="true" android:smallScreens="true"/>
+    <application android:name="org.qtproject.qt.android.bindings.QtApplication" android:extractNativeLibs="true" android:hardwareAccelerated="true" android:label="Qt Design Viewer" android:requestLegacyExternalStorage="true" android:allowNativeHeapPointerTagging="false">
+        <activity android:name="org.qtproject.qt.android.bindings.QtActivity" android:configChanges="orientation|uiMode|screenLayout|screenSize|smallestScreenSize|layoutDirection|locale|fontScale|keyboard|keyboardHidden|navigation|mcc|mnc|density" android:label="Qt Design Viewer" android:launchMode="singleTop" android:screenOrientation="unspecified" android:exported="true">
             <intent-filter>
-                <action android:name="android.intent.action.MAIN" />
-                <category android:name="android.intent.category.LAUNCHER" />
+                <action android:name="android.intent.action.MAIN"/>
+                <category android:name="android.intent.category.LAUNCHER"/>
             </intent-filter>
-
-            <meta-data
-                android:name="android.app.lib_name"
-                android:value="-- %%INSERT_APP_LIB_NAME%% --" />
-
-            <meta-data
-                android:name="android.app.arguments"
-                android:value="-- %%INSERT_APP_ARGUMENTS%% --" />
-
-            <meta-data
-                android:name="android.app.extract_android_style"
-                android:value="minimal" />
+            <intent-filter>
+                <action android:name="android.intent.action.VIEW"/>
+                <category android:name="android.intent.category.DEFAULT"/>
+                <category android:name="android.intent.category.BROWSABLE"/>
+                <data android:scheme="qtdesignviewer"/>
+            </intent-filter>
+            <meta-data android:name="android.app.lib_name" android:value="-- %%INSERT_APP_LIB_NAME%% --"/>
+            <meta-data android:name="android.app.arguments" android:value="-- %%INSERT_APP_ARGUMENTS%% --"/>
+            <meta-data android:name="android.app.extract_android_style" android:value="minimal"/>
         </activity>
     </application>
 </manifest>
diff --git a/src/backend.cpp b/src/backend.cpp
index 0f08497..d946eaf 100644
--- a/src/backend.cpp
+++ b/src/backend.cpp
@@ -24,29 +24,38 @@
 ****************************************************************************/
 
 #include "backend.h"
+#include "qapplication.h"
 
 #if !defined(Q_OS_WASM)
 
 #include <QBuffer>
+#include <QDesktopServices>
 #include <QDirIterator>
 #include <QEventLoop>
 #include <QFileInfo>
 #include <QGuiApplication>
+#include <QJsonArray>
+#include <QJsonDocument>
+#include <QJsonObject>
 #include <QMessageBox>
 #include <QNetworkReply>
 #include <QQuickItem>
 #include <QRandomGenerator>
 #include <QRegularExpression>
 #include <QResource>
+#include <QSettings>
 #include <QSslSocket>
 #include <QTemporaryDir>
 #include <QTemporaryFile>
+#include <QtCore/private/qandroidextras_p.h>
 #include <QtGui/private/qzipreader_p.h>
 
 #define QSTRN QString::number
 
 Backend::Backend(QObject *parent)
 {
+    QDesktopServices::setUrlHandler("qtdesignviewer", this, "registerUser");
+
     const QRect screenGeometry = QGuiApplication::primaryScreen()->geometry();
 
     printLog("Qt Design Viewer");
@@ -56,9 +65,25 @@ Backend::Backend(QObject *parent)
     printLog("-- Screen height: " + QSTRN(screenGeometry.height()));
     printLog("-- Screen width: " + QSTRN(screenGeometry.width()));
 
-    m_buildInfo = QCoreApplication::applicationVersion() + "\n" + "Qt " + QString(QT_VERSION_STR)
-                  + " - OpenSSL support: " + QVariant(QSslSocket::supportsSsl()).toString();
+#ifdef QT_DEBUG
+    QString buildType = "Debug";
+#else
+    QString buildType = "Release";
+#endif
+    m_buildInfo = QCoreApplication::applicationVersion() + "\nQt " + QString(QT_VERSION_STR) + " - "
+                  + buildType + " Build"
+                  + "\nOpenSSL support: " + QVariant(QSslSocket::supportsSsl()).toString();
     emit buildInfoChanged();
+
+    QSettings settings;
+    m_userHash = settings.value("user/hash").toString();
+    if (m_userHash.isEmpty())
+        printLog("User Hash is not registered. Scan QR code to register.");
+    else {
+        printLog("User Hash: " + m_userHash);
+        updateUserProjectList();
+    }
+
     printLog("Initialization complete");
 }
 
@@ -424,4 +449,38 @@ void Backend::downloadAndRun(const QString &url)
     showAppWindow();
 }
 
+void Backend::registerUser(const QUrl &url)
+{
+    const QString userHash = url.toString().remove("qtdesignviewer://");
+    printLog("Registering User Hash: " + userHash);
+    m_userHash = userHash;
+    QSettings().setValue("user/hash", m_userHash);
+    updateUserProjectList();
+}
+
+void Backend::updateUserProjectList()
+{
+    printLog("Fetching available project list for user: " + m_userHash);
+    auto reply = fetchResource("https://designviewer.qt.io/api/v1/qmlrc/list/" + m_userHash
+                               + "?key=818815");
+    if (reply->error() != QNetworkReply::NoError) {
+        printErr("Could not fetch available project list");
+        return;
+    }
+
+    QJsonArray projectList = QJsonDocument::fromJson(reply->readAll())
+                                 .object()
+                                 .value("data")
+                                 .toObject()
+                                 .value("packages")
+                                 .toArray();
+    printLog("List of available projects fetched:");
+    for (const auto &project : projectList) {
+        const QString projectName{project.toObject().value("appName").toString()};
+        printLog("-- " + projectName);
+        m_projectList << projectName;
+    }
+    emit projectListChanged();
+}
+
 #endif // !defined(Q_OS_WASM)
diff --git a/src/backend.h b/src/backend.h
index be1ccbb..7d4a634 100644
--- a/src/backend.h
+++ b/src/backend.h
@@ -40,18 +40,23 @@ class Backend : public QObject
     Q_PROPERTY(QString logs READ logs NOTIFY logsChanged)
     Q_PROPERTY(QString buildInfo READ buildInfo NOTIFY buildInfoChanged)
     Q_PROPERTY(int downloadProgress READ downloadProgress NOTIFY downloadProgressChanged FINAL)
+    Q_PROPERTY(QStringList projectList READ projectList NOTIFY projectListChanged FINAL)
+    Q_PROPERTY(QString userHash READ userHash FINAL)
 
 public:
     explicit Backend(QObject *parent = nullptr);
     QString logs() const { return m_logs; }
     QString buildInfo() const { return m_buildInfo; }
     int downloadProgress() const { return m_downloadProgress; }
+    QStringList projectList() const { return m_projectList; }
+    QString userHash() const { return m_userHash; }
 
 private:
     // UI data
     QString m_logs;
     QString m_buildInfo;
     int m_downloadProgress = 0;
+    QStringList m_projectList;
 
     // Qml related members
     QQmlEngine m_qmlEngine;
@@ -62,6 +67,7 @@ private:
     QNetworkAccessManager m_nam;
     QByteArray m_projectData;
     QString m_projectPath;
+    QString m_userHash;
 
     // member logger functions
     void printLog(const QString &message);
@@ -76,14 +82,17 @@ private:
     QString findFile(const QString &dir, const QString &filter);
     void parseQmlprojectFile(const QString &fileName, QString *mainFile, QStringList *importPaths);
     bool runProject(const QByteArray &projectData, const QString &projectName);
+    void updateUserProjectList();
 
 signals:
     void logsChanged();
     void buildInfoChanged();
     void downloadProgressChanged();
+    void projectListChanged();
 
 public slots:
     void downloadAndRun(const QString &url);
+    void registerUser(const QUrl &url);
 
 private slots:
     void orientateWindow(Qt::ScreenOrientation orientation);
diff --git a/src/importdummy.qml b/src/importdummy.qml
new file mode 100644
index 0000000..085643d
--- /dev/null
+++ b/src/importdummy.qml
@@ -0,0 +1,35 @@
+// Hack to force the qml plugins to be linked statically
+
+import QtQuick
+import QtQuick.Controls
+
+import QtQml
+import QtQml.Models
+import QtQml.StateMachine
+
+import QtQuick3D
+import QtQuick3D.AssetUtils
+import QtQuick3D.Effects
+import QtQuick3D.Helpers
+import QtQuick3D.ParticleEffects
+import QtQuick3D.Particles3D
+import QtQuick.VirtualKeyboard
+
+import QtQuick.Studio.Application
+import QtQuick.Studio.Components
+import QtQuick.Studio.Effects
+import QtQuick.Studio.EventSimulator
+import QtQuick.Studio.EventSystem
+import QtQuick.Studio.LogicHelper
+import QtQuick.Studio.MultiText
+
+import QtQuickUltralite.Extras
+import QtQuickUltralite.Layers
+
+import FlowView
+
+ApplicationWindow {
+    visible: true
+    width: 640
+    height: 480
+}
diff --git a/src/main.cpp b/src/main.cpp
index e9d8987..148e358 100644
--- a/src/main.cpp
+++ b/src/main.cpp
@@ -32,15 +32,10 @@
 
 int main(int argc, char *argv[])
 {
-    qDebug().noquote() << QString("Built on %1 %2\n").arg(__DATE__, __TIME__);
-
-    QSurfaceFormat format = QSurfaceFormat::defaultFormat();
-    format.setVersion(3, 0);
-    QSurfaceFormat::setDefaultFormat(format);
-
-    QCoreApplication::setApplicationVersion(QString("Built on %1 %2").arg(__DATE__, __TIME__));
     QApplication app(argc, argv);
+    QApplication::setOrganizationName("Qt");
     QApplication::setApplicationName(QStringLiteral("Qt Design Viewer"));
+    QApplication::setApplicationVersion(QString("Built on %1 %2").arg(__DATE__, __TIME__));
 
     Backend backend;
 
@@ -50,9 +45,5 @@ int main(int argc, char *argv[])
     view.setResizeMode(QQuickView::SizeRootObjectToView);
     view.showMaximized();
 
-    QThread::msleep(2000);
-
-    //    backend.downloadAndRun("https://designviewer.qt.io/#17e8907b3b84b8206d45be4f551f4e25/"
-    //                           "MaterialBundle.qmlrc");
     return app.exec();
 }
diff --git a/ui/main.qml b/ui/main.qml
index 2266462..d034acb 100644
--- a/ui/main.qml
+++ b/ui/main.qml
@@ -1,10 +1,4 @@
 
-/*
-This is a UI file (.ui.qml) that is intended to be edited in Qt Design Studio only.
-It is supposed to be strictly declarative and only uses a subset of QML. If you edit
-this file manually, you might introduce QML code that is not supported by Qt Design Studio.
-Check out https://doc.qt.io/qtcreator/creator-quick-ui-forms.html for details on .ui.qml files.
-*/
 import QtQuick 6.4
 import QtQuick.Controls 6.4
 import QtQuick.Controls.Material
@@ -52,11 +46,17 @@ Rectangle {
             placeholderText: qsTr("Enter URL")
         }
 
-        Button {
-            id: downloadButton
-            text: qsTr("Download and Run")
-            Layout.alignment: Qt.AlignHCenter | Qt.AlignVCenter
-            onClicked: backend.downloadAndRun(urlTextField.text)
+        RowLayout{
+            id: rowLayout
+            anchors.left: parent.left
+            anchors.right: parent.right
+            Button {
+                id: downloadButton
+                Layout.fillWidth: true
+                text: qsTr("Download and Run")
+                Layout.alignment: Qt.AlignHCenter | Qt.AlignVCenter
+                onClicked: backend.downloadAndRun(urlTextField.text)
+            }
         }
     }
 
@@ -65,7 +65,6 @@ Rectangle {
         width: 351
         height: gridLayout.height
         anchors.top: parent.top
-        anchors.topMargin: 12
         anchors.horizontalCenter: parent.horizontalCenter
 
         GridLayout {
@@ -77,7 +76,7 @@ Rectangle {
             Image {
                 id: qdsicon
                 x: 47
-                y: -23
+                y: -48
                 width: 204
                 height: 202
                 source: "content/images/dvicon.png"
@@ -109,23 +108,52 @@ Rectangle {
         }
     }
 
+    ComboBox {
+        id: projectList
+        height: 50
+        anchors.left: parent.left
+        anchors.right: parent.right
+        anchors.top: header.bottom
+        down: false
+        anchors.topMargin: 15
+        anchors.rightMargin: 22
+        anchors.leftMargin: 22
+        displayText: "Select project..."
+        model: backend.projectList
+        currentIndex: -1
+        onCurrentIndexChanged: {
+            urlTextField.text = "https://designviewer.qt.io/qmlprojects/"
+                    + backend.userHash + "/"
+                    + textAt(currentIndex)
+                    + ".qmlrc"
+            displayText = textAt(currentIndex)
+        }
+    }
+
     Rectangle {
         id: log
-        visible: root.height > 620
+        visible: true
         color: "#EAEAEA"
         anchors.left: parent.left
         anchors.right: parent.right
-        anchors.top: header.bottom
+        anchors.top: projectList.bottom
         anchors.bottom: bar.top
-        anchors.topMargin: 15
         anchors.bottomMargin: 24
         anchors.leftMargin: 22
 
         ScrollView {
             id: scrollArea
+            visible: true
             anchors.fill: parent
             topPadding: 13.1
             ScrollBar.vertical.policy: ScrollBar.AlwaysOn
+            Connections {
+                target: backend
+                function onLogsChanged() {
+                    logTextArea.text = backend.logs
+                    scrollArea.ScrollBar.vertical.position = 1.0 - scrollArea.ScrollBar.vertical.size
+                }
+            }
             TextArea {
                 clip: false
                 id: logTextArea
@@ -139,4 +167,7 @@ Rectangle {
             }
         }
     }
+
+
+
 }
-- 
GitLab