diff --git a/.clang-format b/.clang-format
new file mode 100644
index 0000000000000000000000000000000000000000..2d6d4493d074f56e6c590af70cd22d059ede58ab
--- /dev/null
+++ b/.clang-format
@@ -0,0 +1,118 @@
+# .clang-format for Qt Creator
+#
+# This is for clang-format >= 5.0.
+#
+# The configuration below follows the Qt Creator Coding Rules [1] as closely as
+# possible. For documentation of the options, see [2].
+#
+# Use ../../tests/manual/clang-format-for-qtc/test.cpp for documenting problems
+# or testing changes.
+#
+# In case you update this configuration please also update the qtcStyle() in src\plugins\clangformat\clangformatutils.cpp
+#
+# [1] https://doc-snapshots.qt.io/qtcreator-extending/coding-style.html
+# [2] https://clang.llvm.org/docs/ClangFormatStyleOptions.html
+#
+---
+Language:        Cpp
+AccessModifierOffset: -4
+AlignAfterOpenBracket: Align
+AlignConsecutiveAssignments: false
+AlignConsecutiveDeclarations: false
+AlignEscapedNewlines: DontAlign
+AlignOperands:   true
+AlignTrailingComments: true
+AllowAllParametersOfDeclarationOnNextLine: true
+AllowShortBlocksOnASingleLine: Never
+AllowShortCaseLabelsOnASingleLine: false
+AllowShortFunctionsOnASingleLine: Inline
+AllowShortIfStatementsOnASingleLine: false
+AllowShortLoopsOnASingleLine: false
+AlwaysBreakAfterReturnType: None
+AlwaysBreakBeforeMultilineStrings: false
+AlwaysBreakTemplateDeclarations: Yes
+BinPackArguments: false
+BinPackParameters: false
+BraceWrapping:
+  AfterClass:      true
+  AfterControlStatement: Never
+  AfterEnum:       false
+  AfterFunction:   true
+  AfterNamespace:  false
+  AfterObjCDeclaration: false
+  AfterStruct:     true
+  AfterUnion:      false
+  BeforeCatch:     false
+  BeforeElse:      false
+  IndentBraces:    false
+  SplitEmptyFunction: false
+  SplitEmptyRecord: false
+  SplitEmptyNamespace: false
+BreakBeforeBinaryOperators: All
+BreakBeforeBraces: Custom
+BreakBeforeInheritanceComma: false
+BreakBeforeTernaryOperators: true
+BreakConstructorInitializersBeforeComma: false
+BreakConstructorInitializers: BeforeComma
+BreakAfterJavaFieldAnnotations: false
+BreakStringLiterals: true
+ColumnLimit:    100
+CommentPragmas:  '^ IWYU pragma:'
+CompactNamespaces: false
+ConstructorInitializerAllOnOneLineOrOnePerLine: false
+ConstructorInitializerIndentWidth: 4
+ContinuationIndentWidth: 4
+Cpp11BracedListStyle: true
+DerivePointerAlignment: false
+DisableFormat:   false
+ExperimentalAutoDetectBinPacking: false
+FixNamespaceComments: true
+ForEachMacros:
+  - forever # avoids { wrapped to next line
+  - foreach
+  - Q_FOREACH
+  - BOOST_FOREACH
+IncludeCategories:
+  - Regex:           '^<Q.*'
+    Priority:        200
+IncludeIsMainRegex: '(Test)?$'
+IndentCaseLabels: false
+IndentWidth:     4
+IndentWrappedFunctionNames: false
+InsertBraces: false
+JavaScriptQuotes: Leave
+JavaScriptWrapImports: true
+KeepEmptyLinesAtTheStartOfBlocks: false
+# Do not add QT_BEGIN_NAMESPACE/QT_END_NAMESPACE as this will indent lines in between.
+MacroBlockBegin: ""
+MacroBlockEnd:   ""
+MaxEmptyLinesToKeep: 1
+NamespaceIndentation: None
+ObjCBlockIndentWidth: 4
+ObjCSpaceAfterProperty: false
+ObjCSpaceBeforeProtocolList: true
+PenaltyBreakAssignment: 88
+PenaltyBreakBeforeFirstCallParameter: 300
+PenaltyBreakComment: 500
+PenaltyBreakFirstLessLess: 400
+PenaltyBreakString: 600
+PenaltyExcessCharacter: 50
+PenaltyReturnTypeOnItsOwnLine: 300
+PointerAlignment: Right
+ReflowComments:  false
+SortIncludes: CaseSensitive
+SortUsingDeclarations: true
+SpaceAfterCStyleCast: true
+SpaceAfterTemplateKeyword: false
+SpaceBeforeAssignmentOperators: true
+SpaceBeforeParens: ControlStatements
+SpaceInEmptyParentheses: false
+SpacesBeforeTrailingComments: 1
+SpacesInAngles:  false
+SpacesInContainerLiterals: false
+SpacesInCStyleCastParentheses: false
+SpacesInParentheses: false
+SpacesInSquareBrackets: false
+Standard: c++17
+TabWidth:        4
+UseTab:          Never
diff --git a/CMakeLists.txt b/CMakeLists.txt
index 1f2b1e1c0d1cabbdf1df01c35b138b5145c641d7..75f7fdcf0f8c1f6a2644a5987cd25221641c58b5 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -32,6 +32,8 @@ qt_add_executable(${PROJECT_NAME}
     src/importdummy.qml
     src/main.cpp
     src/backend.cpp src/backend.h
+    src/serviceConnector.cpp src/serviceConnector.h
+    src/projectManager.cpp src/projectManager.h
     ui/main.qml
     ui/resources.qrc
 )
diff --git a/android/AndroidManifest.xml b/android/AndroidManifest.xml
index e7327790a04a506609bb0bc45a7d638128e7dfc8..151df223e244cefe1594a29331cc2efcf25c20ae 100644
--- a/android/AndroidManifest.xml
+++ b/android/AndroidManifest.xml
@@ -1,10 +1,14 @@
 <?xml version="1.0"?>
 <manifest xmlns:android="http://schemas.android.com/apk/res/android" package="io.qt.qtdesignviewer"
-    android:installLocation="auto" android:versionCode="11" android:versionName="1.2">
+    android:installLocation="auto" android:versionCode="13" 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:requestLegacyExternalStorage="true" android:allowNativeHeapPointerTagging="false">
+    <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" android:icon="@mipmap/app_icon">
         <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"/>
diff --git a/src/backend.cpp b/src/backend.cpp
index 206cfcacae906065482a68e76329351a27339d09..0fd27c4bdb923cd0cdb3eb7cee457fbd09f47323 100644
--- a/src/backend.cpp
+++ b/src/backend.cpp
@@ -28,323 +28,58 @@
 
 #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) : QObject(parent)
+Backend::Backend(QObject *parent)
+    : QObject(parent)
 {
+    // This will allow us to open the app with the QR code
     QDesktopServices::setUrlHandler("qtdesignviewer", this, "registerUser");
+}
 
