From 426e9ac5dd894a303b92237388f3837ab79d46bf Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Burak=20Han=C3=A7erli?= <burak.hancerli@qt.io>
Date: Thu, 1 Feb 2024 11:46:33 +0000
Subject: [PATCH] Spyrosoft Feedbacks

---
 3rdparty/qtquickdesigner-components |   2 +-
 CMakeLists.txt                      |  12 +-
 android/AndroidManifest.xml         |   2 +-
 src/backend.cpp                     | 256 ++++++++++++++++++++--------
 src/backend.h                       |  27 +--
 src/main.cpp                        |  26 +--
 src/projectManager.cpp              |   6 +-
 src/projectManager.h                |   3 +-
 src/serviceConnector.cpp            |  36 ++--
 src/serviceConnector.h              |   2 +-
 ui/HomePage.qml                     |  33 ++--
 ui/SettingsPage.qml                 |  46 ++---
 ui/main.qml                         |  30 ++--
 13 files changed, 285 insertions(+), 196 deletions(-)

diff --git a/3rdparty/qtquickdesigner-components b/3rdparty/qtquickdesigner-components
index de28bdb..5972175 160000
--- a/3rdparty/qtquickdesigner-components
+++ b/3rdparty/qtquickdesigner-components
@@ -1 +1 @@
-Subproject commit de28bdbdfb1a9393ca87febafb319125e5578d11
+Subproject commit 59721758afe9d7034ca76f1a19c3c5c97610ceaa
diff --git a/CMakeLists.txt b/CMakeLists.txt
index 9c2f4ad..e027714 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -13,13 +13,13 @@ set(CMAKE_CXX_STANDARD_REQUIRED ON)
 
 find_package(
     QT NAMES Qt6
-    COMPONENTS Core
+    COMPONENTS Core Widgets Quick Gui Qml Multimedia MultimediaWidgets Concurrent
     REQUIRED
 )
 
 find_package(
     Qt6
-    COMPONENTS Core Widgets Quick Gui Qml Multimedia MultimediaWidgets
+    COMPONENTS Core Widgets Quick Gui Qml Multimedia MultimediaWidgets Concurrent
     REQUIRED
 )
 
@@ -44,9 +44,11 @@ qt_add_executable(${PROJECT_NAME}
 )
 
 target_link_libraries(${PROJECT_NAME} PRIVATE
-    Qt::Core Qt::Widgets
-    Qt::Quick Qt::Gui
-    Qt::Qml Qt::GuiPrivate Qt::Multimedia Qt::MultimediaWidgets
+    Qt6::Core Qt6::Widgets
+    Qt6::Quick Qt6::Gui
+    Qt6::Qml Qt6::GuiPrivate
+    Qt6::Multimedia Qt6::MultimediaWidgets
+    Qt6::Concurrent
     ZXing::ZXing
 )
 
diff --git a/android/AndroidManifest.xml b/android/AndroidManifest.xml
index 0bfd19b..1251571 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="23" android:versionName="1.2">
+    android:installLocation="auto" android:versionCode="24" 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 800d3b4..639f3ea 100644
--- a/src/backend.cpp
+++ b/src/backend.cpp
@@ -28,13 +28,17 @@
 
 #include <QCameraDevice>
 #include <QDesktopServices>
+#include <QElapsedTimer>
 #include <QFileInfo>
 #include <QJsonArray>
 #include <QJsonObject>
 #include <QMediaDevices>
+#include <QMessageBox>
+#include <QPermission>
 #include <QSettings>
 #include <QSslSocket>
 #include <QVideoSink>
+#include <QtConcurrent>
 
 #include "../3rdparty/zxing-cpp/example/ZXingQtReader.h"
 
@@ -52,6 +56,11 @@ Backend::Backend(QObject *parent)
             &Backend::initDesignStudioConnector,
             Qt::DirectConnection);
     m_dsConnectorThread.start();
+
+    connect(&m_serviceConnector,
+            &ServiceConnector::downloadProgress,
+            this,
+            &Backend::downloadProgress);
 }
 
 void Backend::initialize()
@@ -76,48 +85,126 @@ void Backend::initialize()
                               + QVariant(QSslSocket::supportsSsl()).toString();
     emit buildInfoChanged(buildInfo);
 
-    QSettings settings;
-    m_userHash = settings.value("user/hash").toString();
-    if (m_userHash.isEmpty())
+    if (userHash().isEmpty())
         qDebug("User Hash is not registered. Scan QR code to register.");
     else {
-        qDebug() << "User Hash: " << m_userHash;
+        qDebug() << "User Hash: " << userHash();
         updateUserProjectList();
     }
 
     // Check if updateInBackground is enabled
