diff --git a/ScreenPlay/main.qml b/ScreenPlay/main.qml index cde43a9d..fc8aa765 100644 --- a/ScreenPlay/main.qml +++ b/ScreenPlay/main.qml @@ -34,11 +34,17 @@ ApplicationWindow { function switchPage(name) { if (nav.currentNavigationName === name) { if (name === "Installed") - ScreenPlay.installedListModel.reset(); - + ScreenPlay.installedListModel.reset() } - stackView.replace("qrc:/qml/" + name + "/" + name + ".qml"); - sidebar.state = "inactive"; + + if (name === "Installed") { + stackView.replace("qrc:/qml/" + name + "/" + name + ".qml", { + "sidebar": sidebar + }) + return + } + stackView.replace("qrc:/qml/" + name + "/" + name + ".qml") + sidebar.state = "inactive" } color: Material.theme === Material.Dark ? Qt.darker(Material.background) : Material.background diff --git a/ScreenPlay/qml/Installed/Installed.qml b/ScreenPlay/qml/Installed/Installed.qml index 5b0d8809..27ed7ccd 100644 --- a/ScreenPlay/qml/Installed/Installed.qml +++ b/ScreenPlay/qml/Installed/Installed.qml @@ -13,6 +13,7 @@ Item { property bool refresh: false property bool enabled: true + property Sidebar sidebar signal setNavigationItem(var pos) signal setSidebarActive(var active) @@ -102,6 +103,36 @@ Item { onDragStarted: isDragging = true onDragEnded: isDragging = false model: ScreenPlay.installedListFilter + removeDisplaced: Transition { + SequentialAnimation { + PauseAnimation { + duration: 150 + } + NumberAnimation { + properties: "x,y" + duration: 250 + easing.type: Easing.InOutQuart + } + } + } + + remove: Transition { + SequentialAnimation { + + NumberAnimation { + property: "opacity" + to: 0 + duration: 200 + easing.type: Easing.InOutQuart + } + NumberAnimation { + properties: "y" + to: 100 + duration: 200 + easing.type: Easing.InOutQuart + } + } + } onContentYChanged: { if (contentY <= -180) gridView.headerItem.isVisible = true; @@ -262,8 +293,8 @@ Item { enabled: contextMenu.publishedFileID !== 0 icon.source: "qrc:/assets/icons/icon_steam.svg" onClicked: { - print(contextMenu.publishedFileID) - Qt.openUrlExternally("steam://url/CommunityFilePage/" + contextMenu.publishedFileID); + Qt.openUrlExternally( + "steam://url/CommunityFilePage/" + contextMenu.publishedFileID) } } @@ -271,15 +302,16 @@ Item { Dialog { id: deleteDialog - - property int currentItemIndex: 0 - title: qsTr("Are you sure you want to delete this item?") standardButtons: Dialog.Ok | Dialog.Cancel modal: true dim: true anchors.centerIn: Overlay.overlay - onAccepted: ScreenPlay.installedListModel.deinstallItemAt(currentItemIndex) + onAccepted: { + root.sidebar.clear() + ScreenPlay.installedListModel.deinstallItemAt( + contextMenu.absoluteStoragePath) + } } Navigation { diff --git a/ScreenPlay/qml/Installed/Sidebar.qml b/ScreenPlay/qml/Installed/Sidebar.qml index 8b9cd585..93cf5a54 100644 --- a/ScreenPlay/qml/Installed/Sidebar.qml +++ b/ScreenPlay/qml/Installed/Sidebar.qml @@ -29,6 +29,14 @@ Item { return -1; } + // This is used for removing wallpaper. We need to clear + // the preview image/gif so we can release the file for deletion. + function clear(){ + image.source = "" + txtHeadline.text = "" + root.state = "inactive" + } + width: 400 state: "inactive" onContentFolderNameChanged: { @@ -48,8 +56,10 @@ Item { btnSetWallpaper.enabled = false; } + Connections { function onSetSidebarItem(folderName, type) { + // Toggle sidebar if clicked on the same content twice if (root.contentFolderName === folderName && root.state !== "inactive") { root.state = "inactive"; @@ -152,10 +162,12 @@ Item { AnimatedImage { id: image - + // Do NOT enable async image loading! + // Otherwhise it will still hold the file + // when calling InstalledListModel::deinstallItemAt + asynchronous: false playing: true fillMode: Image.PreserveAspectCrop - asynchronous: true anchors.fill: parent onStatusChanged: { if (image.status === Image.Error) diff --git a/ScreenPlay/src/installedlistmodel.cpp b/ScreenPlay/src/installedlistmodel.cpp index 9a9c59bb..76c23608 100644 --- a/ScreenPlay/src/installedlistmodel.cpp +++ b/ScreenPlay/src/installedlistmodel.cpp @@ -45,27 +45,65 @@ void InstalledListModel::init() /*! \brief Deleted the item from the local storage and removes it from the - installed list. + installed list. We wait for the qml engine to free all resources before + we proceed. This like the preview.gif will be in use when clicking on an item */ -bool InstalledListModel::deinstallItemAt(const int index) +void InstalledListModel::deinstallItemAt(const QString& absoluteStoragePath) { - if (index < 0 || index >= m_screenPlayFiles.count()) { - qWarning() << "remove folder error, invalid index " << index; - return false; - } + QTimer::singleShot(1000, this, [this, absoluteStoragePath]() { + int index = -1; + for (int i = 0; i < m_screenPlayFiles.size(); ++i) { + if (m_screenPlayFiles.at(i).m_absoluteStoragePath.toString() == absoluteStoragePath) { + index = i; + break; + } + } - beginRemoveRows(QModelIndex(), index, index); - const QString path = QUrl::fromUserInput(m_screenPlayFiles.at(index).m_absoluteStoragePath.toString()).toLocalFile(); + if (index < 0 || index >= m_screenPlayFiles.count()) { + qWarning() << "Remove folder error, invalid index " << index; + return; + } - QDir dir(path); - const bool success = dir.removeRecursively(); + beginRemoveRows(QModelIndex(), index, index); + m_screenPlayFiles.removeAt(index); + endRemoveRows(); - if (!success) - qWarning() << "Could not remove folder: " << m_screenPlayFiles.at(index).m_absoluteStoragePath.toString(); + const QString path = ScreenPlayUtil::toLocal(absoluteStoragePath); - m_screenPlayFiles.removeAt(index); - endRemoveRows(); - return success; + QDir dir(path); + bool success = true; + if (!dir.exists()) { + qWarning() << "Directory does not exist!" << dir; + return; + } + + // We must pause the QFileSystemWatcher to not trigger + // a reload for every removed file + m_fileSystemWatcher.blockSignals(true); + for (auto& item : dir.entryInfoList(QDir::Files)) { + if (!QFile::remove(item.absoluteFilePath())) { + qWarning() << "Unable to remove file:" << item; + success = false; + break; + } + } + + if (!success) { + qWarning() << "Could not remove folder content at: " << path; + loadInstalledContent(); + } + + if (!dir.rmdir(path)) { + qWarning() << "Could not remove folder at: " << path; + return; + } + + // Add delay to the watcher, because it was trigger by + // something when enabling after the removal. + QTimer::singleShot(3000, this, [this]() { + m_fileSystemWatcher.blockSignals(false); + }); + }); } /*! @@ -156,11 +194,17 @@ void InstalledListModel::append(const QJsonObject& obj, const QString& folderNam } /*! - \brief Loads all installed content. Skips projects.json without a "type" field. + \brief Loads all installed content. + - Skips if the loadContentFuture is already running. + - Skips projects.json without a "type" field. */ void InstalledListModel::loadInstalledContent() { - QtConcurrent::run([this]() { + qInfo() << "loadInstalledContent"; + if (m_loadContentFuture.isRunning()) + return; + + m_loadContentFuture = QtConcurrent::run([this]() { QFileInfoList list = QDir(m_globalVariables->localStoragePath().toLocalFile()).entryInfoList(QDir::NoDotAndDotDot | QDir::AllDirs); int counter = 0; @@ -198,27 +242,33 @@ void InstalledListModel::loadInstalledContent() } /*! - \brief . + \brief Used for receiving values from qml. One must add all new fields + when adding new roles to this model. */ -QVariantMap InstalledListModel::get(const QString& folderId) const +QVariantMap InstalledListModel::get(const QString& folderName) const { if (m_screenPlayFiles.count() == 0) return {}; - QVariantMap map; - for (int i = 0; i < m_screenPlayFiles.count(); i++) { + const QString localInstalledPath = ScreenPlayUtil::toLocal(m_globalVariables->localStoragePath().toString()); - if (m_screenPlayFiles[i].m_folderId == folderId) { - map.insert("m_title", m_screenPlayFiles[i].m_title); - map.insert("m_preview", m_screenPlayFiles[i].m_preview); - map.insert("m_previewGIF", m_screenPlayFiles[i].m_previewGIF); - map.insert("m_file", m_screenPlayFiles[i].m_file); - map.insert("m_type", QVariant::fromValue(m_screenPlayFiles[i].m_type)); - map.insert("m_absoluteStoragePath", m_screenPlayFiles[i].m_absoluteStoragePath); - map.insert("m_publishedFileID", m_screenPlayFiles[i].m_publishedFileID); - map.insert("m_isNew", m_screenPlayFiles[i].m_isNew); - map.insert("m_lastModified", m_screenPlayFiles[i].m_lastModified); + if (!QDir(localInstalledPath + "/" + folderName).exists()) { + return {}; + } + + for (const auto& item : m_screenPlayFiles) { + if (item.m_folderId == folderName) { + QVariantMap map; + map.insert("m_title", item.m_title); + map.insert("m_preview", item.m_preview); + map.insert("m_previewGIF", item.m_previewGIF); + map.insert("m_file", item.m_file); + map.insert("m_type", QVariant::fromValue(item.m_type)); + map.insert("m_absoluteStoragePath", item.m_absoluteStoragePath); + map.insert("m_publishedFileID", item.m_publishedFileID); + map.insert("m_isNew", item.m_isNew); + map.insert("m_lastModified", item.m_lastModified); return map; } } @@ -227,7 +277,7 @@ QVariantMap InstalledListModel::get(const QString& folderId) const } /*! - \brief . + \brief Removes all entires and loads it again. */ void InstalledListModel::reset() { diff --git a/ScreenPlay/src/installedlistmodel.h b/ScreenPlay/src/installedlistmodel.h index 02fab2f9..c20b3476 100644 --- a/ScreenPlay/src/installedlistmodel.h +++ b/ScreenPlay/src/installedlistmodel.h @@ -99,13 +99,13 @@ public: } public slots: - QVariantMap get(const QString& folderId) const; + QVariantMap get(const QString& folderName) const; void loadInstalledContent(); void append(const QJsonObject&, const QString&, const bool isNew, const QDateTime& lastModified); void reset(); void init(); - bool deinstallItemAt(const int index); + void deinstallItemAt(const QString& absoluteStoragePath); void setCount(int count) { @@ -124,6 +124,7 @@ private: QFileSystemWatcher m_fileSystemWatcher; QVector m_screenPlayFiles; int m_count { 0 }; + QFuture m_loadContentFuture; const std::shared_ptr& m_globalVariables; };