diff --git a/discover/qml/ApplicationPage.qml b/discover/qml/ApplicationPage.qml index 1bc73f99..3eba05fb 100644 --- a/discover/qml/ApplicationPage.qml +++ b/discover/qml/ApplicationPage.qml @@ -1,473 +1,465 @@ /* * 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.Window 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.6 as Kirigami import "navigation.js" as Navigation DiscoverPage { id: appInfo property QtObject application: null readonly property int visibleReviews: 3 clip: true // Usually this page is not the top level page, but when we are, isHome being // true will ensure that the search field suggests we are searching in the list // of available apps, not inside the app page itself. This will happen when // Discover is launched e.g. from krunner or otherwise requested to show a // specific application on launch. readonly property bool isHome: true function searchFor(text) { if (text.length === 0) return; Navigation.openCategory(null, "") } background: Rectangle { color: Kirigami.Theme.backgroundColor Kirigami.Theme.colorSet: Kirigami.Theme.View Kirigami.Theme.inherit: false } ReviewsPage { id: reviewsSheet model: ReviewsModel { id: reviewsModel resource: appInfo.application } } contextualActions: [originsMenuAction] ActionGroup { id: sourcesGroup exclusive: true } Kirigami.Action { id: originsMenuAction text: i18n("Sources") visible: children.length>1 readonly property var r0: Instantiator { model: ResourcesProxyModel { id: alternativeResourcesModel allBackends: true resourcesUrl: appInfo.application.url } delegate: Action { ActionGroup.group: sourcesGroup text: displayOrigin icon.name: sourceIcon checked: appInfo.application == model.application onTriggered: if(index>=0) { var res = model.application console.assert(res) window.stack.pop() Navigation.openApplication(res) } } onObjectAdded: originsMenuAction.children.push(object) } } Kirigami.Action { id: invokeAction visible: application.isInstalled && application.canExecute && !appbutton.isActive text: application.executeLabel icon.name: "media-playback-start" onTriggered: application.invokeApplication() } actions { main: appbutton.action right: appbutton.isActive ? appbutton.cancelAction : invokeAction } InstallApplicationButton { id: appbutton Layout.rightMargin: Kirigami.Units.smallSpacing application: appInfo.application visible: false } leftPadding: Kirigami.Units.largeSpacing * (applicationWindow().wideScreen ? 2 : 1) rightPadding: Kirigami.Units.largeSpacing * (applicationWindow().wideScreen ? 2 : 1) // Icon, name, caption, screenshots, description and reviews ColumnLayout { spacing: 0 RowLayout { Kirigami.Icon { Layout.preferredHeight: 80 Layout.preferredWidth: 80 source: appInfo.application.icon Layout.rightMargin: Kirigami.Units.smallSpacing * 2 } ColumnLayout { spacing: 0 Kirigami.Heading { level: 1 text: appInfo.application.name lineHeight: 1.0 maximumLineCount: 1 elide: Text.ElideRight Layout.fillWidth: true Layout.alignment: Text.AlignBottom } RowLayout { spacing: Kirigami.Units.largeSpacing Rating { rating: appInfo.application.rating ? appInfo.application.rating.sortableRating : 0 starSize: summary.font.pointSize } Label { text: appInfo.application.rating ? i18np("%1 rating", "%1 ratings", appInfo.application.rating.ratingCount) : i18n("No ratings yet") opacity: 0.5 } } Kirigami.Heading { id: summary level: 4 text: appInfo.application.comment maximumLineCount: 2 lineHeight: lineCount > 1 ? 0.75 : 1.2 elide: Text.ElideRight Layout.fillWidth: true Layout.alignment: Qt.AlignTop } } Layout.bottomMargin: Kirigami.Units.largeSpacing } ApplicationScreenshots { Layout.fillWidth: true visible: count > 0 resource: appInfo.application ScrollBar.horizontal: screenshotsScrollbar } ScrollBar { id: screenshotsScrollbar Layout.fillWidth: true } - Label { + LinkLabel { Layout.topMargin: Kirigami.Units.largeSpacing Layout.fillWidth: true wrapMode: Text.WordWrap text: appInfo.application.longDescription onLinkActivated: Qt.openUrlExternally(link); - // Since Text (and Label) lack cursor-changing abilities of their own, - // as suggested by QTBUG-30804, use a MouseAra to do our dirty work. - // See comment https://bugreports.qt.io/browse/QTBUG-30804?#comment-206287 - MouseArea { - anchors.fill: parent - cursorShape: parent.hoveredLink ? Qt.PointingHandCursor : Qt.ArrowCursor - acceptedButtons: Qt.NoButton // Not actually accepting clicks, just changing the cursor - } } Kirigami.Heading { Layout.topMargin: Kirigami.Units.largeSpacing text: i18n("What's New") level: 2 visible: changelogLabel.text.length > 0 } Rectangle { color: Kirigami.Theme.linkColor Layout.fillWidth: true height: 1 visible: changelogLabel.text.length > 0 } Label { id: changelogLabel Layout.topMargin: Kirigami.Units.largeSpacing Layout.fillWidth: true wrapMode: Text.WordWrap Component.onCompleted: appInfo.application.fetchChangelog() Connections { target: appInfo.application onChangelogFetched: { changelogLabel.text = changelog } } } Kirigami.LinkButton { id: addonsButton text: i18n("Addons") visible: addonsView.containsAddons onClicked: addonsView.sheetOpen = true } RowLayout { Layout.topMargin: Kirigami.Units.largeSpacing Layout.fillWidth: true Kirigami.Heading { Layout.fillWidth: true text: i18n("Reviews") Layout.alignment: Qt.AlignLeft | Qt.AlignBottom level: 2 visible: rep.count > 0 } Kirigami.LinkButton { visible: reviewsModel.count > visibleReviews text: i18np("Show %1 review...", "Show all %1 reviews...", reviewsModel.count) Layout.alignment: Qt.AlignRight | Qt.AlignBottom onClicked: { reviewsSheet.open() } } } Rectangle { color: Kirigami.Theme.linkColor Layout.fillWidth: true height: 1 visible: rep.count > 0 } Repeater { id: rep model: PaginateModel { sourceModel: reviewsSheet.model pageSize: visibleReviews } delegate: ReviewDelegate { Layout.topMargin: Kirigami.Units.largeSpacing separator: false compact: true Layout.bottomMargin: Kirigami.Units.largeSpacing } } Kirigami.LinkButton { function writeReviewText() { if (appInfo.application.isInstalled) { if (reviewsModel.count > 0) { return i18n("Write a review!") } else { return i18n("Be the first to write a review!") } // App not installed } else { if (reviewsModel.count > 0) { return i18n("Install this app to write a review!") } else { return i18n("Install this app and be the first to write a review!") } } } text: writeReviewText() Layout.alignment: Qt.AlignCenter onClicked: reviewsSheet.openReviewDialog() enabled: appInfo.application.isInstalled visible: reviewsModel.backend && reviewsModel.backend.isResourceSupported(appInfo.application) Layout.topMargin: Kirigami.Units.largeSpacing Layout.bottomMargin: Kirigami.Units.largeSpacing } Repeater { model: application.objects delegate: Loader { property QtObject resource: appInfo.application source: modelData } } Item { height: addonsButton.height width: 1 } // Details/metadata Rectangle { color: Kirigami.Theme.linkColor Layout.fillWidth: true height: 1 Layout.bottomMargin: Kirigami.Units.largeSpacing } GridLayout { rowSpacing: 0 columns: 2 // Category row Label { visible: categoryLabel.visible Layout.alignment: Qt.AlignRight text: i18n("Category:") } Label { id: categoryLabel visible: text.length > 0 Layout.fillWidth: true elide: Text.ElideRight text: appInfo.application.categoryDisplay } // Version row Label { visible: versionLabel.visible Layout.alignment: Qt.AlignRight text: i18n("Version:") } Label { readonly property string version: appInfo.application.isInstalled ? appInfo.application.installedVersion : appInfo.application.availableVersion readonly property string releaseDate: appInfo.application.releaseDate.toLocaleString() function versionString() { if (version.length == 0) { return "" } else { if (releaseDate.length > 0) { return i18n("%1, released on %2", version, releaseDate) } else { return version } } } id: versionLabel visible: text.length > 0 Layout.fillWidth: true elide: Text.ElideRight text: versionString() } // Author row Label { Layout.alignment: Qt.AlignRight text: i18n("Author:") visible: authorLabel.visible } Label { id: authorLabel Layout.fillWidth: true elide: Text.ElideRight visible: text.length>0 text: appInfo.application.author } // Size row Label { Layout.alignment: Qt.AlignRight text: i18n("Size:") } Label { Layout.fillWidth: true elide: Text.ElideRight text: appInfo.application.sizeDescription } // Source row Label { Layout.alignment: Qt.AlignRight text: i18n("Source:") } Label { Layout.fillWidth: true horizontalAlignment: Text.AlignLeft text: appInfo.application.displayOrigin elide: Text.ElideRight } // License row Label { Layout.alignment: Qt.AlignRight text: i18n("License:") visible: appInfo.application.license.length>0 } Kirigami.UrlButton { Layout.fillWidth: true horizontalAlignment: Text.AlignLeft // tooltip: i18n("See full license terms") text: appInfo.application.license url: "https://spdx.org/licenses/" + appInfo.application.license + ".html#licenseText" } // Homepage row Label { visible: homepageLink.visible Layout.alignment: Qt.AlignRight text: i18n("Homepage:") } Kirigami.UrlButton { id: homepageLink url: application.homepage Layout.fillWidth: true horizontalAlignment: Text.AlignLeft } // "User Guide" row Label { visible: docsLink.visible Layout.alignment: Qt.AlignRight text: i18n("User Guide:") } Kirigami.UrlButton { id: docsLink url: application.helpURL Layout.fillWidth: true horizontalAlignment: Text.AlignLeft } // Donate row Label { visible: donationLink.visible Layout.alignment: Qt.AlignRight text: i18n("Donate:") } Kirigami.UrlButton { id: donationLink url: application.donationURL Layout.fillWidth: true horizontalAlignment: Text.AlignLeft } // "Report a Problem" row Label { visible: bugLink.visible Layout.alignment: Qt.AlignRight text: i18n("Report a Problem:") } Kirigami.UrlButton { id: bugLink url: application.bugURL Layout.fillWidth: true horizontalAlignment: Text.AlignLeft } } } readonly property var addons: AddonsView { id: addonsView application: appInfo.application parent: overlay } } diff --git a/discover/qml/LinkLabel.qml b/discover/qml/LinkLabel.qml new file mode 100644 index 00000000..4b91aa17 --- /dev/null +++ b/discover/qml/LinkLabel.qml @@ -0,0 +1,36 @@ +/* + * Copyright (C) 2019 Dan Leinir Turthra Jensen + * + * 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 + +/** + * A label which adds showing a hand cursor if there is a link being hovered in + * the text set on the label. + */ +Label { + // Since Text (and Label) lack cursor-changing abilities of their own, + // as suggested by QTBUG-30804, use a MouseAra to do our dirty work. + // See comment https://bugreports.qt.io/browse/QTBUG-30804?#comment-206287 + MouseArea { + anchors.fill: parent + cursorShape: parent.hoveredLink ? Qt.PointingHandCursor : Qt.ArrowCursor + acceptedButtons: Qt.NoButton // Not actually accepting clicks, just changing the cursor + } +} diff --git a/discover/qml/ReviewDelegate.qml b/discover/qml/ReviewDelegate.qml index e57893f8..b886eb7c 100644 --- a/discover/qml/ReviewDelegate.qml +++ b/discover/qml/ReviewDelegate.qml @@ -1,89 +1,89 @@ /* * 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.1 import QtQuick.Layouts 1.1 import QtQuick.Controls 2.1 import org.kde.discover 2.0 import org.kde.kirigami 2.0 as Kirigami ColumnLayout { id: item visible: model.shouldShow property bool compact: false property bool separator: true signal markUseful(bool useful) function usefulnessToString(favorable, total) { return total==0 ? i18n("Tell us about this review!") : i18n("%1 out of %2 people found this review useful", favorable, total) } RowLayout { Layout.fillWidth: true Label { id: content Layout.fillWidth: true elide: Text.ElideRight readonly property string author: reviewer ? reviewer : i18n("unknown reviewer") text: summary ? i18n("%1 by %2", summary, author) : i18n("Comment by %1", author) } Rating { id: rating rating: model.rating starSize: content.font.pointSize } } Label { Layout.fillWidth: true text: display maximumLineCount: item.compact ? 3 : undefined wrapMode: Text.Wrap } Label { visible: !item.compact text: usefulnessToString(usefulnessFavorable, usefulnessTotal) } - Label { + LinkLabel { visible: !item.compact Layout.alignment: Qt.AlignRight text: { switch(usefulChoice) { case ReviewsModel.Yes: i18n("Useful? Yes/No") break; case ReviewsModel.No: i18n("Useful? Yes/No") break; default: i18n("Useful? Yes/No") break; } } onLinkActivated: item.markUseful(link=='true') } Kirigami.Separator { visible: item.separator Layout.fillWidth: true } } diff --git a/discover/qml/UpdatesPage.qml b/discover/qml/UpdatesPage.qml index daba5a60..792cf2c4 100644 --- a/discover/qml/UpdatesPage.qml +++ b/discover/qml/UpdatesPage.qml @@ -1,345 +1,345 @@ import QtQuick.Controls 2.3 import QtQuick.Layouts 1.1 import QtQuick 2.4 import org.kde.discover 2.0 import org.kde.discover.app 1.0 import "navigation.js" as Navigation import org.kde.kirigami 2.3 as Kirigami DiscoverPage { id: page title: i18n("Updates") property string footerLabel: "" ResourcesUpdatesModel { id: resourcesUpdatesModel onPassiveMessage: window.showPassiveNotification(message) onIsProgressingChanged: { if (!isProgressing) { resourcesUpdatesModel.prepare() } } Component.onCompleted: { if (!isProgressing) { resourcesUpdatesModel.prepare() } } } UpdateModel { id: updateModel backend: resourcesUpdatesModel } Kirigami.Action { id: updateAction text: page.unselected>0 ? i18n("Update Selected") : i18n("Update All") visible: updateModel.toUpdateCount iconName: "update-none" enabled: !resourcesUpdatesModel.isProgressing && !ResourcesModel.isFetching onTriggered: resourcesUpdatesModel.updateAll() } footer: ScrollView { id: scv width: parent.width height: visible ? Kirigami.Units.gridUnit * 10 : 0 visible: log.contents.length > 0 TextArea { readOnly: true text: log.contents cursorPosition: text.length - 1 font.family: "monospace" ReadFile { id: log filter: ".*ALPM-SCRIPTLET\\] .*" path: "/var/log/pacman.log" } } } Kirigami.Action { id: cancelUpdateAction iconName: "dialog-cancel" text: i18n("Cancel") enabled: resourcesUpdatesModel.transaction && resourcesUpdatesModel.transaction.isCancellable onTriggered: resourcesUpdatesModel.transaction.cancel() } readonly property int unselected: (updateModel.totalUpdatesCount - updateModel.toUpdateCount) readonly property QtObject currentAction: resourcesUpdatesModel.isProgressing ? cancelUpdateAction : updateAction actions { left: refreshAction main: currentAction } header: ToolBar { Kirigami.Theme.colorSet: Kirigami.Theme.Button Kirigami.Theme.inherit: false visible: (updateModel.totalUpdatesCount > 0 && resourcesUpdatesModel.isProgressing) || updateModel.hasUpdates RowLayout { anchors.fill: parent enabled: page.currentAction.enabled CheckBox { Layout.leftMargin: Kirigami.Units.gridUnit + Kirigami.Units.largeSpacing enabled: !resourcesUpdatesModel.isProgressing && !ResourcesModel.isFetching tristate: true checkState: updateModel.toUpdateCount === 0 ? Qt.Unchecked : updateModel.toUpdateCount === updateModel.totalUpdatesCount ? Qt.Checked : Qt.PartiallyChecked onClicked: { if (updateModel.toUpdateCount === 0) updateModel.checkAll() else updateModel.uncheckAll() } } LabelBackground { text: updateModel.toUpdateCount + " (" + updateModel.updateSize+")" } Label { text: i18n("updates selected") } LabelBackground { id: unselectedItem text: page.unselected visible: page.unselected>0 } Label { text: i18n("updates not selected") visible: unselectedItem.visible } Item { Layout.fillWidth: true } } } supportsRefreshing: true onRefreshingChanged: { showPassiveNotification("Fetching updates...") ResourcesModel.updateAction.triggered() refreshing = false } ListView { id: updatesView currentIndex: -1 displaced: Transition { YAnimator { duration: Kirigami.Units.longDuration easing.type: Easing.InOutQuad } } footer: ColumnLayout { anchors.right: parent.right anchors.left: parent.left Kirigami.Heading { Layout.fillWidth: true Layout.alignment: Qt.AlignHCenter horizontalAlignment: Text.AlignHCenter visible: page.footerLabel !== "" text: page.footerLabel } Kirigami.Icon { Layout.alignment: Qt.AlignHCenter visible: page.footerLabel !== "" source: "update-none" opacity: 0.3 width: Kirigami.Units.gridUnit * 12 height: width } Item { visible: page.footerLabel === "" height: Kirigami.Units.gridUnit width: 1 } } model: QSortFilterProxyModel { sourceModel: updateModel sortRole: UpdateModel.SectionResourceProgressRole } section { property: "section" delegate: Kirigami.Heading { x: Kirigami.Units.gridUnit level: 2 text: section height: implicitHeight + Kirigami.Units.largeSpacing * 2 } } delegate: Kirigami.AbstractListItem { backgroundColor: Kirigami.Theme.backgroundColor highlighted: ListView.isCurrentItem onEnabledChanged: if (!enabled) { layout.extended = false; } Keys.onReturnPressed: { itemChecked.clicked() } Keys.onPressed: if (event.key===Qt.Key_Alt) layout.extended = true Keys.onReleased: if (event.key===Qt.Key_Alt) layout.extended = false ColumnLayout { id: layout property bool extended: false onExtendedChanged: if (extended) { updateModel.fetchUpdateDetails(index) } RowLayout { Layout.fillWidth: true Layout.fillHeight: true CheckBox { id: itemChecked Layout.leftMargin: Kirigami.Units.gridUnit Layout.alignment: Qt.AlignVCenter checked: model.checked == Qt.Checked onClicked: model.checked = (model.checked==Qt.Checked ? Qt.Unchecked : Qt.Checked) enabled: !resourcesUpdatesModel.isProgressing } Kirigami.Icon { width: Kirigami.Units.gridUnit * 2 Layout.preferredHeight: width source: decoration smooth: true } ColumnLayout { // App name Kirigami.Heading { Layout.fillWidth: true text: i18n("%1", display) level: 3 elide: Text.ElideRight } // Old and new version numbers Label { Layout.fillWidth: true elide: Text.ElideRight text: i18n("%1 → %2", installedVersion, availableVersion) } } LabelBackground { Layout.minimumWidth: Kirigami.Units.gridUnit * 6 text: size progress: resourceProgress/100 } } Frame { Layout.fillWidth: true implicitHeight: view.contentHeight visible: layout.extended && changelog.length>0 - Label { + LinkLabel { id: view anchors { right: parent.right left: parent.left } text: changelog textFormat: Text.StyledText wrapMode: Text.WordWrap onLinkActivated: Qt.openUrlExternally(link) } //This saves a binding loop on implictHeight, as the Label //height is updated twice (first time with the wrong value) Behavior on implicitHeight { PropertyAnimation { duration: Kirigami.Units.shortDuration } } } Button { Layout.alignment: Qt.AlignRight text: i18n("More Information...") visible: layout.extended enabled: !resourcesUpdatesModel.isProgressing onClicked: Navigation.openApplication(resource) } } onClicked: { layout.extended = !layout.extended } } } readonly property alias secSinceUpdate: resourcesUpdatesModel.secsToLastUpdate state: ( updateModel.hasUpdates ? "has-updates" : resourcesUpdatesModel.isProgressing ? "progressing" : ResourcesModel.isFetching ? "fetching" : resourcesUpdatesModel.needsReboot ? "reboot" : secSinceUpdate < 0 ? "unknown" : secSinceUpdate === 0 ? "now-uptodate" : secSinceUpdate < 1000 * 60 * 60 * 24 ? "uptodate" : secSinceUpdate < 1000 * 60 * 60 * 24 * 7 ? "medium" : "low" ) states: [ State { name: "fetching" PropertyChanges { target: page; title: i18nc("@info", "Fetching...") } PropertyChanges { target: page; footerLabel: i18nc("@info", "Checking for updates...") } }, State { name: "progressing" PropertyChanges { target: page; title: i18nc("@info", "Updating...") } PropertyChanges { target: page; footerLabel: resourcesUpdatesModel.progress<=0 ? i18nc("@info", "Fetching updates") : "" } }, State { name: "has-updates" PropertyChanges { target: page; title: i18nc("@info", "Updates") } }, State { name: "reboot" PropertyChanges { target: page; title: i18nc("@info", "The system requires a reboot") } PropertyChanges { target: page; footerLabel: i18nc("@info", "Reboot") } }, State { name: "now-uptodate" PropertyChanges { target: page; title: i18nc("@info", "The system is up to date") } PropertyChanges { target: page; footerLabel: i18nc("@info", "No updates") } }, State { name: "uptodate" PropertyChanges { target: page; title: i18nc("@info", "The system is up to date") } PropertyChanges { target: page; footerLabel: i18nc("@info", "No updates") } }, State { name: "medium" PropertyChanges { target: page; title: i18nc("@info", "No updates are available") } }, State { name: "low" PropertyChanges { target: page; title: i18nc("@info", "Should check for updates") } }, State { name: "unknown" PropertyChanges { target: page; title: i18nc("@info", "It is unknown when the last check for updates was") } } ] } diff --git a/discover/resources.qrc b/discover/resources.qrc index b93bd293..3a793dbc 100644 --- a/discover/resources.qrc +++ b/discover/resources.qrc @@ -1,39 +1,40 @@ qml/TopLevelPageData.qml qml/ApplicationsListPage.qml qml/ApplicationPage.qml qml/ReviewsPage.qml qml/AddonsView.qml qml/ApplicationDelegate.qml qml/InstallApplicationButton.qml qml/Rating.qml qml/UpdatesPage.qml qml/ReviewDialog.qml qml/ProgressView.qml qml/BrowsingPage.qml qml/InstalledPage.qml qml/SearchPage.qml qml/SourcesPage.qml qml/ReviewDelegate.qml qml/AddSourceDialog.qml qml/ConditionalLoader.qml qml/ConditionalObject.qml qml/ApplicationScreenshots.qml qml/LabelBackground.qml qml/ActionBridge.qml qml/KirigamiActionBridge.qml qml/DiscoverPage.qml qml/DiscoverWindow.qml qml/DiscoverDrawer.qml qml/ActionListItem.qml qml/LoadingPage.qml qml/SearchField.qml qml/Shadow.qml qml/DiscoverPopup.qml qml/AboutPage.qml + qml/LinkLabel.qml qml/navigation.js