+void Backend::initialize()
+{
     const QRect screenGeometry = QGuiApplication::primaryScreen()->geometry();
-
-    printLog("Qt Design Viewer");
-    printLog("System information:");
-    printLog("-- Qt version: " + QString(QT_VERSION_STR));
-    printLog("-- OpenSSL support: " + QVariant(QSslSocket::supportsSsl()).toString());
-    printLog("-- Screen height: " + QSTRN(screenGeometry.height()));
-    printLog("-- Screen width: " + QSTRN(screenGeometry.width()));
+    qDebug() << "Qt Design Viewer";
+    qDebug() << "System information:";
+    qDebug() << "-- Qt version: " << QT_VERSION_STR;
+    qDebug() << "-- OpenSSL support: " << QVariant(QSslSocket::supportsSsl()).toString();
+    qDebug() << "-- Screen height: " << QString::number(screenGeometry.height());
+    qDebug() << "-- Screen width: " << QString::number(screenGeometry.width());
 
 #ifdef QT_DEBUG
-    QString buildType = "Debug";
+    const QString buildType = "Debug";
 #else
-    QString buildType = "Release";
+    const QString buildType = "Release";
 #endif
-    m_buildInfo = QCoreApplication::applicationVersion() +
-                  "\nTechnology Preview - " + QString(GIT_VERSION) +
-                  "\nQt " + QString(QT_VERSION_STR) + " - " + buildType + " Build" +
-                  "\nQt Quick Components " + QString(QT_QUICK_COMPONENTS_VERSION) +
-                  "\nOpenSSL support: " + QVariant(QSslSocket::supportsSsl()).toString();
-    emit buildInfoChanged();
+    const QString buildInfo = QCoreApplication::applicationVersion() + "\nTechnology Preview - "
+                              + QString(GIT_VERSION) + "\nQt " + QString(QT_VERSION_STR) + " - "
+                              + buildType + " Build" + "\nQt Quick Components "
+                              + QString(QT_QUICK_COMPONENTS_VERSION) + "\nOpenSSL support: "
+                              + QVariant(QSslSocket::supportsSsl()).toString();
+    emit buildInfoChanged(buildInfo);
 
     QSettings settings;
     m_userHash = settings.value("user/hash").toString();
     if (m_userHash.isEmpty())
-        printLog("User Hash is not registered. Scan QR code to register.");
+        qDebug("User Hash is not registered. Scan QR code to register.");
     else {
-        printLog("User Hash: " + m_userHash);
+        qDebug() << "User Hash: " << m_userHash;
         updateUserProjectList();
     }
 
-    printLog("Initialization complete");
-}
-
-void Backend::printLog(const QString &log)
-{
-    qDebug() << log;
-    m_logs += log + "\n";
-    emit logsChanged();
-}
-
-void Backend::printWarn(const QString &warn)
-{
-    printLog("WARN: " + warn);
-}
-
-void Backend::printError(const QString &error, const QString &fileName, int line)
-{
-    printLog(QString(error)
-                 .prepend("ERROR: ")
-                 .append(" (")
-                 .append(fileName)
-                 .append(":")
-                 .append(QSTRN(line))
-                 .append(")"));
-}
-
-QString Backend::unpackProject(const QByteArray &project, bool extractZip)
-{
-    QTemporaryDir tempDir("qmlprojector");
-    QString projectLocation = tempDir.path();
-
-    if (extractZip) {
-        QDir().mkpath(projectLocation);
-        QBuffer buffer;
-        buffer.setData(project);
-        buffer.open(QIODevice::ReadOnly);
-        QZipReader reader(&buffer);
-        reader.extractAll(projectLocation);
-    }
-
-    printLog("Initial project location: " + projectLocation);
-
-    QDir projectLocationDir(projectLocation);
-
-    // maybe it was not a zip file so try it as resource binary
-    if (projectLocationDir.isEmpty()) {
-        if (extractZip)
-            printLog("File could not be extracted. Trying to open it as a resource file.");
-
-        const uchar *data;
-        if (m_projectData.size()) {
-            printLog("Unregistering the previous data from QRC system. Path: " + m_projectPath
-                     + " Size: " + QString::number(m_projectData.size()) + " bytes.");
-            data = reinterpret_cast<const uchar *>(m_projectData.data());
-            if (!QResource::unregisterResource(data, m_projectPath)) {
-                printErr("Cannot unregister the previous resource data.");
-            }
-        }
-
-        m_projectData = project;
-        data = reinterpret_cast<const uchar *>(m_projectData.data());
-        printLog("Registering resource data. Size: " + QString::number(m_projectData.size()));
-
-        const QString resourcePath{"/" + QString::number(QRandomGenerator::global()->generate())};
-        m_projectPath = resourcePath;
-
-        if (!QDir(resourcePath).removeRecursively()) {
-            printLog("Could not remove resource path: " + resourcePath);
-        }
-
-        if (!QResource::registerResource(data, resourcePath)) {
-            printErr("Can not load the resource data.");
-            return "";
-        }
-
-        projectLocation = ":" + resourcePath;
-    }
-
-    return projectLocation;
-}
-
-QString Backend::findFile(const QString &dir, const QString &filter)
-{
-    QDirIterator it(dir, {filter}, QDir::Files, QDirIterator::Subdirectories);
-    return it.next();
-}
-
-void Backend::parseQmlprojectFile(const QString &fileName,
-                                  QString *mainFile,
-                                  QStringList *importPaths)
-{
-    /* if filename comes from a resource, then qml need qrc:/ at the mainfile and importPaths.
-     * But all other c++ call like QFileInfo::exists do not understand that, there we
-     * need to keep the only ":" character at the beginning of the string
-     */
-    QFile file(fileName);
-    if (!file.open(QIODevice::ReadOnly)) {
-        printErr("Could not open Qml Project file! " + fileName + ": " + file.errorString());
-        return;
-    }
-
-    const QString text = QString::fromUtf8(file.readAll());
-
-    const QRegularExpression mainFileRegExp("mainFile:\\s*\"(.*)\"");
-    const QRegularExpressionMatch mainFileMatch = mainFileRegExp.match(text);
-
-    if (!mainFileMatch.hasMatch()) {
-        printErr("No main file found in " + fileName);
-        return;
-    }
-
-    printLog("Found main file: " + mainFileMatch.captured(1));
-    QString basePath = QFileInfo(fileName).path() + "/";
-
-    *mainFile = basePath + mainFileMatch.captured(1);
-    if (mainFile->startsWith(QLatin1String(":/")))
-        *mainFile = "qrc:" + mainFile->mid(1);
-
-    const QRegularExpression qt6ProjectRegExp("qt6Project:\\s*true");
-    const QRegularExpressionMatch qt6ProjectMatch = qt6ProjectRegExp.match(text);
-    if (!qt6ProjectMatch.hasMatch()) {
-        printWarn("This is not a Qt6 project.\nQt5 projects might work, but they are not "
-                  "officially supported.");
-    }
-
-    const QRegularExpression importPathsRegExp("importPaths:\\s*\\[\\s*(.*)\\s*\\]");
-    const QRegularExpressionMatch importPathsMatch = importPathsRegExp.match(text);
-
-    if (importPathsMatch.hasMatch()) {
-        for (const QString &path : importPathsMatch.captured(1).split(",")) {
-            QString cleanedPath = path.trimmed();
-            cleanedPath = basePath + cleanedPath.mid(1, cleanedPath.length() - 2);
-            if (QFileInfo::exists(cleanedPath)) {
-                if (cleanedPath.startsWith(QLatin1String(":/")))
-                    cleanedPath = "qrc:" + cleanedPath.mid(1);
-                importPaths->append(cleanedPath);
-            }
-        }
-    }
-}
-
-bool Backend::runProject(const QByteArray &projectData, const QString &projectName)
-{
-    emit popupTextChanged("Unpacking project...");
-    emit popupProgressIndeterminateChanged(true);
-    QEventLoop().processEvents(QEventLoop::AllEvents, 1000);
-
-    const QString projectLocation = unpackProject(projectData);
-    const QString newProjectName = QString(projectName).remove(".qmlrc").remove("#");
-    printLog("Final project location: " + projectLocation);
-    printLog("Project name: " + projectName);
-    printLog("New project name: " + newProjectName);
-
-    QString mainQmlFilePath;
-    QStringList importPaths;
-
-    printLog("Looking for qmlproject file in " + projectLocation);
-    const QString qmlProjectFile = findFile(projectLocation, newProjectName + "*.qmlproject");
-    if (!qmlProjectFile.isEmpty()) {
-        printLog("Found qmlproject file: " + qmlProjectFile);
-        parseQmlprojectFile(qmlProjectFile, &mainQmlFilePath, &importPaths);
-    } else {
-        printWarn("Not found: \"*.qmlproject\". Looking for main.qml..");
-        mainQmlFilePath = findFile(projectLocation, "main.qml");
-
-        if (mainQmlFilePath.isEmpty()) {
-            printWarn("Not found: \"main.qml\". Looking for \"" + newProjectName + ".qml\"..");
-            mainQmlFilePath = findFile(projectLocation, newProjectName + ".qml");
-        }
-    }
-
-    if (mainQmlFilePath.isEmpty()) {
-        printErr("No \"*.qmlproject\", \"main.qml\" or \"" + newProjectName + ".qml\" found in \"" + projectLocation + "\".");
-        return false;
-    }
-
-    printLog("Found mainQmlFile: " + mainQmlFilePath);
-
-    QUrl mainQmlUrl = QUrl::fromUserInput(mainQmlFilePath);
-    QFile file(mainQmlUrl.path().prepend(":"));
-    if (!file.open(QIODevice::ReadOnly)) {
-        printErr("Could not open mainQmlfile for reading! " + file.fileName() + ": "
-                 + file.errorString());
-        return false;
-    }
-
-    printLog("Looking for qtquickcontrols2File in " + projectLocation);
-    const QString qtquickcontrols2File = findFile(projectLocation, "qtquickcontrols2.conf");
-
-    if (!qtquickcontrols2File.isEmpty()) {
-        printLog("Found qtquickcontrols2File: " + qtquickcontrols2File);
-        qputenv("QT_QUICK_CONTROLS_CONF", qtquickcontrols2File.toLatin1());
-    }
-
-    printLog("Adding import paths");
-    for (const QString &importPath : importPaths) {
-        printLog("-- Import path: " + importPath);
-        m_qmlEngine.addImportPath(importPath);
-    }
-
-    QObject::connect(&m_qmlEngine,
-                     &QQmlEngine::warnings,
-                     this,
-                     [&](const QList<QQmlError> &warnings) {
-                         for (const auto &warning : warnings) {
-                             printWarn(warning.toString());
-                         }
-                     });
-
-    emit popupTextChanged("Loading project...");
-    QEventLoop().processEvents(QEventLoop::AllEvents, 1000);
-
-    printLog("Loading mainQmlUrl: " + mainQmlUrl.toString());
-    m_qmlComponent.reset(new QQmlComponent(&m_qmlEngine));
-    m_qmlComponent->loadUrl(mainQmlUrl);
-
-    printLog("Waiting for qmlComponent to load");
-    while (m_qmlComponent->isLoading())
-        QCoreApplication::processEvents();
-
-    printLog("Checking if m_qmlComponent is ready");
-    if (!m_qmlComponent->isReady())
-    {
-        printErr("m_qmlComponent is not ready. Reason: " + m_qmlComponent->errorString());
-        return false;
-    }
-    printLog("Creating top level object");
-    QObject *topLevel = m_qmlComponent->create();
-    if (!topLevel && m_qmlComponent->isError())
-    {
-        printErr("Error while creating Qml m_qmlComponent:" + m_qmlComponent->errorString());
-        return false;
-    }
-
-    emit popupTextChanged("Setting up the quickWindow...");
-    QEventLoop().processEvents(QEventLoop::AllEvents, 1000);
-
-    printLog("Setting up the quickWindow");
-    m_quickWindow.reset(qobject_cast<QQuickWindow *>(topLevel));
-    if (m_quickWindow) {
-        printLog("Running with incubator controller");
-        m_qmlEngine.setIncubationController(m_quickWindow->incubationController());
-    } else {
-        printWarn("Top level object is not a QQuickWindow. Trying QQuickView...");
-
-        QQuickItem *contentItem = qobject_cast<QQuickItem *>(topLevel);
-        if (!contentItem) {
-            printErr("Top level object cannot be casted to QQuickItem. Aborting.");
-            return false;
-        }
-
-        printLog("Initializing QQuickView");
-        QQuickView *view = new QQuickView(&m_qmlEngine, nullptr);
-        m_quickWindow.reset(view);
-        view->setContent(mainQmlUrl, m_qmlComponent.data(), contentItem);
-        view->setResizeMode(QQuickView::SizeViewToRootObject);
-        m_quickWindow->setBaseSize(QSize(contentItem->width(), contentItem->height()));
-    }
-    return true;
+    connect(&m_serviceConnector, &ServiceConnector::downloadProgress, this, &Backend::downloadProgress);
+    connect(&m_projectManager,
+            &ProjectManager::projectStateChanged,
+            this,
+            &Backend::popupTextChanged);
+    qDebug("Initialization complete");
 }
 
 void Backend::showWarning(const QString &message)