-    updateInBackground(settings.value("system/updateInBackground").toBool());
+    enableBackgroundUpdate(updateInBackground());
     qDebug() << "Initialization complete";
 }
 
-void Backend::updateInBackground(const bool &enabled)
+void Backend::enableBackgroundUpdate(const bool &enabled)
 {
-    QSettings().setValue("system/updateInBackground", enabled);
     if (enabled) {
-        qDebug() << "Update in background is enabled";
         m_backgroundTimer.setInterval(1000 * 10);
+
         connect(&m_backgroundTimer, &QTimer::timeout, this, [&] {
             qDebug() << "Checking for updates in background";
-            if (m_userHash.isEmpty())
+
+            const QString userHash = Backend::userHash();
+            if (userHash.isEmpty())
                 return;
 
-            m_serviceConnector.reset(new ServiceConnector);
-            QJsonArray projectList = m_serviceConnector->fetchUserProjectList(m_userHash);
-            if (projectList.isEmpty())
+            QJsonArray projectList = m_serviceConnector.fetchUserProjectList(userHash);
+            if (projectList.isEmpty()) {
+                qWarning() << "Could not fetch project list. Please check your internet "
+                              "connection and try again";
                 return;
+            }
 
             qDebug() << "New projects available. Updating project list";
             updateUserProjectList();
         });
+
         m_backgroundTimer.start();
     } else {
-        qDebug() << "Update in background is disabled";
         m_backgroundTimer.stop();
     }
 }
 
+void Backend::setUpdateInBackground(const bool &enabled)
+{
+    QSettings().setValue("system/updateInBackground", enabled);
+    if (enabled) {
+        qDebug() << "Update in background is enabled";
+    } else {
+        qDebug() << "Update in background is disabled";
+    }
+    enableBackgroundUpdate(enabled);
+}
+
+void Backend::setAutoScaleProject(const bool &enabled)
+{
+    QSettings().setValue("system/autoScaleProject", enabled);
+    if (enabled) {
+        qDebug() << "Auto scale project is enabled";
+    } else {
+        qDebug() << "Auto scale project is disabled";
+    }
+}
+
+void Backend::setUserHash(const QString &userHash)
+{
+    QSettings().setValue("user/hash", userHash);
+    updateUserProjectList();
+}
+
+bool Backend::updateInBackground()
+{
+    return QSettings().value("system/updateInBackground", false).toBool();
+}
+
+bool Backend::autoScaleProject()
+{
+    return QSettings().value("system/autoScaleProject", true).toBool();
+}
+
+QString Backend::userHash()
+{
+    return QSettings().value("user/hash").toString();
+}
+
+void Backend::updatePopup(const QString &text, bool indeterminate)
+{
+    emit popupTextChanged(text);
+    emit popupProgressIndeterminateChanged(indeterminate);
+    QEventLoop().processEvents(QEventLoop::AllEvents, 1000);
+}
+
 void Backend::scanQrCode()
 {
+    // request permissions
+    QCameraPermission permission;
+    QCoreApplication &app = *QCoreApplication::instance();
+    switch (app.checkPermission(permission)) {
+    case Qt::PermissionStatus::Granted:
+        openCamera();
+        break;
+    case Qt::PermissionStatus::Undetermined:
+    case Qt::PermissionStatus::Denied:
+        app.requestPermission(permission, [this](const QPermission &permission) {
+            if (permission.status() == Qt::PermissionStatus::Denied) {
+                QMessageBox msgBox{QMessageBox::Critical,
+                                   "Critical:",
+                                   "Camera permission denied",
+                                   QMessageBox::Ok};
+                msgBox.exec();
+                return;
+            }
+
+            openCamera();
+        });
+        break;
+    }
+}
+
+void Backend::openCamera()
+{
+    // start camera
     m_captureSession.reset(new QMediaCaptureSession(this));
     m_captureSession->setCamera(new QCamera(this));
     m_captureSession->setVideoOutput(new CustomVideoWidget);
@@ -132,22 +219,60 @@ void Backend::scanQrCode()
             this,
             [&](const QVideoFrame &frame) {
                 static int i = 0;
-                if (i++ < 30) {
+                static QAtomicInt running = 0;
+
+                if (i++ < 20 || running > 0) {
                     return;
                 }
 
                 i = 0;
 
-                auto results = ReadBarcodes(frame.toImage());
-                for (auto &result : results) {
+                QtConcurrent::run([=] {
+                    // lock the thread so that only one barcode can be read at a time
+                    running++;
+
+                    QElapsedTimer timer;
+                    timer.start();
+                    QList<Result> results = ReadBarcodes(frame.toImage());
+                    qDebug() << "Barcode detection took" << timer.elapsed() << "ms";
+
+                    // release the lock so that another barcode can be read
+                    running--;
+                    return results;
+                }).then([=](const QList<Result> &results) {
+                    if (results.isEmpty() || !m_captureSession)
+                        return;
+
+                    qDebug() << "Stopping camera";
+                    qobject_cast<CustomVideoWidget *>(m_captureSession->videoOutput())->close();
+                    qDebug() << "Camera stopped";
+
+                    Result result = results.first();
+
                     qDebug() << "Text:   " << result.text();
                     qDebug() << "Format: " << result.format();
                     qDebug() << "Content:" << result.contentType();
-                    parseDesignViewerUrl(result.text());
-                    qobject_cast<CustomVideoWidget *>(m_captureSession->videoOutput())->close();
-                }
+
+                    // we have to use invokeMethod because we are in a different thread
+                    // then where the serviceConnector is created
+                    QMetaObject::invokeMethod(this,
+                                              "parseDesignViewerUrl",
+                                              Qt::QueuedConnection,
+                                              Q_ARG(QUrl, result.text()));
+                });
             });
 
+    // stop camera when app is not active.
+    // i.e. when the user switches to another app or the screen is locked
+    QGuiApplication *app(qobject_cast<QGuiApplication *>(QCoreApplication::instance()));
+    connect(app, &QGuiApplication::applicationStateChanged, this, [&](Qt::ApplicationState state) {
+        if (state != Qt::ApplicationState::ApplicationActive && m_captureSession) {
+            qDebug() << "Application is not active. Stopping camera";
+            qobject_cast<CustomVideoWidget *>(m_captureSession->videoOutput())->close();
+        }
+    });
+
+    // stop camera when video widget is closed
     connect(videoWidget, &CustomVideoWidget::closed, this, [&] {
         qDebug() << "Video widget closed. Clearing capture session";
         m_captureSession->camera()->stop();
@@ -156,52 +281,21 @@ void Backend::scanQrCode()
         m_captureSession.clear();
     });
 
+    videoWidget->setWindowFlags(Qt::Window | Qt::WindowStaysOnTopHint);
     videoWidget->show();
     m_captureSession->camera()->start();
 }
 
