diff --git a/discover/CMakeLists.txt b/discover/CMakeLists.txt index 929614d3..90fb9b19 100644 --- a/discover/CMakeLists.txt +++ b/discover/CMakeLists.txt @@ -1,83 +1,84 @@ add_subdirectory(icons) if(BUILD_TESTING) add_subdirectory(autotests) endif() include_directories(${CMAKE_SOURCE_DIR} ${CMAKE_CURRENT_BINARY_DIR}/..) ecm_qt_declare_logging_category(plasma_discover_SRCS HEADER discover_debug.h IDENTIFIER DISCOVER_LOG CATEGORY_NAME org.kde.plasma.discover) +kconfig_add_kcfg_files(plasma_discover_SRCS discoversettings.kcfgc GENERATE_MOC) add_executable(plasma-discover ${plasma_discover_SRCS} main.cpp DiscoverObject.cpp DiscoverDeclarativePlugin.cpp FeaturedModel.cpp PaginateModel.cpp UnityLauncher.cpp ReadFile.cpp CachedNetworkAccessManager.cpp resources.qrc assets.qrc ) add_executable(Plasma::Discover ALIAS plasma-discover) set_target_properties(plasma-discover PROPERTIES INSTALL_RPATH ${CMAKE_INSTALL_FULL_LIBDIR}/plasma-discover) target_link_libraries(plasma-discover PUBLIC KF5::Crash KF5::DBusAddons KF5::I18n KF5::XmlGui KF5::ItemModels KF5::KIOWidgets KF5::QuickAddons Qt5::Quick Discover::Common ) install(TARGETS plasma-discover ${INSTALL_TARGETS_DEFAULT_ARGS} ) # if (BUILD_DummyBackend) # target_compile_definitions(plasma-discover PRIVATE $<$:QT_QML_DEBUG=1>) # endif() # Standard desktop file accepts local files as input. set(DesktopNoDisplay "false") find_program(DPKG dpkg) find_program(RPM rpm) set(DesktopMimeType "") if(DPKG) set(DesktopMimeType "${DesktopMimeType}application/vnd.debian.binary-package;") endif() if(RPM) set(DesktopMimeType "${DesktopMimeType}application/x-rpm;") endif() set(DesktopExec "plasma-discover %F") configure_file(org.kde.discover.desktop.cmake ${CMAKE_CURRENT_BINARY_DIR}/org.kde.discover.desktop) install(PROGRAMS ${CMAKE_CURRENT_BINARY_DIR}/org.kde.discover.desktop DESTINATION ${XDG_APPS_INSTALL_DIR} ) # Support appstream:// URI set(DesktopNoDisplay "true") set(DesktopMimeType "x-scheme-handler/appstream;") set(DesktopExec "plasma-discover %U") configure_file(org.kde.discover.desktop.cmake ${CMAKE_CURRENT_BINARY_DIR}/org.kde.discover.urlhandler.desktop) install(PROGRAMS ${CMAKE_CURRENT_BINARY_DIR}/org.kde.discover.urlhandler.desktop DESTINATION ${XDG_APPS_INSTALL_DIR} ) # support snap:/ URI set(DesktopNoDisplay "true") set(DesktopMimeType "x-scheme-handler/snap;") set(DesktopExec "plasma-discover %U") configure_file(org.kde.discover.desktop.cmake ${CMAKE_CURRENT_BINARY_DIR}/org.kde.discover.snap.urlhandler.desktop) install(PROGRAMS ${CMAKE_CURRENT_BINARY_DIR}/org.kde.discover.snap.urlhandler.desktop DESTINATION ${XDG_APPS_INSTALL_DIR} ) if(EXISTS "/etc/debian_version") set(DesktopNoDisplay "true") set(DesktopMimeType "x-scheme-handler/apt") set(DesktopExec "plasma-discover %U") configure_file(org.kde.discover.desktop.cmake ${CMAKE_CURRENT_BINARY_DIR}/org.kde.discover.apt.urlhandler.desktop) install(PROGRAMS ${CMAKE_CURRENT_BINARY_DIR}/org.kde.discover.apt.urlhandler.desktop DESTINATION ${XDG_APPS_INSTALL_DIR} ) endif() install(FILES plasmadiscoverui.rc DESTINATION ${KXMLGUI_INSTALL_DIR}/plasmadiscover) install( FILES org.kde.discover.appdata.xml DESTINATION ${KDE_INSTALL_METAINFODIR} ) diff --git a/discover/DiscoverObject.cpp b/discover/DiscoverObject.cpp index cd7bfb87..3036dd17 100644 --- a/discover/DiscoverObject.cpp +++ b/discover/DiscoverObject.cpp @@ -1,478 +1,486 @@ /* * Copyright (C) 2012 Aleix Pol Gonzalez * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU Library/Lesser General Public License * version 2, or (at your option) any later version, as published by the * Free Software Foundation * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details * * You should have received a copy of the GNU Library/Lesser General Public * License along with this program; if not, write to the * Free Software Foundation, Inc., * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "DiscoverObject.h" #include "PaginateModel.h" #include "UnityLauncher.h" #include "FeaturedModel.h" #include "CachedNetworkAccessManager.h" #include "DiscoverDeclarativePlugin.h" #include "DiscoverBackendsFactory.h" // Qt includes #include #include "discover_debug.h" #include #include #include #include #include #include #include #include #include #include #include #include // KDE includes #include #include #include #include #include #include #include #include #include #include #include // #include // DiscoverCommon includes #include #include #include #include #include #include #include #include #include #include #include +#include "discoversettings.h" + class OurSortFilterProxyModel : public QSortFilterProxyModel, public QQmlParserStatus { Q_OBJECT Q_INTERFACES(QQmlParserStatus) public: void classBegin() override {} void componentComplete() override { if (dynamicSortFilter()) sort(0); } }; DiscoverObject::DiscoverObject(CompactMode mode) : QObject() , m_engine(new QQmlApplicationEngine) , m_mode(mode) , m_networkAccessManagerFactory(new CachedNetworkAccessManagerFactory) { setObjectName(QStringLiteral("DiscoverMain")); m_engine->rootContext()->setContextObject(new KLocalizedContext(m_engine)); auto factory = m_engine->networkAccessManagerFactory(); m_engine->setNetworkAccessManagerFactory(nullptr); delete factory; m_engine->setNetworkAccessManagerFactory(m_networkAccessManagerFactory.data()); qmlRegisterType("org.kde.discover.app", 1, 0, "UnityLauncher"); qmlRegisterType("org.kde.discover.app", 1, 0, "PaginateModel"); qmlRegisterType("org.kde.discover.app", 1, 0, "KConcatenateRowsProxyModel"); qmlRegisterType("org.kde.discover.app", 1, 0, "FeaturedModel"); qmlRegisterType("org.kde.discover.app", 1, 0, "QSortFilterProxyModel"); + qmlRegisterSingletonType("org.kde.discover.app", 1, 0, "DiscoverSettings", [](QQmlEngine*, QJSEngine*) -> QObject* { + auto r = new DiscoverSettings; + connect(r, &DiscoverSettings::installedPageSortingChanged, r, &DiscoverSettings::save); + connect(r, &DiscoverSettings::appsListPageSortingChanged, r, &DiscoverSettings::save); + return r; + }); qmlRegisterType(); qmlRegisterType(); qmlRegisterType(); qmlRegisterType(); qmlRegisterType(); qmlRegisterType(); qmlRegisterUncreatableType("org.kde.discover.app", 1, 0, "DiscoverMainWindow", QStringLiteral("don't do that")); setupActions(); auto uri = "org.kde.discover"; DiscoverDeclarativePlugin* plugin = new DiscoverDeclarativePlugin; plugin->setParent(this); plugin->initializeEngine(m_engine, uri); plugin->registerTypes(uri); m_engine->rootContext()->setContextProperty(QStringLiteral("app"), this); m_engine->rootContext()->setContextProperty(QStringLiteral("discoverAboutData"), QVariant::fromValue(KAboutData::applicationData())); connect(m_engine, &QQmlApplicationEngine::objectCreated, this, &DiscoverObject::integrateObject); m_engine->load(QUrl(QStringLiteral("qrc:/qml/DiscoverWindow.qml"))); connect(QCoreApplication::instance(), &QCoreApplication::aboutToQuit, this, [this](){ const auto objs = m_engine->rootObjects(); for(auto o: objs) delete o; }); auto action = new OneTimeAction( [this]() { bool found = DiscoverBackendsFactory::hasRequestedBackends(); for (auto b : ResourcesModel::global()->backends()) found |= b->hasApplications(); if (!found) Q_EMIT openErrorPage(i18n("No application back-ends found, please report to your distribution.")); } , this); if (ResourcesModel::global()->backends().isEmpty()) { connect(ResourcesModel::global(), &ResourcesModel::allInitialized, action, &OneTimeAction::trigger); } else { action->trigger(); } } DiscoverObject::~DiscoverObject() { delete m_engine; } bool DiscoverObject::isRoot() { return ::getuid() == 0; } QStringList DiscoverObject::modes() const { QStringList ret; QObject* obj = rootObject(); for(int i = obj->metaObject()->propertyOffset(); imetaObject()->propertyCount(); i++) { QMetaProperty p = obj->metaObject()->property(i); QByteArray compName = p.name(); if(compName.startsWith("top") && compName.endsWith("Comp")) { ret += QString::fromLatin1(compName.mid(3, compName.length()-7)); } } return ret; } void DiscoverObject::openMode(const QString& _mode) { QObject* obj = rootObject(); if (!obj) { qCWarning(DISCOVER_LOG) << "could not get the main object"; return; } if(!modes().contains(_mode, Qt::CaseInsensitive)) qCWarning(DISCOVER_LOG) << "unknown mode" << _mode << modes(); QString mode = _mode; mode[0] = mode[0].toUpper(); const QByteArray propertyName = "top"+mode.toLatin1()+"Comp"; const QVariant modeComp = obj->property(propertyName.constData()); if (!modeComp.isValid()) qCWarning(DISCOVER_LOG) << "couldn't open mode" << _mode; else obj->setProperty("currentTopLevel", modeComp); } void DiscoverObject::openMimeType(const QString& mime) { emit listMimeInternal(mime); } void DiscoverObject::openCategory(const QString& category) { setRootObjectProperty("defaultStartup", false); auto action = new OneTimeAction( [this, category]() { Category* cat = CategoryModel::global()->findCategoryByName(category); if (cat) { emit listCategoryInternal(cat); } else { showPassiveNotification(i18n("Could not find category '%1'", category)); setRootObjectProperty("defaultStartup", false); } } , this); if (ResourcesModel::global()->backends().isEmpty()) { connect(ResourcesModel::global(), &ResourcesModel::backendsChanged, action, &OneTimeAction::trigger); } else { action->trigger(); } } void DiscoverObject::openLocalPackage(const QUrl& localfile) { if (!QFile::exists(localfile.toLocalFile())) { // showPassiveNotification(i18n("Trying to open unexisting file '%1'", localfile.toString())); qCWarning(DISCOVER_LOG) << "Trying to open unexisting file" << localfile; return; } setRootObjectProperty("defaultStartup", false); auto action = new OneTimeAction( [this, localfile]() { auto res = ResourcesModel::global()->resourceForFile(localfile); qCDebug(DISCOVER_LOG) << "all initialized..." << res; if (res) { emit openApplicationInternal(res); } else { QMimeDatabase db; auto mime = db.mimeTypeForUrl(localfile); auto fIsFlatpakBackend = [](AbstractResourcesBackend* backend) { return backend->metaObject()->className() == QByteArray("FlatpakBackend"); }; if (mime.name().startsWith(QLatin1String("application/vnd.flatpak")) && !kContains(ResourcesModel::global()->backends(), fIsFlatpakBackend)) { openApplication(QUrl(QLatin1String("appstream://org.kde.discover.flatpak"))); showPassiveNotification(i18n("Cannot interact with flatpak resources without the flatpak backend %1. Please install it first.", localfile.toDisplayString())); } else { setRootObjectProperty("defaultStartup", true); showPassiveNotification(i18n("Couldn't open %1", localfile.toDisplayString())); } } } , this); if (ResourcesModel::global()->backends().isEmpty()) { connect(ResourcesModel::global(), &ResourcesModel::backendsChanged, action, &OneTimeAction::trigger); } else { action->trigger(); } } void DiscoverObject::openApplication(const QUrl& url) { Q_ASSERT(!url.isEmpty()); setRootObjectProperty("defaultStartup", false); auto action = new OneTimeAction( [this, url]() { AbstractResourcesBackend::Filters f; f.resourceUrl = url; auto stream = new StoredResultsStream({ResourcesModel::global()->search(f)}); connect(stream, &StoredResultsStream::finished, this, [this, url, stream]() { const auto res = stream->resources(); if (res.count() >= 1) { emit openApplicationInternal(res.first()); } else { setRootObjectProperty("defaultStartup", true); Q_EMIT openErrorPage(i18n("Couldn't open %1", url.toDisplayString())); } }); } , this); if (ResourcesModel::global()->backends().isEmpty()) { connect(ResourcesModel::global(), &ResourcesModel::backendsChanged, action, &OneTimeAction::trigger); } else { action->trigger(); } } void DiscoverObject::integrateObject(QObject* object) { if (!object) { qCWarning(DISCOVER_LOG) << "Errors when loading the GUI"; QTimer::singleShot(0, QCoreApplication::instance(), [](){ QCoreApplication::instance()->exit(1); }); return; } Q_ASSERT(object == rootObject()); KConfigGroup window(KSharedConfig::openConfig(), "Window"); if (window.hasKey("geometry")) rootObject()->setGeometry(window.readEntry("geometry", QRect())); if (window.hasKey("visibility")) { QWindow::Visibility visibility(QWindow::Visibility(window.readEntry("visibility", QWindow::Windowed))); rootObject()->setVisibility(qMax(visibility, QQuickView::AutomaticVisibility)); } object->installEventFilter(this); connect(object, &QObject::destroyed, qGuiApp, &QCoreApplication::quit); object->setParent(m_engine); connect(qGuiApp, &QGuiApplication::commitDataRequest, this, [this](QSessionManager &sessionManager) { if (ResourcesModel::global()->isBusy()) { Q_EMIT preventedClose(); sessionManager.cancel(); } }); } bool DiscoverObject::eventFilter(QObject * object, QEvent * event) { if (object!=rootObject()) return false; if (event->type() == QEvent::Close) { if (ResourcesModel::global()->isBusy()) { qCWarning(DISCOVER_LOG) << "not closing because there's still pending tasks"; Q_EMIT preventedClose(); return true; } KConfigGroup window(KSharedConfig::openConfig(), "Window"); window.writeEntry("geometry", rootObject()->geometry()); window.writeEntry("visibility", rootObject()->visibility()); // } else if (event->type() == QEvent::ShortcutOverride) { // qCWarning(DISCOVER_LOG) << "Action conflict" << event; } return false; } void DiscoverObject::setupActions() { if (KAuthorized::authorizeAction(QStringLiteral("help_report_bug")) && !KAboutData::applicationData().bugAddress().isEmpty()) { auto mReportBugAction = KStandardAction::reportBug(this, &DiscoverObject::reportBug, this); m_collection[mReportBugAction->objectName()] = mReportBugAction; } if (KAuthorized::authorizeAction(QStringLiteral("help_about_app"))) { auto mAboutAppAction = KStandardAction::aboutApp(this, &DiscoverObject::aboutApplication, this); m_collection[mAboutAppAction->objectName()] = mAboutAppAction; } } QAction * DiscoverObject::action(const QString& name) const { return m_collection.value(name); } QString DiscoverObject::iconName(const QIcon& icon) { return icon.name(); } void DiscoverObject::aboutApplication() { static QPointer dialog; if (!dialog) { dialog = new KAboutApplicationDialog(KAboutData::applicationData(), nullptr); dialog->setAttribute(Qt::WA_DeleteOnClose); } dialog->show(); } void DiscoverObject::reportBug() { static QPointer dialog; if (!dialog) { dialog = new KBugReport(KAboutData::applicationData(), nullptr); dialog->setAttribute(Qt::WA_DeleteOnClose); } dialog->show(); } void DiscoverObject::switchApplicationLanguage() { // auto langDialog = new KSwitchLanguageDialog(nullptr); // connect(langDialog, SIGNAL(finished(int)), this, SLOT(dialogFinished())); // langDialog->show(); } void DiscoverObject::setCompactMode(DiscoverObject::CompactMode mode) { if (m_mode != mode) { m_mode = mode; Q_EMIT compactModeChanged(m_mode); } } class DiscoverTestExecutor : public QObject { public: DiscoverTestExecutor(QObject* rootObject, QQmlEngine* engine, const QUrl &url) : QObject(engine) { connect(engine, &QQmlEngine::quit, this, &DiscoverTestExecutor::finish, Qt::QueuedConnection); QQmlComponent* component = new QQmlComponent(engine, url, engine); m_testObject = component->create(engine->rootContext()); if (!m_testObject) { qCWarning(DISCOVER_LOG) << "error loading test" << url << m_testObject << component->errors(); Q_ASSERT(false); } m_testObject->setProperty("appRoot", QVariant::fromValue(rootObject)); connect(engine, &QQmlEngine::warnings, this, &DiscoverTestExecutor::processWarnings); } void processWarnings(const QList &warnings) { foreach(const QQmlError &warning, warnings) { if (warning.url().path().endsWith(QLatin1String("DiscoverTest.qml"))) { qCWarning(DISCOVER_LOG) << "Test failed!" << warnings; qGuiApp->exit(1); } } m_warnings << warnings; } void finish() { //The CI doesn't seem to have icons, remove when it's not an issue anymore m_warnings.erase(std::remove_if(m_warnings.begin(), m_warnings.end(), [](const QQmlError& err) -> bool { return err.description().contains(QLatin1String("QML Image: Failed to get image from provider: image://icon/")); })); if (m_warnings.isEmpty()) qCDebug(DISCOVER_LOG) << "cool no warnings!"; else qCDebug(DISCOVER_LOG) << "test finished successfully despite" << m_warnings; qGuiApp->exit(m_warnings.count()); } private: QObject* m_testObject; QList m_warnings; }; void DiscoverObject::loadTest(const QUrl& url) { new DiscoverTestExecutor(rootObject(), engine(), url); } QWindow* DiscoverObject::rootObject() const { return qobject_cast(m_engine->rootObjects().at(0)); } void DiscoverObject::setRootObjectProperty(const char* name, const QVariant& value) { auto ro = rootObject(); if (!ro) { qCWarning(DISCOVER_LOG) << "please check your installation"; return; } rootObject()->setProperty(name, value); } void DiscoverObject::showPassiveNotification(const QString& msg) { QTimer::singleShot(100, this, [this, msg](){ QMetaObject::invokeMethod(rootObject(), "showPassiveNotification", Qt::QueuedConnection, Q_ARG(QVariant, msg), Q_ARG(QVariant, {}), Q_ARG(QVariant, {}), Q_ARG(QVariant, {})); }); } void DiscoverObject::copyTextToClipboard(const QString& text) { qGuiApp->clipboard()->setText(text); } #include "DiscoverObject.moc" diff --git a/discover/discoversettings.kcfg b/discover/discoversettings.kcfg new file mode 100644 index 00000000..668a3cfe --- /dev/null +++ b/discover/discoversettings.kcfg @@ -0,0 +1,8 @@ + + + + + ResourcesProxyModel::SortableRatingRole + ResourcesProxyModel::NameRole + + diff --git a/discover/discoversettings.kcfgc b/discover/discoversettings.kcfgc new file mode 100644 index 00000000..9afaaf7f --- /dev/null +++ b/discover/discoversettings.kcfgc @@ -0,0 +1,5 @@ +File=discoversettings.kcfg +ClassName=DiscoverSettings +GenerateProperties=true +Mutators=true +IncludeFiles=resources/ResourcesProxyModel.h diff --git a/discover/qml/ApplicationsListPage.qml b/discover/qml/ApplicationsListPage.qml index 2feb6eb8..0cde29ce 100644 --- a/discover/qml/ApplicationsListPage.qml +++ b/discover/qml/ApplicationsListPage.qml @@ -1,217 +1,222 @@ /* * Copyright (C) 2012 Aleix Pol Gonzalez * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU Library/Lesser General Public License * version 2, or (at your option) any later version, as published by the * Free Software Foundation * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details * * You should have received a copy of the GNU Library/Lesser General Public * License along with this program; if not, write to the * Free Software Foundation, Inc., * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ import QtQuick 2.5 import QtQuick.Controls 2.3 import QtQuick.Layouts 1.1 import QtQuick.Window 2.2 import "navigation.js" as Navigation import org.kde.discover.app 1.0 import org.kde.discover 2.0 import org.kde.kirigami 2.4 as Kirigami DiscoverPage { id: page readonly property var model: appsModel property alias category: appsModel.filteredCategory property alias sortRole: appsModel.sortRole property alias sortOrder: appsModel.sortOrder property alias originFilter: appsModel.originFilter property alias mimeTypeFilter: appsModel.mimeTypeFilter property alias stateFilter: appsModel.stateFilter property alias extending: appsModel.extending property alias search: appsModel.search property alias resourcesUrl: appsModel.resourcesUrl property alias isBusy: appsModel.isBusy property alias allBackends: appsModel.allBackends property alias count: apps.count property alias listHeader: apps.header property alias listHeaderPositioning: apps.headerPositioning + property alias sortProperty: saveChanges.property property bool compact: page.width < 550 || !applicationWindow().wideScreen property bool showRating: true property bool canNavigate: true readonly property alias subcategories: appsModel.subcategories function escapeHtml(unsafe) { return unsafe .replace(/&/g, "&") .replace(//g, ">") .replace(/"/g, """) .replace(/'/g, "'"); } title: search.length>0 ? i18n("Search: %1", escapeHtml(search)) : category ? category.name : "" signal clearSearch() supportsRefreshing: true onRefreshingChanged: if (refreshing) { appsModel.invalidateFilter() refreshing = false } ActionGroup { id: sortGroup exclusive: true } + Binding { + id: saveChanges + target: DiscoverSettings + property: "appsListPageSorting" + value: appsModel.sortRole + } + contextualActions: [ Kirigami.Action { visible: !appsModel.sortByRelevancy text: i18n("Sort: %1", sortGroup.checkedAction.text) Action { ActionGroup.group: sortGroup text: i18n("Name") onTriggered: { appsModel.sortRole = ResourcesProxyModel.NameRole - appsModel.sortOrder = Qt.AscendingOrder } checkable: true checked: appsModel.sortRole == ResourcesProxyModel.NameRole } Action { ActionGroup.group: sortGroup text: i18n("Rating") onTriggered: { appsModel.sortRole = ResourcesProxyModel.SortableRatingRole - appsModel.sortOrder = Qt.DescendingOrder } checkable: true checked: appsModel.sortRole == ResourcesProxyModel.SortableRatingRole } Action { ActionGroup.group: sortGroup text: i18n("Size") onTriggered: { appsModel.sortRole = ResourcesProxyModel.SizeRole - appsModel.sortOrder = Qt.AscendingOrder } checkable: true checked: appsModel.sortRole == ResourcesProxyModel.SizeRole } Action { ActionGroup.group: sortGroup text: i18n("Release Date") onTriggered: { appsModel.sortRole = ResourcesProxyModel.ReleaseDateRole - appsModel.sortOrder = Qt.DescendingOrder } checkable: true checked: appsModel.sortRole == ResourcesProxyModel.ReleaseDateRole } } ] Kirigami.CardsListView { id: apps section.delegate: Label { text: section anchors { right: parent.right } } model: ResourcesProxyModel { id: appsModel - sortRole: ResourcesProxyModel.SortableRatingRole - sortOrder: Qt.DescendingOrder + sortRole: DiscoverSettings.appsListPageSorting + sortOrder: sortRole === ResourcesProxyModel.SortableRatingRole || sortRole === ResourcesProxyModel.ReleaseDateRole ? Qt.DescendingOrder : Qt.AscendingOrder + onBusyChanged: if (isBusy) { apps.currentIndex = -1 } } currentIndex: -1 delegate: ApplicationDelegate { application: model.application compact: page.compact showRating: page.showRating } Label { anchors.centerIn: parent opacity: apps.count == 0 && !appsModel.isBusy ? 0.3 : 0 Behavior on opacity { PropertyAnimation { duration: Kirigami.Units.longDuration; easing.type: Easing.InOutQuad; } } text: i18n("Sorry, nothing found...") } footer: BusyIndicator { id: busyIndicator anchors { horizontalCenter: parent.horizontalCenter } running: false opacity: 0 states: [ State { name: "running"; when: appsModel.isBusy PropertyChanges { target: busyIndicator; opacity: 1; running: true; } } ] transitions: [ Transition { from: "" to: "running" SequentialAnimation { PauseAnimation { duration: Kirigami.Units.longDuration * 5; } ParallelAnimation { AnchorAnimation { duration: Kirigami.Units.longDuration; easing.type: Easing.InOutQuad; } PropertyAnimation { property: "opacity"; duration: Kirigami.Units.longDuration; easing.type: Easing.InOutQuad; } } } }, Transition { from: "running" to: "" ParallelAnimation { AnchorAnimation { duration: Kirigami.Units.shortDuration; easing.type: Easing.InOutQuad; } PropertyAnimation { property: "opacity"; duration: Kirigami.Units.shortDuration; easing.type: Easing.InOutQuad; } } } ] Label { id: busyLabel anchors { horizontalCenter: parent.horizontalCenter bottom: parent.top } text: i18n("Still looking...") opacity: 0 states: [ State { name: "running"; when: busyIndicator.opacity === 1; PropertyChanges { target: busyLabel; opacity: 1; } } ] transitions: Transition { from: "" to: "running" SequentialAnimation { PauseAnimation { duration: Kirigami.Units.longDuration * 5; } PropertyAnimation { property: "opacity"; duration: Kirigami.Units.longDuration * 10; easing.type: Easing.InOutCubic; } } } } } } } diff --git a/discover/qml/InstalledPage.qml b/discover/qml/InstalledPage.qml index ddb179f6..4c8f8bb8 100644 --- a/discover/qml/InstalledPage.qml +++ b/discover/qml/InstalledPage.qml @@ -1,21 +1,21 @@ import QtQuick 2.1 import QtQuick.Controls 2.1 import QtQuick.Layouts 1.1 import org.kde.discover 2.0 import org.kde.discover.app 1.0 import org.kde.kirigami 2.1 as Kirigami ApplicationsListPage { id: page stateFilter: AbstractResource.Installed - sortRole: ResourcesProxyModel.NameRole sortOrder: Qt.AscendingOrder allBackends: true + sortProperty: "installedPageSorting" title: i18n("Installed") compact: true showRating: false canNavigate: false listHeader: null }