@@ -353,134 +88,78 @@ void Backend::showWarning(const QString &message)
     msg.exec();
 }
 
-QSharedPointer<QNetworkReply> Backend::fetchResource(const QString &url)
-{
-    printLog("Fetching resource from " + url);
-
-    QNetworkRequest request(url);
-    request.setRawHeader("Authorization", "test");
-    QSharedPointer<QNetworkReply> reply(m_nam.get(request));
-    QObject::connect(reply.data(),
-                     &QNetworkReply::sslErrors,
-                     this,
-                     [&](const QList<QSslError> &errors) {
-                         printErr(errors.first().errorString());
-                     });
-
-    QEventLoop loop;
-    QObject::connect(reply.data(), &QNetworkReply::finished, &loop, &QEventLoop::quit);
-    QObject::connect(reply.data(),
-                     &QNetworkReply::downloadProgress,
-                     this,
-                     [&](qint64 bytesReceived, qint64 bytesTotal) {
-                         float percentage = roundf((float) bytesReceived / (float) bytesTotal * 100);
-                         printLog("Download progress " + QSTRN(percentage) + "% - "
-                                  + QSTRN(bytesReceived) + "/" + QSTRN(bytesTotal));
-                         m_downloadProgress = percentage;
-                         emit downloadProgressChanged();
-                     });
-    loop.exec();
-
-    if (reply->error() != QNetworkReply::NoError) {
-        printErr(reply->errorString());
-    } else {
-        printLog("Resource fetched successfully");
-    }
-
-    return reply;
-}
-
-void Backend::orientateWindow(Qt::ScreenOrientation orientation)
+void Backend::downloadAndRun(const QString &url)
 {
-    QQuickItem *contentItem = m_quickWindow->contentItem();
-    QQuickItem *childItem{contentItem->childItems().at(0)};
-    QQuickItem *grandChildItem{childItem->childItems().at(0)};
-    QQuickItem *targetItem = childItem;
-
-    const QRect screenGeometry = QGuiApplication::primaryScreen()->geometry();
-    const QString orientationString = (orientation == Qt::LandscapeOrientation ? "landscape"
-                                                                               : "portrait");
-    printLog("Adapting orientation to " + orientationString + " mode");
-    printLog("Initial sizing:");
-    printLog("-- Screen size: " + QSTRN(screenGeometry.height()) + " x " + QSTRN(screenGeometry.width()));
-    printLog("-- Quick window size: " + QSTRN(m_quickWindow->height()) + " x "
-             + QSTRN(m_quickWindow->width()));
-    printLog("-- Child size: " + QSTRN(targetItem->height()) + " x " + QSTRN(targetItem->width()));
-    printLog("-- Child pos: " + QSTRN(targetItem->x()) + ", " + QSTRN(targetItem->y()));
-    printLog("-- Child scale: " + QSTRN(targetItem->scale()));
-
-    printLog("Calculating the new size and scale...");
+    updatePopup("Downloading project...", false);
+    emit popupOpen();
 
-    const QSizeF newContentSize = targetItem->size().scaled(screenGeometry.size().toSizeF(),
-                                                            Qt::AspectRatioMode::KeepAspectRatio);
+    qDebug() << "Fetching project from " << url << "...";
+    QByteArray project = m_serviceConnector.fetchProject(url);
 
-    const qreal newScale = newContentSize.height() / targetItem->size().height();
-    const qreal newX = (targetItem->width() - screenGeometry.width()) / -2.0f;
-    const qreal newY = (targetItem->height() - screenGeometry.height()) / -2.0f;
+    updatePopup("Unpacking project...");
 
-    targetItem->setScale(newScale);
-    targetItem->setPosition(QPointF(newX, newY));
+    const QString projectPath = m_projectManager.unpackProject(project);
 
-    printLog("-- Calculated item height: " + QSTRN(newContentSize.height()));
-    printLog("-- Calculated item width: " + QSTRN(newContentSize.width()));
-    printLog("-- Calculated item scale: " + QSTRN(newScale));
-    printLog("-- Calculated item pos..: " + QSTRN(newX) + "," + QSTRN(newY));
+    updatePopup("Running project...");
+    if (!m_projectManager.runProject(projectPath, QFileInfo(url).baseName()))
+        qCritical("Could not run project");
+    else
+        m_projectManager.showAppWindow();
 
-    printLog("Final Sizing:");
-    printLog("-- Child height: " + QSTRN(targetItem->height()));
-    printLog("-- Child width: " + QSTRN(targetItem->width()));
-    printLog("-- Child scale: " + QSTRN(targetItem->scale()));
-    printLog("-- Child pos-x: " + QSTRN(targetItem->x()));
-    printLog("-- Child pos-y: " + QSTRN(targetItem->y()));
+    emit popupClose();
 }
 