-void Backend::cacheDemoProjects(const bool &enabled)
-{
-    QSettings().setValue("system/cacheDemoProjects", enabled);
-    if (enabled) {
-        qDebug() << "Caching demo projects is enabled";
-    } else {
-        qDebug() << "Caching demo projects is disabled";
-    }
-}
-void Backend::cacheUserProjects(const bool &enabled)
-{
-    QSettings().setValue("system/cacheUserProjects", enabled);
-    if (enabled) {
-        qDebug() << "Caching user projects is enabled";
-    } else {
-        qDebug() << "Caching user projects is disabled";
-    }
-}
-
-void Backend::updatePopup(const QString &text, bool indeterminate)
-{
-    emit popupTextChanged(text);
-    emit popupProgressIndeterminateChanged(indeterminate);
-    QEventLoop().processEvents(QEventLoop::AllEvents, 1000);
-}
-
 void Backend::initializeProjectManager()
 {
     if (m_projectManager)
         return;
 
-    m_projectManager.reset(new ProjectManager);
+    m_projectManager.reset(new ProjectManager(autoScaleProject()));
     connect(m_projectManager.data(), &ProjectManager::closingProject, this, [&] {
         emit popupClose();
         m_projectManager.reset();
     });
-
-    m_serviceConnector.reset(new ServiceConnector);
-    connect(m_serviceConnector.data(),
-            &ServiceConnector::downloadProgress,
-            this,
-            &Backend::downloadProgress);
 }
 
 void Backend::initDesignStudioConnector()
@@ -255,7 +349,14 @@ void Backend::runDemoProject(const QString &projectName)
 
     // sample project info
     // [{"lastUpdate":1701947766739.9812,"name":"ClusterTutorial.qmlrc"}]
-    const QJsonArray projectList = m_serviceConnector->fetchUserDemoList();
+    const QJsonArray projectList = m_serviceConnector.fetchUserDemoList();
+
+    if (projectList.isEmpty()) {
+        qCritical("Please check your internet connection and try again");
+        emit popupClose();
+        return;
+    }
+
     QJsonObject projectInfo;
     for (auto project : projectList) {
         if (projectName == project.toObject().value("name").toString().remove(".qmlrc")) {
@@ -271,7 +372,7 @@ void Backend::runDemoProject(const QString &projectName)
 
         const QString url = "https://designviewer.qt.io/qmlprojects/demos/" + 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...");
@@ -314,8 +415,16 @@ void Backend::runUserProject(const QString &projectName)
 
     qDebug() << "Running user project:" << projectName;
 
+    const QString userHash = Backend::userHash();
+
     qDebug("Checking if project is cached. Getting list of available projects...");
-    const QJsonArray projectList = m_serviceConnector->fetchUserProjectList(m_userHash);
+    const QJsonArray projectList = m_serviceConnector.fetchUserProjectList(userHash);
+
+    if (projectList.isEmpty()) {
+        qCritical("Please check your internet connection and try again");
+        emit popupClose();
+        return;
+    }
 
     QString projectLastModified;
     QString projectId;
@@ -332,7 +441,7 @@ void Backend::runUserProject(const QString &projectName)
     if (!projectCached) {
         qDebug("Project is not cached. Downloading...");
         updatePopup("Project is not cached. Downloading...", false);
-        QByteArray projectData = m_serviceConnector->fetchUserProject(m_userHash, projectName);
+        QByteArray projectData = m_serviceConnector.fetchUserProject(userHash, projectName);
 
         updatePopup("Caching user project...");
         if (!m_projectManager->cacheProject(projectData, projectInfo)) {
@@ -359,7 +468,7 @@ void Backend::runOnlineProject(const QString &url)
     initializeProjectManager();
     emit popupOpen();
     updatePopup("Downloading...", false);
-    QByteArray projectData = m_serviceConnector->fetchProject(url);
+    QByteArray projectData = m_serviceConnector.fetchProject(url);
     if (projectData.isEmpty()) {
         qCritical() << "Could not download project. Please check the logs for more information.";
         return;
@@ -378,21 +487,19 @@ void Backend::runOnlineProject(const QString &url)
     emit popupClose();
 }
 
-void Backend::registerUser(const QUrl &url)
+void Backend::updateUserProjectList()
 {
-    const QString userHash = url.toString().remove("qtdesignviewer://");
-    qDebug() << "Registering User Hash: " << userHash;
-    m_userHash = userHash;
-    QSettings().setValue("user/hash", m_userHash);
     emit userRegistered();
-    updateUserProjectList();
-}
 
-void Backend::updateUserProjectList()
-{
-    qDebug() << "Fetching available project list for user: " << m_userHash;
-    m_serviceConnector.reset(new ServiceConnector);
-    QJsonArray projectList = m_serviceConnector->fetchUserProjectList(m_userHash);
+    const QString userHash = Backend::userHash();
+
+    if (userHash.isEmpty()) {
+        qWarning("User hash is not registered");
+        return;
+    }
+
+    qDebug() << "Fetching available project list for user:" << userHash;
+    QJsonArray projectList = m_serviceConnector.fetchUserProjectList(userHash);
 
     if (projectList.isEmpty()) {
         qCritical("Could not fetch available project list");
@@ -411,16 +518,17 @@ void Backend::updateUserProjectList()
         qDebug() << "--" << projectName;
         m_projectList << projectName;
     }
+
     emit projectListChanged();
 }
 
 void Backend::parseDesignViewerUrl(const QUrl &url)
 {
-    QString urlData = url.toString().remove("qtdesignviewer://");
+    QString urlData = url.toString();
     // 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 url: https://<url>/<project_name>.qmlrc
     // sample user hash: qtdesignviewer://17e8907b3b84029384hs8djshdu38476
     if (urlData.isEmpty())
         return;
@@ -429,8 +537,10 @@ void Backend::parseDesignViewerUrl(const QUrl &url)
         emit urlUpdated(urlData);
     } else if (urlData.startsWith("https://")) {
         emit urlUpdated(urlData);
-    } else {
+    } else if (urlData.startsWith("qtdesignviewer://")) {
         qDebug() << "Registering user from QR code";
-        registerUser(url);
+        setUserHash(url.toString().remove("qtdesignviewer://"));
+    } else {
+        qWarning() << "Unknown QR code data: " << urlData;
     }
 }
diff --git a/src/backend.h b/src/backend.h
index d4c08ef..3363acd 100644
--- a/src/backend.h
+++ b/src/backend.h
@@ -59,7 +59,6 @@ class Backend : public QObject
 {
     Q_OBJECT
     Q_PROPERTY(QStringList projectList READ projectList NOTIFY projectListChanged FINAL)
-    Q_PROPERTY(QString userHash READ userHash FINAL)
 
 public:
     explicit Backend(QObject *parent = nullptr);
@@ -71,7 +70,6 @@ public:
     }
 
     QStringList projectList() const { return m_projectList; }
-    QString userHash() const { return m_userHash; }
 
 private:
     // UI data
@@ -80,15 +78,17 @@ private:
     QJsonArray m_projectListArray;
 
     // Other members
-    QString m_userHash;
-    QScopedPointer<ServiceConnector> m_serviceConnector;
+    ServiceConnector m_serviceConnector;
     QScopedPointer<ProjectManager> m_projectManager;
     QScopedPointer<DesignStudioConnector> m_designStudioConnector;
     QThread m_dsConnectorThread;
 
-    QTimer m_backgroundTimer;
+    // QR code scanner
     QSharedPointer<QMediaCaptureSession> m_captureSession;
 
+    // Settings
+    QTimer m_backgroundTimer;
+
     // member functions
     void updateUserProjectList();
     void updatePopup(const QString &text, bool indeterminate = true);
@@ -106,9 +106,11 @@ signals:
     void userRegistered();
     void networkUpdated(QString);
     void urlUpdated(QString);
+    void userHashChanged(QString);
 
 public slots:
     void scanQrCode();
+    void openCamera();
 
     void runOnlineProject(const QString &url);
     void runUserProject(const QString &projectName);
@@ -118,15 +120,20 @@ public slots:
     void initDesignStudioConnector();
 
     void parseDesignViewerUrl(const QUrl &url);
-    void registerUser(const QUrl &url);
 
-    // settings
-    void updateInBackground(const bool &enabled);
-    void cacheDemoProjects(const bool &enabled);
-    void cacheUserProjects(const bool &enabled);
+    // settings - setters
+    void setUpdateInBackground(const bool &enabled);
+    void setAutoScaleProject(const bool &enabled);
+    void setUserHash(const QString &userHash);
+
+    // settings - getters
+    bool updateInBackground();
+    bool autoScaleProject();
+    QString userHash();
 
 private slots:
     void initializeProjectManager();
+    void enableBackgroundUpdate(const bool &enabled);
 };
 
 #endif // DV_ANDROID_H
diff --git a/src/main.cpp b/src/main.cpp
index a4cc540..2703572 100644
--- a/src/main.cpp
+++ b/src/main.cpp
@@ -27,7 +27,6 @@
 
 #include <QApplication>
 #include <QMessageBox>
-#include <QPermission>
 #include <QQmlContext>
 
 #include "backend.h"
@@ -65,8 +64,10 @@ 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));
 