-void Backend::showAppWindow()
+void Backend::updatePopup(const QString &text, bool indeterminate)
 {
-    QScreen *screen = QGuiApplication::primaryScreen();
-    QObject::connect(screen, &QScreen::orientationChanged, this, &Backend::orientateWindow);
-    orientateWindow(screen->orientation());
-
-    printLog("Initializing and showing the QML app window");
-    // m_quickWindow->showMaximized();
-    m_quickWindow->show();
+    emit popupTextChanged(text);
+    emit popupProgressIndeterminateChanged(indeterminate);
+    QEventLoop().processEvents(QEventLoop::AllEvents, 100);
 }
 
-void Backend::downloadAndRun(const QString &url)
+void Backend::runUserProject(const QString &url)
 {
-    emit popupTextChanged("Downloading project...");
-    emit popupProgressIndeterminateChanged(false);
+    updatePopup("Running user project");
     emit popupOpen();
 
-    printLog("=========================");
-    printLog("Fetching a new project...");
-
-    QString projectUrl = url;
-    if (projectUrl.startsWith("https://designviewer.qt.io/#")) {
-        projectUrl = projectUrl.split("#").at(1);
-        projectUrl.prepend("https://designviewer.qt.io/qmlprojects/");
+    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();
+    QString projectLastModified;
+    QString projectId;
+    QJsonObject projectInfo;
+    for (auto project : projectList) {
+        if (projectName == project.toObject().value("appName").toString()) {
+            projectInfo = project.toObject();
+            break;
+        }
     }
 
-    auto reply = fetchResource(projectUrl);
-    if (reply->error() != QNetworkReply::NoError) {
-        printErr("Could not fetch project");
-        emit popupClose();
-        return;
-    }
+    const bool projectCached = m_projectManager.isProjectCached(projectInfo);
+
+    if (!projectCached) {
+        qDebug("Project is not cached. Downloading...");
+        updatePopup("Project is not cached. Downloading...", false);
+        QByteArray projectData = m_serviceConnector.fetchProject(url);
 
-    if (!runProject(reply->readAll(), QFileInfo(url).baseName())) {
-        printErr("Could not run project");
-        emit popupClose();
-        return;
+        updatePopup("Caching user project...");
+        m_projectManager.cacheProject(projectData, projectInfo);
+    } else {
+        qDebug("Project is cached. Running cached project...");
     }
 
+    updatePopup("Running cached project...");
+    if (!m_projectManager.runCachedProject(projectInfo))
+        qCritical("Could not run project");
+    else
+        m_projectManager.showAppWindow();
+
     emit popupClose();
-    showAppWindow();
 }
 
 void Backend::registerUser(const QUrl &url)
 {
     const QString userHash = url.toString().remove("qtdesignviewer://");
-    printLog("Registering User Hash: " + userHash);
+    qDebug() << "Registering User Hash: " << userHash;
     m_userHash = userHash;
     QSettings().setValue("user/hash", m_userHash);
     emit userRegistered();
@@ -489,26 +168,14 @@ void Backend::registerUser(const QUrl &url)
 
 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();
+    qDebug() << "Fetching available project list for user: " << m_userHash;
+    QJsonArray projectList = m_serviceConnector.fetchUserProjectList(m_userHash);
 
     m_projectList.clear();
-    printLog("List of available projects fetched:");
+    qDebug("List of available projects fetched:");
     for (const auto &project : projectList) {
         const QString projectName{project.toObject().value("appName").toString()};
-        printLog("-- " + projectName);
+        qDebug() << "--" << projectName;
         m_projectList << projectName;
     }
     emit projectListChanged();
diff --git a/src/backend.h b/src/backend.h
index f8defbb802ef059852d0aefa431ba105d11ae713..af0e18176b64d0540cf63cbac7e928052b34f56d 100644
--- a/src/backend.h
+++ b/src/backend.h
@@ -26,69 +26,48 @@
 #ifndef DV_ANDROID_H
 #define DV_ANDROID_H
 
-#include <QNetworkAccessManager>
-#include <QQmlComponent>
-#include <QQmlEngine>
-#include <QQuickView>
-#include <QQuickWindow>
-#include <QWidget>
-
-#define printErr(x) printError(x, __FILE_NAME__, __LINE__)
+#include "projectManager.h"
+#include "serviceConnector.h"
 
 class Backend : public QObject
 {
     Q_OBJECT
-    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; }
+    void initialize();
+    void setLogs(const QString &logs)
+    {
+        m_logs = logs;
+        qDebug() << "emitting logs changed signal";
+        emit logsChanged(m_logs);
+    }
+
     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;
-    QSharedPointer<QQmlComponent> m_qmlComponent;
-    QSharedPointer<QQuickWindow> m_quickWindow;
-
     // Other members
-    QNetworkAccessManager m_nam;
-    QByteArray m_projectData;
-    QString m_projectPath;
     QString m_userHash;
+    ServiceConnector m_serviceConnector;
+    ProjectManager m_projectManager;
 
-    // member logger functions
-    void printLog(const QString &message);
-    void printWarn(const QString &message);
-    void printError(const QString &message, const QString &fileName, int line);
+    // member functions
     void showWarning(const QString &message);
-
-    void showAppWindow();
-
-    QSharedPointer<QNetworkReply> fetchResource(const QString &url);
-    QString unpackProject(const QByteArray &project, bool extractZip = false);
-    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();
+    void updatePopup(const QString &text, bool indeterminate = true);
 
 signals:
-    void logsChanged();
-    void buildInfoChanged();
-    void downloadProgressChanged();
+    // UI signals
+    void logsChanged(QString);
+    void buildInfoChanged(QString);
+    void downloadProgress(float);
     void projectListChanged();
     void popupProgressIndeterminateChanged(bool indeterminate);
     void popupTextChanged(QString text);
@@ -97,11 +76,9 @@ signals:
     void userRegistered();
 
 public slots:
+    void runUserProject(const QString &url);
     void downloadAndRun(const QString &url);
     void registerUser(const QUrl &url);
-
-private slots:
-    void orientateWindow(Qt::ScreenOrientation orientation);
 };
 
 #endif // DV_ANDROID_H
diff --git a/src/main.cpp b/src/main.cpp
index 00f8f6bc6d77c07bc0f489d1ab89cd3bd0c76935..823dc0ad62108e547a8b0a712d254ffe5922c060 100644
--- a/src/main.cpp
+++ b/src/main.cpp
@@ -23,27 +23,65 @@
 **
 ****************************************************************************/
 
+#include <android/log.h>
 #include <QApplication>
-#include <QDebug>
 #include <QQmlContext>
-#include <QSurfaceFormat>
 
 #include "backend.h"
 
+Backend *backend;
+
+void messageHandler(QtMsgType type, const QMessageLogContext &context, const QString &msg)
+{
+    QByteArray localMsg = msg.toLocal8Bit();
+    const char *file = context.file ? context.file : "";
+    const char *function = context.function ? context.function : "";
+    QString logPrefix, logSuffix, applicationLogs;
+
+    switch (type)
+    {
+    case QtDebugMsg:
+        logPrefix = QStringLiteral("Debug: ");
+        break;
+    case QtInfoMsg:
+        logPrefix = QStringLiteral("Info: ");
+        break;
+    case QtWarningMsg:
+        logPrefix = QStringLiteral("Warning: ");
+        break;
+    case QtCriticalMsg:
+        logPrefix = QStringLiteral("Critical: ");
+        break;
+    case QtFatalMsg:
+        logPrefix = QStringLiteral("Fatal: ");
+        logSuffix = QStringLiteral(" (%1:%2, %3)").arg(file).arg(context.line).arg(function);
+        break;
+    }
+    applicationLogs += logPrefix + localMsg + logSuffix + "\n";
+    __android_log_print(ANDROID_LOG_DEBUG, "Qt_Design_Viewer", "%s", qPrintable(applicationLogs));
+
+    if (backend)
+        backend->setLogs(applicationLogs);
+}
+
 int main(int argc, char *argv[])
 {
+    qInstallMessageHandler(messageHandler);
     QApplication app(argc, argv);
     QApplication::setOrganizationName("Qt");
     QApplication::setApplicationName(QStringLiteral("Qt Design Viewer"));
     QApplication::setApplicationVersion(QString("Built on %1 %2").arg(__DATE__, __TIME__));
 
     QQuickView view;
-    Backend backend;
+    backend = new Backend();
 
-    view.engine()->rootContext()->setContextProperty("backend", &backend);
+    view.engine()->rootContext()->setContextProperty("backend", backend);
     view.setSource(QUrl(QStringLiteral("qrc:/main.qml")));
     view.setResizeMode(QQuickView::SizeRootObjectToView);
     view.showMaximized();
 
+    backend->initialize();
+    backend->registerUser(QUrl("17e8907b3b84b8206d45be4f551f4e25"));
+
     return app.exec();
 }
diff --git a/src/projectManager.cpp b/src/projectManager.cpp
new file mode 100644
index 0000000000000000000000000000000000000000..37f3654dc9be05b6ca6746ecbe37783fbae4ce6b
--- /dev/null
+++ b/src/projectManager.cpp
@@ -0,0 +1,448 @@
+/****************************************************************************
+**
+** Copyright (C) 2023 The Qt Company Ltd.
+** Contact: https://www.qt.io/licensing/
+**
+** This file is part of the Qt Design Viewer of the Qt Toolkit.
+**
+** Commercial License Usage
+** Licensees holding valid commercial Qt licenses may use this file in
+** accordance with the commercial license agreement provided with the
+** Software or, alternatively, in accordance with the terms contained in
+** a written agreement between you and The Qt Company. For licensing terms
+** and conditions see https://www.qt.io/terms-conditions. For further
+** information use the contact form at https://www.qt.io/contact-us.
+**
+** GNU General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU
+** General Public License version 3 as published by the Free Software
+** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT
+** included in the packaging of this file. Please review the following
+** information to ensure the GNU General Public License requirements will
+** be met: https://www.gnu.org/licenses/gpl-3.0.html.
+**
+****************************************************************************/
+
+#include "projectManager.h"
+
+#include <QBuffer>
+#include <QDir>
+#include <QDirIterator>
+#include <QEventLoop>
+#include <QFile>
+#include <QFileInfo>
+#include <QGuiApplication>
+#include <QJsonDocument>
+#include <QQuickItem>
+#include <QRandomGenerator>
+#include <QRegularExpression>
+#include <QResource>
+#include <QStandardPaths>
+#include <QTemporaryDir>
+#include <QTemporaryFile>
+#include <QtGui/private/qzipreader_p.h>
+
+#define QSTRN QString::number
+
+ProjectManager::ProjectManager(QObject *parent)
+    : QObject(parent)
+{
+    qDebug() << "ProjectManager created.";
+    m_projectCachePath = QStandardPaths::writableLocation(QStandardPaths::AppDataLocation);
+}
+
+QString ProjectManager::unpackProject(const QByteArray &project, bool extractZip)
+{
+    QTemporaryDir tempDir("qmlprojector");
+    QString projectPath = tempDir.path();
+
+    if (extractZip) {
+        QDir().mkpath(projectPath);
+        QBuffer buffer;
+        buffer.setData(project);
+        buffer.open(QIODevice::ReadOnly);
+        QZipReader reader(&buffer);
+        reader.extractAll(projectPath);
+    }
+
+    qDebug() << "Initial project location: " << projectPath;
+
+    QDir projectPathDir(projectPath);
+
+    // maybe it was not a zip file so try it as resource binary
+    if (projectPathDir.isEmpty()) {
+        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: " << QString::number(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.");
+            }
+        }
+
+        m_projectData = project;
+        data = reinterpret_cast<const uchar *>(m_projectData.data());
+        qDebug() << "Registering resource data. Size: " << QString::number(m_projectData.size());
+
+        const QString resourcePath{"/" + QString::number(QRandomGenerator::global()->generate())};
+        m_projectPath = resourcePath;
+
+        if (!QDir(resourcePath).removeRecursively()) {
+            qDebug() << "Could not remove resource path: " << resourcePath;
+        }
+
+        if (!QResource::registerResource(data, resourcePath)) {
+            qCritical("Can not load the resource data.");
+            return "";
+        }
+
+        projectPath = ":" + resourcePath;
+    }
+    return projectPath;
+}
+
+QString ProjectManager::findFile(const QString &dir, const QString &filter)
+{
+    QDirIterator it(dir, {filter}, QDir::Files, QDirIterator::Subdirectories);
+    return it.next();
+}
+
+void ProjectManager::parseQmlProjectFile(const QString &fileName,
+                                         QString *mainFile,
+                                         QStringList *importPaths)
+{
+    /* if filename comes from a resource, then qml need qrc:/ at the mainfile and importPaths.
+     * But all other c++ call like QFileInfo::exists do not understand that, there we
+     * need to keep the only ":" character at the beginning of the string
+     */
+    QFile file(fileName);
+    if (!file.open(QIODevice::ReadOnly)) {
+        qCritical() << "Could not open Qml Project file! " << fileName + ": " << file.errorString();
+        return;
+    }
+
+    const QString text = QString::fromUtf8(file.readAll());
+
+    const QRegularExpression mainFileRegExp("mainFile:\\s*\"(.*)\"");
+    const QRegularExpressionMatch mainFileMatch = mainFileRegExp.match(text);
+
+    if (!mainFileMatch.hasMatch()) {
+        qCritical() << "No main file found in " << fileName;
+        return;
+    }
+
+    qDebug() << "Found main file: " << mainFileMatch.captured(1);
+    QString basePath = QFileInfo(fileName).path() + "/";
+
+    *mainFile = basePath + mainFileMatch.captured(1);
+    if (mainFile->startsWith(QLatin1String(":/")))
+        *mainFile = "qrc:" + mainFile->mid(1);
+
+    const QRegularExpression qt6ProjectRegExp("qt6Project:\\s*true");
+    const QRegularExpressionMatch qt6ProjectMatch = qt6ProjectRegExp.match(text);
+    if (!qt6ProjectMatch.hasMatch()) {
+        qWarning("This is not a Qt6 project.\nQt5 projects might work, but they are not "
+                 "officially supported.");
+    }
+
+    const QRegularExpression importPathsRegExp("importPaths:\\s*\\[\\s*(.*)\\s*\\]");
+    const QRegularExpressionMatch importPathsMatch = importPathsRegExp.match(text);
+
+    if (importPathsMatch.hasMatch()) {
+        for (const QString &path : importPathsMatch.captured(1).split(",")) {
+            QString cleanedPath = path.trimmed();
+            cleanedPath = basePath + cleanedPath.mid(1, cleanedPath.length() - 2);
+            if (QFileInfo::exists(cleanedPath)) {
+                if (cleanedPath.startsWith(QLatin1String(":/")))
+                    cleanedPath = "qrc:" + cleanedPath.mid(1);
+                importPaths->append(cleanedPath);
+            }
+        }
+    }
+}
+
+bool ProjectManager::runProject(const QString &projectPath, const QString &projectName)
+{
+    const QString newProjectName = QString(projectName).remove(".qmlrc").remove("#");
+    qDebug() << "Project location: " << projectPath;
+    qDebug() << "Project name: " << projectName;
+    qDebug() << "New project name: " << newProjectName;
+
+    QString mainQmlFilePath;
+    QStringList importPaths;
+
+    qDebug() << "Looking for qmlproject file in " << projectPath;
+    const QString qmlProjectFile = findFile(projectPath, newProjectName + "*.qmlproject");
+    if (!qmlProjectFile.isEmpty()) {
+        qDebug() << "Found qmlproject file: " << qmlProjectFile;
+        parseQmlProjectFile(qmlProjectFile, &mainQmlFilePath, &importPaths);
+    } else {
+        qWarning("Not found: \"*.qmlproject\". Looking for main.qml..");
+        mainQmlFilePath = findFile(projectPath, "main.qml");
+
+        if (mainQmlFilePath.isEmpty()) {
+            qWarning() << "Not found: \"main.qml\". Looking for \"" << newProjectName << ".qml\"..";
+            mainQmlFilePath = findFile(projectPath, newProjectName + ".qml");
+        }
+    }
+
+    if (mainQmlFilePath.isEmpty()) {
+        qCritical() << "No \"*.qmlproject\", \"main.qml\" or \"" << newProjectName
+                    << ".qml\" found in \"" << projectPath << "\".";
+        return false;
+    }
+
+    qDebug() << "Found mainQmlFile: " + mainQmlFilePath;
+
+    QUrl mainQmlUrl = QUrl::fromUserInput(mainQmlFilePath);
+    QFile file(mainQmlUrl.path());
+    // QFile file(mainQmlUrl.path().prepend(":"));
+    if (!file.open(QIODevice::ReadOnly)) {
+        qCritical() << "Could not open mainQmlfile for reading! " << file.fileName() << ": "
+                    << file.errorString();
+        return false;
+    }
+
+    qDebug() << "Looking for qtquickcontrols2File in " << projectPath;
+    const QString qtquickcontrols2File = findFile(projectPath, "qtquickcontrols2.conf");
+
+    if (!qtquickcontrols2File.isEmpty()) {
+        qDebug() << "Found qtquickcontrols2File: " << qtquickcontrols2File;
+        qputenv("QT_QUICK_CONTROLS_CONF", qtquickcontrols2File.toLatin1());
+    }
+
+    qDebug("Adding import paths");
+    for (const QString &importPath : importPaths) {
+        qDebug() << "-- Import path: " << importPath;
+        m_qmlEngine.addImportPath(importPath);
+    }
+
+    QObject::connect(&m_qmlEngine,
+                     &QQmlEngine::warnings,
+                     this,
+                     [&](const QList<QQmlError> &warnings) {
+                         for (const auto &warning : warnings) {
+                             qWarning() << warning.toString();
+                         }
+                     });
+
+    emit projectStateChanged("Loading project...");
+    QEventLoop().processEvents(QEventLoop::AllEvents, 1000);
+
+    qDebug() << "Loading mainQmlUrl: " << mainQmlUrl.toString();
+    m_qmlComponent.reset(new QQmlComponent(&m_qmlEngine));
+    m_qmlComponent->loadUrl(mainQmlUrl);
+
+    qDebug() << "Waiting for qmlComponent to load";
+    while (m_qmlComponent->isLoading())
+        QCoreApplication::processEvents();
+
+    qDebug() << "Checking if m_qmlComponent is ready";
+    if (!m_qmlComponent->isReady()) {
+        qCritical() << "m_qmlComponent is not ready. Reason:" << m_qmlComponent->errorString();
+        return false;
+    }
+    qDebug() << "Creating top level object";
+    QObject *topLevel = m_qmlComponent->create();
+    if (!topLevel && m_qmlComponent->isError()) {
+        qCritical() << "Error while creating Qml m_qmlComponent:" << m_qmlComponent->errorString();
+        return false;
+    }
+
+    emit projectStateChanged("Setting up the quickWindow...");
+    QEventLoop().processEvents(QEventLoop::AllEvents, 1000);
+
+    qDebug() << "Setting up the quickWindow";
+    m_quickWindow.reset(qobject_cast<QQuickWindow *>(topLevel));
+    if (m_quickWindow) {
+        qDebug() << "Running with incubator controller";
+        m_qmlEngine.setIncubationController(m_quickWindow->incubationController());
+    } else {
+        qWarning() << "Top level object is not a QQuickWindow. Trying QQuickView...";
+
+        QQuickItem *contentItem = qobject_cast<QQuickItem *>(topLevel);
+        if (!contentItem) {
+            qCritical() << "Top level object cannot be casted to QQuickItem. Aborting.";
+            return false;
+        }
+
+        qDebug() << "Initializing QQuickView";
+        QQuickView *view = new QQuickView(&m_qmlEngine, nullptr);
+        m_quickWindow.reset(view);
+        view->setContent(mainQmlUrl, m_qmlComponent.data(), contentItem);
+        view->setResizeMode(QQuickView::SizeViewToRootObject);
+        m_quickWindow->setBaseSize(QSize(contentItem->width(), contentItem->height()));
+    }
+    return true;
+}
+
+bool ProjectManager::cacheProject(const QByteArray &projectData, const QJsonObject &projectInfo)
+{
+    // sample project info
+    /*
+     {
+        "appName": "Testmcu",
+        "id": "0sadf8fa9s8df67s8998690a7sdf",
+        "owner": "",
+        "passwordHash": "",
+        "qdsIsEnterprise": true,
+        "qdsVersion": "Qt Design Studio 4.2.0",
+        "ttlDays": 31,
+        "uploadTime": "2023-10-27T13:57:22",
+        "userHash": "12038740912873462987"
+      },
+    */
+
+    const QString projectId = projectInfo.value("id").toString();
+
+    qDebug() << "Caching project " << projectId << " with last modified "
+             << projectInfo.value("uploadTime");
+    const QString cachePath = m_projectCachePath + "/" + projectId;
+
+    // remove old cache
+    if (QDir(cachePath).exists()) {
+        qDebug() << "Removing old cache for project " << projectId;
+        if (!QDir(cachePath).removeRecursively()) {
+            qCritical() << "Could not remove old cache for project " << projectId;
+            return false;
+        }
+    }
+
+    QDir().mkpath(cachePath);
+    const QString tempProjectPath = unpackProject(projectData);
+
+    // copy all files from tempProjectPath to cachePath
+    QDirIterator it(tempProjectPath, QDirIterator::Subdirectories);
+    while (it.hasNext()) {
+        it.next();
+        const QString filePath = it.filePath();
+        const QString relativeFilePath = filePath.mid(tempProjectPath.length());
+        const QString newFilePath = cachePath + relativeFilePath;
+        qDebug() << "Copying " << filePath << " to " << newFilePath;
+        if (QFileInfo(filePath).isDir()) {
+            QDir().mkpath(newFilePath);
+        } else {
+            QFile::copy(filePath, newFilePath);
+        }
+    }
+
+    // write project info to cache
+    QFile lastModifiedFile(cachePath + "/projectInfo.json");
+    if (!lastModifiedFile.open(QIODevice::WriteOnly)) {
+        qCritical() << "Could not open projectInfo file for writing";
+        return false;
+    }
+
+    lastModifiedFile.write(QJsonDocument(projectInfo).toJson());
+    return true;
+}
+
+bool ProjectManager::isProjectCached(const QJsonObject &projectInfo)
+{
+    const QString projectId = projectInfo.value("id").toString();
+    const QString lastModified = projectInfo.value("uploadTime").toString();
+
+    qDebug() << "Checking if project " << projectId << " is cached";
+    const QString cachePath = m_projectCachePath + "/" + projectId;
+    if (!QDir(cachePath).exists()) {
+        qDebug() << "Project " << projectId << " is not cached";
+        return false;
+    }
+
+    qDebug() << "Project " << projectId << " is cached. Checking if it is up to date";
+    QFile lastModifiedFile(cachePath + "/projectInfo.json");
+    if (!lastModifiedFile.open(QIODevice::ReadOnly)) {
+        qCritical() << "Could not open projectInfo file for reading";
+        return false;
+    }
+
+    const QJsonObject cachedProjectInfo
+        = QJsonDocument::fromJson(lastModifiedFile.readAll()).object();
+    const QString cachedLastModified = cachedProjectInfo.value("uploadTime").toString();
+
+    qDebug() << "Project " << projectId << " last modified: " << lastModified;
+    qDebug() << "Cached project " << projectId << " last modified: " << cachedLastModified;
+
+    if (lastModified != cachedLastModified) {
+        qDebug() << "Project " << projectId << " is not up to date";
+        return false;
+    }
+
+    qDebug() << "Project " << projectId << " is up to date";
+    return true;
+}
+
+bool ProjectManager::runCachedProject(const QJsonObject &projectInfo)
+{
+    const QString projectId = projectInfo.value("id").toString();
+    const QString projectName = projectInfo.value("appName").toString();
+    qDebug() << "Running cached project " << projectId;
+
+    const QString projectPath = m_projectCachePath + "/" + projectId;
+    return runProject(projectPath, projectName);
+}
+
+void ProjectManager::orientateWindow(Qt::ScreenOrientation orientation)
+{
+    QQuickItem *contentItem = m_quickWindow->contentItem();
+    QQuickItem *childItem{contentItem->childItems().at(0)};
+    QQuickItem *targetItem = childItem;
+
+    const QRect screenGeometry = QGuiApplication::primaryScreen()->geometry();
+    const QString orientationString = (orientation == Qt::LandscapeOrientation ? "landscape"
+                                                                               : "portrait");
+    qDebug() << "Adapting orientation to " << orientationString << " mode";
+    qDebug() << "Initial sizing:";
+    qDebug() << "-- Screen size: " << QSTRN(screenGeometry.height()) << " x "
+             << QSTRN(screenGeometry.width());
+    qDebug() << "-- Quick window size: " << QSTRN(m_quickWindow->height()) << " x "
+             << QSTRN(m_quickWindow->width());
+    qDebug() << "-- Child size: " << QSTRN(targetItem->height()) << " x "
+             << QSTRN(targetItem->width());
+    qDebug() << "-- Child pos: " << QSTRN(targetItem->x()) << ", " << QSTRN(targetItem->y());
+    qDebug() << "-- Child scale: " << QSTRN(targetItem->scale());
+
+    qDebug("Calculating the new size and scale...");
+
+    const QSizeF newContentSize = targetItem->size().scaled(screenGeometry.size().toSizeF(),
+                                                            Qt::AspectRatioMode::KeepAspectRatio);
+
+    const qreal newScale = newContentSize.height() / targetItem->size().height();
+    const qreal newX = (targetItem->width() - screenGeometry.width()) / -2.0f;
+    const qreal newY = (targetItem->height() - screenGeometry.height()) / -2.0f;
+
+    targetItem->setScale(newScale);
+    targetItem->setPosition(QPointF(newX, newY));
+
+    qDebug() << "-- Calculated item height: " << QSTRN(newContentSize.height());
+    qDebug() << "-- Calculated item width: " << QSTRN(newContentSize.width());
+    qDebug() << "-- Calculated item scale: " << QSTRN(newScale);
+    qDebug() << "-- Calculated item pos..: " << QSTRN(newX) << "," << QSTRN(newY);
+
+    qDebug() << "Final Sizing:";
+    qDebug() << "-- Child height: " << QSTRN(targetItem->height());
+    qDebug() << "-- Child width: " << QSTRN(targetItem->width());
+    qDebug() << "-- Child scale: " << QSTRN(targetItem->scale());
+    qDebug() << "-- Child pos-x: " << QSTRN(targetItem->x());
+    qDebug() << "-- Child pos-y: " << QSTRN(targetItem->y());
+}
+
+void ProjectManager::showAppWindow()
+{
+    QScreen *screen = QGuiApplication::primaryScreen();
+    QObject::connect(screen, &QScreen::orientationChanged, this, &ProjectManager::orientateWindow);
+    orientateWindow(screen->orientation());
+
+    qDebug("Initializing and showing the QML app window");
+    m_quickWindow->show();
+}
+
+void ProjectManager::hideAppWindow()
+{
+    qDebug("Hiding the QML app window");
+    m_quickWindow->hide();
+}
diff --git a/src/projectManager.h b/src/projectManager.h
new file mode 100644
index 0000000000000000000000000000000000000000..e036e72867c0f9a90280854c4738605423f8c047
--- /dev/null
+++ b/src/projectManager.h
@@ -0,0 +1,73 @@
+/****************************************************************************
+**
+** Copyright (C) 2023 The Qt Company Ltd.
+** Contact: https://www.qt.io/licensing/
+**
+** This file is part of the Qt Design Viewer of the Qt Toolkit.
+**
+** Commercial License Usage
+** Licensees holding valid commercial Qt licenses may use this file in
+** accordance with the commercial license agreement provided with the
+** Software or, alternatively, in accordance with the terms contained in
+** a written agreement between you and The Qt Company. For licensing terms
+** and conditions see https://www.qt.io/terms-conditions. For further
+** information use the contact form at https://www.qt.io/contact-us.
+**
+** GNU General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU
+** General Public License version 3 as published by the Free Software
+** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT
+** included in the packaging of this file. Please review the following
+** information to ensure the GNU General Public License requirements will
+** be met: https://www.gnu.org/licenses/gpl-3.0.html.
+**
+****************************************************************************/
+
+#ifndef PROJECTMANAGER_H
+#define PROJECTMANAGER_H
+
+#include <QJsonObject>
+#include <QObject>
+#include <QQmlComponent>
+#include <QQmlEngine>
+#include <QQuickView>
+#include <QQuickWindow>
+
+class ProjectManager : public QObject
+{
+    Q_OBJECT
+public:
+    explicit ProjectManager(QObject *parent = nullptr);
+    QString unpackProject(const QByteArray &project, bool extractZip = false);
+    bool runProject(const QString &projectPath, const QString &projectName);
+    bool cacheProject(const QByteArray &projectData, const QJsonObject &projectInfo);
+    bool isProjectCached(const QJsonObject &projectInfo);
+    bool runCachedProject(const QJsonObject &projectInfo);
+    void showAppWindow();
+    void hideAppWindow();
+
+public slots:
+    void orientateWindow(Qt::ScreenOrientation orientation);
+
+private:
+    // Member variables
+    QByteArray m_projectData;
+    QString m_projectPath;
+    QString m_projectCachePath;
+
+    // Qml related members
+    QQmlEngine m_qmlEngine;
+    QSharedPointer<QQmlComponent> m_qmlComponent;
+    QSharedPointer<QQuickWindow> m_quickWindow;
+
+    // Member functions
+    QString findFile(const QString &dir, const QString &filter);
+
+    void parseQmlProjectFile(const QString &fileName, QString *mainFile, QStringList *importPaths);
+    void cacheProject(const QString &projectName, const QString &projectPath);
+
+signals:
+    void projectStateChanged(const QString &state);
+};
+
+#endif // PROJECTMANAGER_H
diff --git a/src/serviceConnector.cpp b/src/serviceConnector.cpp
new file mode 100644
index 0000000000000000000000000000000000000000..fe95e0565a49a3085998a4d08c60c63ee55f6419
--- /dev/null
+++ b/src/serviceConnector.cpp
@@ -0,0 +1,106 @@
+/****************************************************************************
+**
+** Copyright (C) 2023 The Qt Company Ltd.
+** Contact: https://www.qt.io/licensing/
+**
+** This file is part of the Qt Design Viewer of the Qt Toolkit.
+**
+** Commercial License Usage
+** Licensees holding valid commercial Qt licenses may use this file in
+** accordance with the commercial license agreement provided with the
+** Software or, alternatively, in accordance with the terms contained in
+** a written agreement between you and The Qt Company. For licensing terms
+** and conditions see https://www.qt.io/terms-conditions. For further
+** information use the contact form at https://www.qt.io/contact-us.
+**
+** GNU General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU
+** General Public License version 3 as published by the Free Software
+** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT
+** included in the packaging of this file. Please review the following
+** information to ensure the GNU General Public License requirements will
+** be met: https://www.gnu.org/licenses/gpl-3.0.html.
+**
+****************************************************************************/
+
+#include "serviceConnector.h"
+
+#include <QNetworkAccessManager>
+#include <QNetworkReply>
+#include <QEventLoop>
+#include <QJsonDocument>
+#include <QJsonObject>
+
+QSharedPointer<QNetworkReply> ServiceConnector::fetchResource(const QString &url)
+{
+    qDebug() << "Fetching resource from" << url;
+
+    QNetworkRequest request(url);
+    request.setRawHeader("Authorization", "test");
+    QSharedPointer<QNetworkReply> reply(m_manager.get(request));
+    QObject::connect(reply.data(),
+                     &QNetworkReply::sslErrors,
+                     this,
+                     [&](const QList<QSslError> &errors)
+                     {
+                         qCritical() << errors.first().errorString();
+                     });
+
+    QEventLoop loop;
+    QObject::connect(reply.data(), &QNetworkReply::finished, &loop, &QEventLoop::quit);
+    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);
+                         emit downloadProgress(percentage);
+                     });
+    loop.exec();
+
+    if (reply->error() != QNetworkReply::NoError)
+        qCritical() << reply->errorString();
+    else
+        qDebug() << "Resource fetched successfully";
+
+    return reply;
+}
+
+QByteArray ServiceConnector::fetchProject(const QString &url)
+{
+    QString projectUrl = url;
+    if (projectUrl.startsWith("https://designviewer.qt.io/#"))
+    {
+        projectUrl = projectUrl.split("#").at(1);
+        projectUrl.prepend("https://designviewer.qt.io/qmlprojects/");
+    }
+
+    auto reply = fetchResource(projectUrl);
+    if (reply->error() != QNetworkReply::NoError)
+    {
+        qCritical("Could not fetch project");
+        return QByteArray();
+    }
+
+    return reply->readAll();
+}
+
+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)
+    {
+        qCritical("Could not fetch available project list");
+        return QJsonArray();
+    }
+
+    QJsonArray projectList = QJsonDocument::fromJson(reply->readAll())
+                                 .object()
+                                 .value("data")
+                                 .toObject()
+                                 .value("packages")
+                                 .toArray();
+
+    return projectList;
+}
diff --git a/src/serviceConnector.h b/src/serviceConnector.h
new file mode 100644
index 0000000000000000000000000000000000000000..b552806d758fb9651861c24f7a671f45a3349b95
--- /dev/null
+++ b/src/serviceConnector.h
@@ -0,0 +1,47 @@
+/****************************************************************************
+**
+** Copyright (C) 2023 The Qt Company Ltd.
+** Contact: https://www.qt.io/licensing/
+**
+** This file is part of the Qt Design Viewer of the Qt Toolkit.
+**
+** Commercial License Usage
+** Licensees holding valid commercial Qt licenses may use this file in
+** accordance with the commercial license agreement provided with the
+** Software or, alternatively, in accordance with the terms contained in
+** a written agreement between you and The Qt Company. For licensing terms
+** and conditions see https://www.qt.io/terms-conditions. For further
+** information use the contact form at https://www.qt.io/contact-us.
+**
+** GNU General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU
+** General Public License version 3 as published by the Free Software
+** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT
+** included in the packaging of this file. Please review the following
+** information to ensure the GNU General Public License requirements will
+** be met: https://www.gnu.org/licenses/gpl-3.0.html.
+**
+****************************************************************************/
+
+#ifndef SERVICECONNECTOR_H
+#define SERVICECONNECTOR_H
+
+#include <QNetworkAccessManager>
+#include <QJsonArray>
+
+class ServiceConnector : public QObject
+{
+    Q_OBJECT
+public:
+    QByteArray fetchProject(const QString &url);
+    QJsonArray fetchUserProjectList(const QString &userHash);
+
+private:
+    QNetworkAccessManager m_manager;
+    QSharedPointer<QNetworkReply> fetchResource(const QString &url);
+
+signals:
+    void downloadProgress(float percentage);
+};
+
+#endif // SERVICECONNECTOR_H
diff --git a/ui/AboutHeader.qml b/ui/AboutHeader.qml
index 124714b3de40a9f8733ba3205f5f58dc0268a03e..882880f94c8d0fd98925778879109e4dd7eddc68 100644
--- a/ui/AboutHeader.qml
+++ b/ui/AboutHeader.qml
@@ -18,21 +18,24 @@ Item {
             Layout.fillWidth: true
         }
 