-    // only show critical and fatal messages in the UI
-    if (type == QtCriticalMsg || type == QtFatalMsg) {
+    if (type == QtWarningMsg) {
+        if (backend)
+            backend->setLogs(appLogs += newLog);
+    } else if (type == QtCriticalMsg || type == QtFatalMsg) {
         QMessageBox msgBox{QMessageBox::Critical, "Critical:", msg, QMessageBox::Ok};
         msgBox.exec();
         if (backend)
@@ -85,25 +86,6 @@ int main(int argc, char *argv[])
     QQuickView view;
     backend = new Backend();
 
-    // request permissions
-    QCameraPermission permission;
-    switch (app.checkPermission(permission)) {
-    case Qt::PermissionStatus::Granted:
-        break;
-    case Qt::PermissionStatus::Undetermined:
-    case Qt::PermissionStatus::Denied:
-        app.requestPermission(permission, [](const QPermission &permission) {
-            if (permission.status() == Qt::PermissionStatus::Denied) {
-                QMessageBox msgBox{QMessageBox::Critical,
-                                   "Critical:",
-                                   "Camera permission denied",
-                                   QMessageBox::Ok};
-                msgBox.exec();
-            }
-        });
-        break;
-    }
-
     view.engine()->rootContext()->setContextProperty("backend", backend);
     view.setSource(QUrl(QStringLiteral("qrc:/main.qml")));
     view.setResizeMode(QQuickView::SizeRootObjectToView);
diff --git a/src/projectManager.cpp b/src/projectManager.cpp
index 1b3867c..b60006e 100644
--- a/src/projectManager.cpp
+++ b/src/projectManager.cpp
@@ -46,8 +46,9 @@
 #include <QtGui/private/qzipreader_p.h>
 #endif
 
-ProjectManager::ProjectManager(QObject *parent)
+ProjectManager::ProjectManager(const bool &autoScaleProject, QObject *parent)
     : QObject(parent)
+    , m_autoScaleProject(autoScaleProject)
     , m_projectCachePath(QStandardPaths::writableLocation(QStandardPaths::AppDataLocation))
     , m_demoProjectCachePath(m_projectCachePath + "/demoProjects")
 {
@@ -512,6 +513,9 @@ bool ProjectManager::runDemoProject(const QString &projectName)
 
 void ProjectManager::orientateWindow(Qt::ScreenOrientation orientation)
 {
+    if (!m_autoScaleProject)
+        return;
+
     QQuickItem *contentItem = m_quickWindow->contentItem();
     QQuickItem *childItem{contentItem->childItems().at(0)};
 
diff --git a/src/projectManager.h b/src/projectManager.h
index 3b86a8e..9cc3b95 100644
--- a/src/projectManager.h
+++ b/src/projectManager.h
@@ -37,7 +37,7 @@ class ProjectManager : public QObject
 {
     Q_OBJECT
 public:
-    explicit ProjectManager(QObject *parent = nullptr);
+    explicit ProjectManager(const bool &autoScaleProject = true, QObject *parent = nullptr);
     ~ProjectManager();
 
     QString unpackProject(const QByteArray &project, bool extractZip = false);
@@ -64,6 +64,7 @@ private:
     QString m_projectPath;
     const QString m_projectCachePath;
     const QString m_demoProjectCachePath;
+    const bool m_autoScaleProject;
 
     // Qml related members
     QScopedPointer<QQmlEngine> m_qmlEngine;
diff --git a/src/serviceConnector.cpp b/src/serviceConnector.cpp
index f19d8ab..01ec60a 100644
--- a/src/serviceConnector.cpp
+++ b/src/serviceConnector.cpp
@@ -31,7 +31,7 @@
 #include <QJsonDocument>
 #include <QJsonObject>
 
-QByteArray ServiceConnector::fetchResource(const QString &url)
+QByteArray ServiceConnector::fetchResource(const QString &url, const bool quite)
 {
     qDebug() << "Fetching resource from" << url;
 
@@ -41,9 +41,11 @@ QByteArray ServiceConnector::fetchResource(const QString &url)
     QObject::connect(reply.data(),
                      &QNetworkReply::sslErrors,
                      this,
-                     [&](const QList<QSslError> &errors)
-                     {
-                         qCritical() << errors.first().errorString();
+                     [&](const QList<QSslError> &errors) {
+                         if (!quite)
+                             qCritical() << errors.first().errorString();
+                         else
+                             qWarning() << errors.first().errorString();
                      });
 
     QEventLoop loop;
@@ -51,18 +53,23 @@ QByteArray ServiceConnector::fetchResource(const QString &url)
     QObject::connect(reply.data(),
                      &QNetworkReply::downloadProgress,
                      this,
-                     [&](qint64 bytesReceived, qint64 bytesTotal)
-                     {
-                         float percentage = roundf((float)bytesReceived / (float)bytesTotal * 100);
-                         qDebug() << "Download progress" << QString::number(percentage) << "% -" << QString::number(bytesReceived) << "/" << QString::number(bytesTotal);
+                     [&](qint64 bytesReceived, qint64 bytesTotal) {
+                         float percentage = roundf((float) bytesReceived / (float) bytesTotal * 100);
+                         qDebug() << "Download progress" << QString::number(percentage) << "% -"
+                                  << QString::number(bytesReceived) << "/"
+                                  << QString::number(bytesTotal);
                          emit downloadProgress(percentage);
                      });
     loop.exec();
 
-    if (reply->error() != QNetworkReply::NoError)
-        qCritical() << reply->errorString();
-    else
+    if (reply->error() != QNetworkReply::NoError) {
+        if (!quite)
+            qCritical() << reply->errorString();
+        else
+            qWarning() << reply->errorString();
+    } else {
         qDebug() << "Resource fetched successfully";
+    }
 
     return reply->readAll();
 }
@@ -88,9 +95,11 @@ QByteArray ServiceConnector::fetchUserProject(const QString &userHash, const QSt
 
 QJsonArray ServiceConnector::fetchUserProjectList(const QString &userHash)
 {
-    auto reply = fetchResource("https://designviewer.qt.io/api/v1/qmlrc/list/" + userHash + "?key=818815");
+    auto reply = fetchResource("https://designviewer.qt.io/api/v1/qmlrc/list/" + userHash
+                                   + "?key=818815",
+                               true);
+
     if (reply.size() == 0) {
-        qCritical("Could not fetch available project list");
         return QJsonArray();
     }
 
@@ -108,7 +117,6 @@ QJsonArray ServiceConnector::fetchUserDemoList()
 {
     auto reply = fetchResource("https://designviewer.qt.io/api/v1/demos");
     if (reply.size() == 0) {
-        qCritical("Could not fetch available demo list");
         return QJsonArray();
     }
 
diff --git a/src/serviceConnector.h b/src/serviceConnector.h
index 7af0d21..ad0c4d9 100644
--- a/src/serviceConnector.h
+++ b/src/serviceConnector.h
@@ -40,7 +40,7 @@ public:
 
 private:
     QNetworkAccessManager m_manager;
-    QByteArray fetchResource(const QString &url);
+    QByteArray fetchResource(const QString &url, const bool quite = false);
 
 signals:
     void downloadProgress(float percentage);
diff --git a/ui/HomePage.qml b/ui/HomePage.qml
index add017c..b498482 100644
--- a/ui/HomePage.qml
+++ b/ui/HomePage.qml
@@ -5,24 +5,21 @@ import QtQuick.Layouts
 
 Item {
     id: homePage
-    //width: 400
-    //height: 740
 
     ColumnLayout {
         anchors.fill: parent
 
-
         Item {
             id: item2
             Layout.preferredWidth: 10
-            Layout.preferredHeight: 10
+            Layout.preferredHeight: 3
             Layout.fillWidth: true
             Layout.fillHeight: true
         }
 
         Text {
             id: qrCodeStatus
-            text: qsTr("No user code is registered.\nTo access your shared projects;")
+            text: qsTr("No user code is registered. To access your shared projects;")
             font.pixelSize: 12
             horizontalAlignment: Text.AlignHCenter
             verticalAlignment: Text.AlignVCenter
@@ -39,7 +36,7 @@ Item {
 
         Text {
             id: qrCodeInstructions
-            text: qsTr("1. Open Design Studio (4.4 or later)\n2. Open any project\n3. Click \"File\" menu from top\n4. Select \"Share Application Online\"\n5. Scan QR code with your camera or any QR reader app")
+            text: qsTr("1. Open a project with Design Studio (4.4 or later)\n2. Click \"File\" menu from top and select \"Share Application Online\"\n3. Scan the QR code on the screen")
             font.pixelSize: 12
             horizontalAlignment: Text.AlignHCenter
             verticalAlignment: Text.AlignVCenter
@@ -61,40 +58,39 @@ Item {
             Layout.fillWidth: true
             Layout.fillHeight: true
         }
+
         ColumnLayout {
             id: column2
             Layout.fillWidth: true
             ComboBox {
                 id: projectList
                 Layout.fillWidth: true
+                enabled: backend.userHash() !== ''
                 onCurrentIndexChanged: {
                     displayText = textAt(currentIndex)
-                    downloadUserProject.enabled = true
-                }
-                onModelChanged: {
-                    if (model.count > 0) {
-                        currentIndex = 0
-                    }
                 }
                 model: backend.projectList
-                down: false
-                displayText: "Select project..."
+                displayText: "Scan QR code to access your projects";
                 currentIndex: -1
                 Layout.preferredHeight: 50
+                Connections {
+                    target: backend
+                    function onUserRegistered(){
+                        projectList.enabled = true;
+                    }
+                }
             }
 
             Button {
                 id: downloadUserProject
                 text: qsTr("Run Project")
                 onClicked: backend.runUserProject(projectList.currentText)
-                enabled: backend.userHash !== ''
+                enabled: backend.userHash() !== ''
                 Layout.fillWidth: true
                 Layout.alignment: Qt.AlignHCenter | Qt.AlignVCenter
             }
         }
 
-
-
         Item {
             id: item3
             Layout.fillHeight: true
@@ -103,11 +99,10 @@ Item {
             Layout.preferredWidth: 10
         }
 
-
-
         ColumnLayout {
             id: column
             Layout.fillWidth: true
+            visible: false
 
             Text {
                 id: downloadInstructions
diff --git a/ui/SettingsPage.qml b/ui/SettingsPage.qml
index 7e86b60..7bb4ceb 100644
--- a/ui/SettingsPage.qml
+++ b/ui/SettingsPage.qml
@@ -24,7 +24,8 @@ Item {
                 id: checkBox
                 text: qsTr("Update user projects in the backgroud")
                 font.pointSize: 15
-                onCheckStateChanged: backend.updateInBackground(checkState)
+                onCheckStateChanged: backend.setUpdateInBackground(checkState)
+                checkState: backend.updateInBackground() ? Qt.Checked : Qt.Unchecked
             }
 
             Text{
@@ -33,31 +34,20 @@ Item {
                 font.pointSize: 12
             }
 
-//            CheckBox {
-//                id: checkBox2
-//                text: qsTr("Cache user projects")
-//                font.pointSize: 15
-//                onCheckStateChanged: backend.cacheUserProjects(checkState)
-//            }
-
-//            Text{
-//                leftPadding: 45
-//                text: qsTr("Downloads the user projects each time if disabled")
-//                font.pointSize: 12
-//            }
+           CheckBox {
+                id: checkBox2
+                text: qsTr("Auto scale the project")
+                font.pointSize: 15
+                onCheckStateChanged: backend.setAutoScaleProject(checkState)
+                checkState: backend.autoScaleProject() ? Qt.Checked : Qt.Unchecked
 
-//            CheckBox {
-//                id: checkBox3
-//                text: qsTr("Cache demo projects")
-//                font.pointSize: 15
-//                onCheckStateChanged: backend.cacheDemoProjects(checkState)
-//            }
+           }
 
-//            Text{
-//                leftPadding: 45
-//                text: qsTr("Downloads the demo projects each time if disabled")
-//                font.pointSize: 12
-//            }
+           Text{
+                leftPadding: 45
+                text: qsTr("Scales the project to fit it to the current display and orientation")
+                font.pointSize: 12
+           }
         }
 
         Item {
@@ -67,13 +57,5 @@ Item {
             Layout.preferredHeight: 10
             Layout.preferredWidth: 10
         }
-
-//        Button {
-//            id: downloadButton
-//            text: qsTr("Save")
-//            onClicked: backend.saveSettings(checkBox.checked)
-//            Layout.fillWidth: true
-//            Layout.alignment: Qt.AlignHCenter | Qt.AlignVCenter
-//        }
     }
 }
diff --git a/ui/main.qml b/ui/main.qml
index 2e5491d..1e76caf 100644
--- a/ui/main.qml
+++ b/ui/main.qml
@@ -2,7 +2,6 @@ import QtQuick 6.4
 import QtQuick.Controls 6.4
 import QtQuick.Controls.Material as M
 import QtQuick.Layouts
-//import QtQuick.Window 2.2
 
 Rectangle {
     id: root
@@ -11,6 +10,7 @@ Rectangle {
     color: "#EAEAEA"
     state: "vertical"
 
+
     M.Material.theme: M.Material.Light
     M.Material.accent: M.Material.Blue
     M.Material.primary: M.Material.Blue
@@ -26,7 +26,7 @@ Rectangle {
 
         Text {
             id: qdvLabel
-            text: qsTr("Qt UI Viewer for Android")
+            text: qsTr("Qt UI Viewer")
             anchors.top: parent.bottom
             font.pixelSize: 12
             anchors.topMargin: -22
@@ -124,11 +124,12 @@ Rectangle {
     states: [
         State {
             name: "vertical"
-            when: root.width >= 400
+            when: root.height >= 400
+
         },
         State {
             name: "horizontal"
-            when: root.width < 400
+            when: root.height < 400
 
             PropertyChanges {
                 target: qdsicon1
@@ -145,30 +146,25 @@ Rectangle {
                 target: stackLayout
                 anchors.topMargin: 20
             }
-
         }
     ]
 
 
-     Drawer {
+    Drawer {
         id: drawer
         width: 150
         height: root.height
+        ScrollView {
+            id: scrollview
+            anchors.fill: parent
 
         ColumnLayout {
             id: column
-            anchors.fill: parent
-            anchors.bottomMargin: 31
+            anchors.fill: drawer
             anchors.rightMargin: 10
             anchors.leftMargin: 10
-            spacing: 5
-
-            Image {
-                id: qdsicon2
-                source: "content/images/appicon.png"
-                sourceSize.width: drawer.width / 2
-                anchors.horizontalCenter: parent.horizontalCenter
-            }
+            width: Math.max(implicitWidth, drawer.availableWidth)
+            height: Math.max(implicitHeight, drawer.availableHeight)
 
             TabButton {
                 id: home
@@ -207,6 +203,7 @@ Rectangle {
                 Layout.fillWidth: true
                 checkable: true
                 autoExclusive: true
+
                 Connections {
                     target: logs
                     function onClicked(){
@@ -261,6 +258,7 @@ Rectangle {
                 }
             }
         }
+        }
      }
 
     Button {
-- 
GitLab