-
-
         Label {
-            id: label2
-            text: backend.buildInfo
+            id: logs
             Layout.alignment: Qt.AlignHCenter | Qt.AlignVCenter
             horizontalAlignment: "AlignHCenter"
+            text: qsTr("Hello World")
         }
 
-
         Item {
             id: item1
             Layout.fillHeight: true
             Layout.fillWidth: true
         }
 
+        Connections {
+            target: backend
+            function onBuildInfoChanged(buildInfo){
+                logs.text = buildInfo;
+            }
+        }
     }
 }
diff --git a/ui/ExamplesPage.qml b/ui/ExamplesPage.qml
index cb40cd5ffffffc3269ef30647ee7e59dd3fa25ea..c67367628c4112cdf08abbcda2b4008827b154fc 100644
--- a/ui/ExamplesPage.qml
+++ b/ui/ExamplesPage.qml
@@ -16,12 +16,6 @@ Item {
             onClicked: backend.downloadAndRun("https://designviewer.qt.io/#ClusterTutorial.qmlrc")
         }
 
-        Button {
-            id: button5
-            text: qsTr("Coffee Machine")
-            Layout.fillWidth: true
-            onClicked: backend.downloadAndRun("https://designviewer.qt.io/#CoffeeMachine.qmlrc")
-        }
 
         Button {
             id: button
@@ -30,20 +24,6 @@ Item {
             onClicked: backend.downloadAndRun("https://designviewer.qt.io/#EBikeDesign.qmlrc")
         }
 
-        Button {
-            id: button2
-            text: qsTr("Side Menu")
-            Layout.fillWidth: true
-            onClicked: backend.downloadAndRun("https://designviewer.qt.io/#SideMenu.qmlrc")
-        }
-
-        Button {
-            id: button1
-            text: qsTr("Webinar Demo")
-            Layout.fillWidth: true
-            onClicked: backend.downloadAndRun("https://designviewer.qt.io/#WebinarDemo.qmlrc")
-        }
-
         Button {
             id: button4
             text: qsTr("Material Bundle")
diff --git a/ui/HomePage.qml b/ui/HomePage.qml
index 714d40fadd2c4aa4f706d8d582581a47e0bef2d6..515b68cb07068e049ab5907cb02d52053945ac18 100644
--- a/ui/HomePage.qml
+++ b/ui/HomePage.qml
@@ -83,12 +83,10 @@ Item {
 
             RowLayout {
                 id: rowLayout
-                anchors.left: parent.left
-                anchors.right: parent.right
                 Button {
                     id: downloadButton
                     text: qsTr("Download and Run")
-                    onClicked: backend.downloadAndRun(urlTextField.text)
+                    onClicked: backend.runUserProject(urlTextField.text)
                     Layout.fillWidth: true
                     Layout.alignment: Qt.AlignHCenter | Qt.AlignVCenter
                 }
@@ -101,7 +99,6 @@ Item {
             Layout.fillHeight: true
             Layout.fillWidth: true
             Layout.preferredHeight: 10
-
             Layout.preferredWidth: 10
         }
 
diff --git a/ui/Logs.qml b/ui/Logs.qml
index 695223c69b1f72c6d3dbc784bd8cce966b5a93e8..44527d5c8a254e83d7a2294414331b190d054e54 100644
--- a/ui/Logs.qml
+++ b/ui/Logs.qml
@@ -15,23 +15,23 @@ Rectangle {
         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
             wrapMode: TextEdit.Wrap
-            anchors.fill: parent
-            text: backend.logs
             rightInset: 20
             readOnly: true
             anchors.topMargin: 5
             placeholderText: qsTr("Application Logs")
         }
+
+        Connections {
+            target: backend
+            function onLogsChanged(logs) {
+                logTextArea.text = logs
+                scrollArea.ScrollBar.vertical.position = 1.0 - scrollArea.ScrollBar.vertical.size
+            }
+        }
     }
 }
diff --git a/ui/main.qml b/ui/main.qml
index ad11c1a0a1dc6819f4237d10f3f38b6bd127aede..256b60a42d59cb7a005f504c87cd7f707a35ee67 100644
--- a/ui/main.qml
+++ b/ui/main.qml
@@ -47,7 +47,6 @@ Rectangle {
             anchors.fill: parent
             Text {
                 id: popupText
-                text: backend.popupText
             }
             Item {
                 id: name
@@ -55,9 +54,7 @@ Rectangle {
             ProgressBar {
                 id: popupProgressBar
                 Layout.fillWidth: true
-                value: backend.downloadProgress
                 to: 100
-                indeterminate: backend.popupProgressIndeterminate
             }
         }
 
@@ -75,6 +72,9 @@ Rectangle {
             function onPopupProgressIndeterminateChanged(status) {
                 popupProgressBar.indeterminate = status
             }
+            function onDownloadProgress(progress) {
+                popupProgressBar.value = progress
+            }
         }
     }
 
@@ -148,7 +148,9 @@ Rectangle {
 
                     Connections {
                         target: home
-                        onClicked: stackLayout.currentIndex = 0
+                        function onClicked(){
+                            stackLayout.currentIndex = 0
+                        }
                     }
                 }
 
@@ -162,7 +164,9 @@ Rectangle {
 
                     Connections {
                         target: examples
-                        onClicked: stackLayout.currentIndex = 1
+                        function onClicked(){
+                            stackLayout.currentIndex = 1
+                        }
                     }
                 }
 
@@ -176,7 +180,9 @@ Rectangle {
 
                     Connections {
                         target: logs
-                        onClicked: stackLayout.currentIndex = 2
+                        function onClicked(){
+                            stackLayout.currentIndex = 2
+                        }
                     }
                 }
 
@@ -190,13 +196,11 @@ Rectangle {
 
                     Connections {
                         target: about
-                        onClicked: stackLayout.currentIndex = 3
+                        function onClicked(){
+                            stackLayout.currentIndex = 3
+                        }
                     }
                 }
-
-
-
-
             }
         }
     }