Warning, /plasma/discover/discover/qml/ApplicationPage.qml is written in an unsupported language. File is not indexed.

0001 /*
0002  *   SPDX-FileCopyrightText: 2012 Aleix Pol Gonzalez <aleixpol@blue-systems.com>
0003  *   SPDX-FileCopyrightText: 2022 Nate Graham <nate@kde.org>
0004  *
0005  *   SPDX-License-Identifier: LGPL-2.0-or-later
0006  */
0007 
0008 import QtQuick 2.15
0009 import QtQuick.Controls 2.15
0010 import QtQuick.Window 2.15
0011 import QtQuick.Layouts 1.15
0012 import org.kde.discover 2.0
0013 import org.kde.discover.app 1.0
0014 import org.kde.kirigami 2.20 as Kirigami
0015 import org.kde.purpose 1.0 as Purpose
0016 import "navigation.js" as Navigation
0017 
0018 DiscoverPage {
0019     id: appInfo
0020 
0021     title: appInfo.application.name
0022     clip: true
0023 
0024     property QtObject application: null
0025     readonly property int visibleReviews: 3
0026     readonly property int internalSpacings: Kirigami.Units.largeSpacing
0027     readonly property int pageContentMargins: Kirigami.Units.gridUnit
0028 
0029     // Usually this page is not the top level page, but when we are, isHome being
0030     // true will ensure that the search field suggests we are searching in the list
0031     // of available apps, not inside the app page itself. This will happen when
0032     // Discover is launched e.g. from krunner or otherwise requested to show a
0033     // specific application on launch.
0034     readonly property bool isHome: true
0035 
0036     readonly property bool isOfflineUpgrade: application.packageName === "discover-offline-upgrade"
0037 
0038     function searchFor(text) {
0039         if (text.length === 0)
0040             return;
0041         Navigation.openCategory(null, "")
0042     }
0043 
0044     ReviewsPage {
0045         id: reviewsSheet
0046         model: ReviewsModel {
0047             id: reviewsModel
0048             resource: appInfo.application
0049         }
0050     }
0051 
0052     contextualActions: [originsMenuAction]
0053 
0054     ActionGroup {
0055         id: sourcesGroup
0056         exclusive: true
0057     }
0058 
0059     Kirigami.Action {
0060         id: originsMenuAction
0061 
0062         text: i18n("Sources")
0063         visible: children.length>1
0064         children: sourcesGroup.actions
0065         readonly property var r0: Instantiator {
0066             model: ResourcesProxyModel {
0067                 id: alternativeResourcesModel
0068                 allBackends: true
0069                 resourcesUrl: appInfo.application.url
0070             }
0071             delegate: Action {
0072                 ActionGroup.group: sourcesGroup
0073                 text: model.application.availableVersion ? i18n("%1 - %2", displayOrigin, model.application.availableVersion) : displayOrigin
0074                 icon.name: sourceIcon
0075                 checkable: true
0076                 checked: appInfo.application === model.application
0077                 onTriggered: if(index>=0) {
0078                     appInfo.application = model.application
0079                 }
0080             }
0081         }
0082     }
0083 
0084     Kirigami.Action {
0085         id: invokeAction
0086         visible: application.isInstalled && application.canExecute && !appbutton.isActive
0087         text: application.executeLabel
0088         icon.name: "media-playback-start"
0089         onTriggered: application.invokeApplication()
0090     }
0091 
0092     actions {
0093         main: appbutton.isActive ? appbutton.cancelAction : appbutton.action
0094         right: invokeAction
0095     }
0096 
0097     InstallApplicationButton {
0098         id: appbutton
0099         Layout.rightMargin: Kirigami.Units.smallSpacing
0100         application: appInfo.application
0101         visible: false
0102     }
0103 
0104     topPadding: 0
0105     bottomPadding: 0
0106     leftPadding: 0
0107     rightPadding: 0
0108 
0109     // Page content
0110     ColumnLayout {
0111         id: pageLayout
0112 
0113         spacing: appInfo.internalSpacings
0114 
0115         // Header and its background rectangle
0116         Rectangle {
0117             Layout.fillWidth: true
0118 
0119             implicitHeight: headerLayout.height + (headerLayout.anchors.topMargin * 2)
0120             color: Qt.darker(Kirigami.Theme.backgroundColor, 1.05)
0121 
0122             // Header layout with App icon, name, author, rating,
0123             // screenshots, and metadata
0124             ColumnLayout {
0125                 id: headerLayout
0126 
0127                 anchors {
0128                     topMargin: appInfo.internalSpacings
0129                     top: parent.top
0130                     left: parent.left
0131                     right: parent.right
0132                 }
0133 
0134                 spacing: appInfo.internalSpacings
0135 
0136                 // App icon, name, author or update info, rating
0137                 RowLayout {
0138                     Layout.leftMargin: appInfo.internalSpacings
0139                     Layout.rightMargin: appInfo.internalSpacings
0140 
0141                     spacing: 0 // Children bring their own
0142 
0143                     // App icon
0144                     Kirigami.Icon {
0145                         readonly property int size : applicationWindow().wideScreen ? Kirigami.Units.iconSizes.large * 2 : Kirigami.Units.iconSizes.huge
0146 
0147                         implicitWidth: size
0148                         implicitHeight: size
0149                         source: appInfo.application.icon
0150                         Layout.rightMargin: appInfo.internalSpacings
0151                     }
0152 
0153                     // App name, author, and rating
0154                     ColumnLayout {
0155                         Layout.fillWidth: true
0156 
0157                         spacing: 0
0158 
0159                         // App name
0160                         Kirigami.Heading {
0161                             Layout.fillWidth: true
0162                             text: appInfo.application.name
0163                             type: Kirigami.Heading.Type.Primary
0164                             wrapMode: Text.Wrap
0165                             maximumLineCount: 5
0166                             elide: Text.ElideRight
0167                         }
0168 
0169                         // Author (for apps) or upgrade info (for offline upgrades)
0170                         Label {
0171                             id: author
0172 
0173                             Layout.fillWidth: true
0174                             Layout.bottomMargin: appInfo.internalSpacings
0175 
0176                             visible: text.length > 0
0177 
0178                             opacity: 0.8
0179                             text: {
0180                                 if (appInfo.isOfflineUpgrade) {
0181                                     return appInfo.application.upgradeText.length > 0 ? appInfo.application.upgradeText : "";
0182                                 } else if (appInfo.application.author.length > 0) {
0183                                     return appInfo.application.author;
0184                                 } else {
0185                                     return i18n("Unknown author");
0186                                 }
0187                             }
0188                             wrapMode: Text.Wrap
0189                             maximumLineCount: 5
0190                             elide: Text.ElideRight
0191                         }
0192 
0193                         // Rating
0194                         RowLayout {
0195                             Layout.fillWidth: true
0196 
0197                             // Not relevant to the offline upgrade use case
0198                             visible: !appInfo.isOfflineUpgrade
0199 
0200                             Rating {
0201                                 opacity: 0.8
0202                                 rating: appInfo.application.rating ? appInfo.application.rating.sortableRating : 0
0203                                 starSize: author.font.pointSize
0204                             }
0205 
0206                             Label {
0207                                 opacity: 0.8
0208                                 text: appInfo.application.rating ? i18np("%1 rating", "%1 ratings", appInfo.application.rating.ratingCount) : i18n("No ratings yet")
0209                             }
0210                         }
0211                     }
0212                 }
0213 
0214                 // Screenshots
0215                 Kirigami.InlineMessage {
0216                     type: Kirigami.MessageType.Warning
0217                     Layout.fillWidth: true
0218                     Layout.margins: Kirigami.Units.smallSpacing
0219                     visible: screenshots.hasFailed
0220                     text: i18n("Could not access the screenshots")
0221                 }
0222 
0223                 ScrollView {
0224                     id: screenshotsScroll
0225                     visible: screenshots.count > 0 && !screenshots.hasFailed
0226                     Layout.maximumWidth: headerLayout.width
0227                     Layout.alignment: Qt.AlignHCenter
0228                     Layout.preferredHeight: Math.min(Kirigami.Units.gridUnit * 20, Window.height * 0.25)
0229 
0230                     ScrollBar.vertical.policy: ScrollBar.AlwaysOff
0231 
0232                     ApplicationScreenshots {
0233                         id: screenshots
0234                         resource: appInfo.application
0235                         delegateHeight: parent.Layout.preferredHeight * 0.8
0236                         showNavigationArrows: screenshotsScroll.width === headerLayout.width
0237                     }
0238                 }
0239 
0240                 // Metadata
0241                 Flow {
0242                     id: metadataLayout
0243                     readonly property int itemWidth: Kirigami.Units.gridUnit * 7
0244                     readonly property int visibleChildren: countVisibleChildren(children)
0245                     function countVisibleChildren(items) {
0246                         let ret = 0;
0247                         for (const itemPos in items) {
0248                             const item = items[itemPos];
0249                             ret += item.visible;
0250                         }
0251                         return ret;
0252                     }
0253 
0254                     // This centers the Flow in the page, no matter how many items have
0255                     // flowed onto other rows
0256                     Layout.maximumWidth: ((itemWidth + spacing) * Math.min(visibleChildren, Math.floor(headerLayout.width / (itemWidth + spacing)))) - spacing
0257                     Layout.fillWidth: true
0258                     Layout.alignment: Qt.AlignHCenter
0259                     Layout.leftMargin: appInfo.internalSpacings
0260                     Layout.rightMargin: appInfo.internalSpacings
0261 
0262                     spacing: Kirigami.Units.smallSpacing
0263 
0264                     // Not relevant to the offline upgrade use case
0265                     visible: !appInfo.isOfflineUpgrade
0266 
0267                     onImplicitWidthChanged: visibleChildrenChanged()
0268 
0269                     // Version
0270                     ColumnLayout {
0271                         id: versionColumn
0272                         width: metadataLayout.itemWidth
0273 
0274                         spacing: Kirigami.Units.smallSpacing
0275 
0276                         Label {
0277                             Layout.fillWidth: true
0278                             opacity: 0.7
0279                             text: i18n("Version")
0280                             horizontalAlignment: Text.AlignHCenter
0281                             verticalAlignment: Text.AlignBottom
0282                             wrapMode: Text.Wrap
0283                             maximumLineCount: 2
0284                         }
0285                         Label {
0286                             Layout.fillWidth: true
0287                             text: appInfo.application.versionString
0288                             horizontalAlignment: Text.AlignHCenter
0289                             verticalAlignment: Text.AlignTop
0290                             wrapMode: Text.Wrap
0291                             maximumLineCount: 3
0292                             elide: Text.ElideRight
0293                         }
0294                     }
0295 
0296                     // Size
0297                     ColumnLayout {
0298                         id: sizeColumn
0299                         width: metadataLayout.itemWidth
0300 
0301                         spacing: Kirigami.Units.smallSpacing
0302 
0303                         Label {
0304                             Layout.fillWidth: true
0305                             opacity: 0.7
0306                             text: i18n("Size")
0307                             horizontalAlignment: Text.AlignHCenter
0308                             verticalAlignment: Text.AlignBottom
0309                             wrapMode: Text.Wrap
0310                             maximumLineCount: 2
0311                         }
0312                         Label {
0313                             Layout.fillWidth: true
0314                             text: appInfo.application.sizeDescription
0315                             horizontalAlignment: Text.AlignHCenter
0316                             verticalAlignment: Text.AlignTop
0317                             wrapMode: Text.Wrap
0318                             maximumLineCount: 3
0319                             elide: Text.ElideRight
0320                         }
0321                     }
0322 
0323                     // Distributor
0324                     ColumnLayout {
0325                         id: distributorColumn
0326                         width: metadataLayout.itemWidth
0327 
0328                         spacing: Kirigami.Units.smallSpacing
0329 
0330                         Label {
0331                             Layout.fillWidth: true
0332                             opacity: 0.7
0333                             text: i18n("Distributed by")
0334                             horizontalAlignment: Text.AlignHCenter
0335                             verticalAlignment: Text.AlignBottom
0336                             wrapMode: Text.Wrap
0337                             maximumLineCount: 2
0338                         }
0339                         Label {
0340                             Layout.fillWidth: true
0341                             text: appInfo.application.displayOrigin
0342                             horizontalAlignment: Text.AlignHCenter
0343                             verticalAlignment: Text.AlignTop
0344                             wrapMode: Text.Wrap
0345                             maximumLineCount: 3
0346                             elide: Text.ElideRight
0347                         }
0348                     }
0349 
0350                     // License(s)
0351                     ColumnLayout {
0352                         id: licenseColumn
0353                         width: metadataLayout.itemWidth
0354 
0355                         spacing: Kirigami.Units.smallSpacing
0356 
0357                         Label {
0358                             Layout.fillWidth: true
0359                             opacity: 0.7
0360                             text: i18np("License", "Licenses", appInfo.application.licenses.length)
0361                             horizontalAlignment: Text.AlignHCenter
0362                             verticalAlignment: Text.AlignBottom
0363                             wrapMode: Text.Wrap
0364                             maximumLineCount: 2
0365                         }
0366                         Label {
0367                             Layout.fillWidth: true
0368                             visible : appInfo.application.licenses.length === 0
0369                             text: i18nc("The app does not provide any licenses", "Unknown")
0370                             horizontalAlignment: Text.AlignHCenter
0371                             verticalAlignment: Text.AlignTop
0372                             wrapMode: Text.Wrap
0373                             maximumLineCount: 3
0374                             elide: Text.ElideRight
0375                         }
0376                         ColumnLayout {
0377                             Layout.fillWidth: true
0378                             visible : appInfo.application.licenses.length > 0
0379                             spacing: 0
0380 
0381                             Repeater {
0382                                 model: appInfo.application.licenses.slice(0, 2)
0383                                 delegate: RowLayout {
0384                                     Layout.fillWidth: true
0385                                     spacing: 0
0386 
0387                                     Kirigami.UrlButton {
0388                                         Layout.fillWidth: true
0389                                         enabled: url !== ""
0390                                         text: modelData.name
0391                                         url: modelData.url
0392                                         horizontalAlignment: Text.AlignHCenter
0393                                         verticalAlignment: Text.AlignTop
0394                                         wrapMode: Text.Wrap
0395                                         maximumLineCount: 3
0396                                         elide: Text.ElideRight
0397                                         color: !modelData.hasFreedom ? Kirigami.Theme.neutralTextColor: enabled ? Kirigami.Theme.linkColor : Kirigami.Theme.textColor
0398                                     }
0399 
0400                                     // Button to open "What's the risk of proprietary software?" sheet
0401                                     ToolButton {
0402                                         visible: !modelData.hasFreedom
0403                                         icon.name: "help-contextual"
0404                                         onClicked: properietarySoftwareRiskExplanationDialog.open();
0405 
0406                                         ToolTip {
0407                                             text: i18n("What does this mean?")
0408                                         }
0409                                     }
0410                                 }
0411                             }
0412 
0413                             // "See More licenses" link, in case there are a lot of them
0414                             Kirigami.LinkButton {
0415                                 Layout.fillWidth: true
0416                                 visible: application.licenses.length > 3
0417                                 text: i18np("See more…", "See more…", appInfo.application.licenses.length)
0418                                 horizontalAlignment: Text.AlignHCenter
0419                                 verticalAlignment: Text.AlignTop
0420                                 elide: Text.ElideRight
0421                                 onClicked: allLicensesSheet.open();
0422                             }
0423 
0424                         }
0425                     }
0426                     // Content Rating
0427                     ColumnLayout {
0428                         width: metadataLayout.itemWidth
0429                         visible: appInfo.application.contentRatingText.length > 0
0430                         spacing: Kirigami.Units.smallSpacing
0431 
0432                         Label {
0433                             Layout.fillWidth: true
0434                             opacity: 0.7
0435                             text: i18n("Content Rating")
0436                             horizontalAlignment: Text.AlignHCenter
0437                             verticalAlignment: Text.AlignBottom
0438                             wrapMode: Text.Wrap
0439                             maximumLineCount: 2
0440                         }
0441 
0442                         Label {
0443                             Layout.fillWidth: true
0444                             visible: text.length > 0
0445                             text: appInfo.application.contentRatingMinimumAge === 0 ? "" : i18n("Age: %1+", appInfo.application.contentRatingMinimumAge)
0446                             horizontalAlignment: Text.AlignHCenter
0447                             verticalAlignment: Text.AlignTop
0448                             wrapMode: Text.Wrap
0449                             maximumLineCount: 3
0450                             elide: Text.ElideRight
0451                         }
0452 
0453                         Label {
0454                             Layout.fillWidth: true
0455                             text: appInfo.application.contentRatingText
0456                             horizontalAlignment: Text.AlignHCenter
0457                             verticalAlignment: Text.AlignTop
0458                             wrapMode: Text.Wrap
0459                             maximumLineCount: 3
0460                             elide: Text.ElideRight
0461 
0462                             readonly property var colors: [ Kirigami.Theme.textColor, Kirigami.Theme.neutralTextColor ]
0463                             color: colors[appInfo.application.contentRatingIntensity]
0464                         }
0465 
0466                         Kirigami.LinkButton {
0467                             Layout.fillWidth: true
0468                             visible: appInfo.application.contentRatingDescription.length > 0
0469                             text: i18nc("@action", "See details…")
0470                             horizontalAlignment: Text.AlignHCenter
0471                             verticalAlignment: Text.AlignTop
0472                             elide: Text.ElideRight
0473                             onClicked: contentRatingDialog.open();
0474                         }
0475                     }
0476                 }
0477             }
0478 
0479             Kirigami.Separator {
0480                 anchors {
0481                     left: parent.left
0482                     right: parent.right
0483                     top: parent.bottom
0484                 }
0485             }
0486         }
0487 
0488 
0489         Repeater {
0490             Layout.bottomMargin: appInfo.internalSpacings * 2
0491 
0492             model: application.topObjects
0493             delegate: Loader {
0494                 property QtObject resource: appInfo.application
0495 
0496                 Layout.fillWidth: item.Layout.fillWidth
0497                 Layout.leftMargin: appInfo.pageContentMargins
0498                 Layout.rightMargin: appInfo.pageContentMargins
0499 
0500                 source: modelData
0501             }
0502         }
0503 
0504         // Layout for textual content; this isn't in the main ColumnLayout
0505         // because we want it to be bounded to a maximum width
0506         ColumnLayout {
0507             id: textualContentLayout
0508 
0509             Layout.fillWidth: true
0510             Layout.maximumWidth: Math.max(Math.round(appInfo.width * 0.75), Kirigami.Units.gridUnit * 35)
0511             Layout.margins: appInfo.pageContentMargins
0512             Layout.alignment: Qt.AlignHCenter
0513 
0514             spacing: appInfo.internalSpacings
0515 
0516             // Short description
0517             // Not using Kirigami.Heading here because that component doesn't
0518             // support selectable text, and we want this to be selectable because
0519             // it's also used to show the path for local packages, and that makes
0520             // sense to be selectable
0521             Kirigami.SelectableLabel {
0522                 Layout.fillWidth: true
0523                 // Not relevant to the offline upgrade use case because we
0524                 // display the info in the header instead
0525                 visible: !appInfo.isOfflineUpgrade
0526                 text: appInfo.application.comment
0527                 wrapMode: Text.Wrap
0528 
0529                 // Match `level: 2` in Kirigami.Heading
0530                 font.pointSize: Kirigami.Theme.defaultFont.pointSize * 1.2
0531                 // Match `type: Kirigami.Heading.Type.Primary` in Kirigami.Heading
0532                 font.weight: Font.DemiBold
0533 
0534                 Accessible.role: Accessible.Heading
0535             }
0536 
0537             // Long app description
0538             Kirigami.SelectableLabel {
0539                 Layout.fillWidth: true
0540                 wrapMode: Text.WordWrap
0541                 text: appInfo.application.longDescription
0542                 textFormat: TextEdit.RichText
0543                 onLinkActivated: Qt.openUrlExternally(link);
0544             }
0545 
0546             // External resources
0547             GridLayout {
0548                 id: externalResourcesLayout
0549                 readonly property int visibleButtons: (helpButton.visible ? 1 : 0)
0550                                                     + (homepageButton.visible ? 1: 0)
0551                                                     + (addonsButton.visible ? 1 : 0)
0552                                                     + (shareButton.visible ? 1 : 0)
0553                 readonly property int buttonWidth: Math.round(textualContentLayout.width / columns)
0554                 readonly property int tallestButtonHeight: Math.max(helpButton.implicitHeight,
0555                                                                     homepageButton.implicitHeight,
0556                                                                     shareButton.implicitHeight,
0557                                                                     addonsButton.implicitHeight)
0558                 readonly property int minWidth: Math.max(helpButton.visible ? helpButton.implicitMinWidth : 0,
0559                                                                   homepageButton.visible ? homepageButton.implicitMinWidth: 0,
0560                                                                   addonsButton.visible ? addonsButton.implicitMinWidth : 0,
0561                                                                   shareButton.visible ? shareButton.implicitMinWidth : 0)
0562                 readonly property bool stackedlayout: minWidth > Math.round(textualContentLayout.width / visibleButtons) -
0563                                                         (columnSpacing * (visibleButtons + 1))
0564 
0565                 Layout.fillWidth: true
0566                 Layout.bottomMargin: appInfo.internalSpacings * 2
0567 
0568 
0569                 rows: stackedlayout ? visibleButtons : 1
0570                 columns: stackedlayout ? 1: visibleButtons
0571                 rowSpacing: Kirigami.Units.smallSpacing
0572                 columnSpacing: Kirigami.Units.smallSpacing
0573 
0574                 visible: visibleButtons > 0
0575 
0576                 ApplicationResourceButton {
0577                     id: helpButton
0578 
0579                     Layout.fillWidth: true
0580                     Layout.maximumWidth: externalResourcesLayout.buttonWidth
0581                     Layout.minimumHeight: externalResourcesLayout.tallestButtonHeight
0582 
0583                     visible: application.helpURL != ""
0584 
0585                     buttonIcon: "documentation"
0586                     title: i18n("Documentation")
0587                     subtitle: i18n("Read the project's official documentation")
0588                     tooltipText: application.helpURL
0589 
0590                     onClicked: Qt.openUrlExternally(application.helpURL);
0591                 }
0592 
0593                 ApplicationResourceButton {
0594                     id: homepageButton
0595 
0596                     Layout.fillWidth: true
0597                     Layout.maximumWidth: externalResourcesLayout.buttonWidth
0598                     Layout.minimumHeight: externalResourcesLayout.tallestButtonHeight
0599 
0600                     visible: application.homepage != ""
0601 
0602                     buttonIcon: "internet-services"
0603                     title: i18n("Website")
0604                     subtitle: i18n("Visit the project's website")
0605                     tooltipText: application.homepage
0606 
0607                     onClicked: Qt.openUrlExternally(application.homepage);
0608                 }
0609 
0610                 ApplicationResourceButton {
0611                     id: addonsButton
0612 
0613                     Layout.fillWidth: true
0614                     Layout.maximumWidth: externalResourcesLayout.buttonWidth
0615                     Layout.minimumHeight: externalResourcesLayout.tallestButtonHeight
0616 
0617                     visible: addonsView.containsAddons
0618 
0619                     buttonIcon: "extension-symbolic"
0620                     title: i18n("Addons")
0621                     subtitle: i18n("Install or remove additional functionality")
0622 
0623                     onClicked: {
0624                         if (addonsView.addonsCount === 0) {
0625                             Navigation.openExtends(application.appstreamId, appInfo.application.name)
0626                         } else {
0627                             addonsView.sheetOpen = true
0628                         }
0629                     }
0630                 }
0631 
0632                 ApplicationResourceButton {
0633                     id: shareButton
0634 
0635                     Layout.fillWidth: true
0636                     Layout.maximumWidth: externalResourcesLayout.buttonWidth
0637                     Layout.minimumHeight: externalResourcesLayout.tallestButtonHeight
0638 
0639                     buttonIcon: "document-share"
0640                     title: i18nc("Exports the application's URL to an external service", "Share")
0641                     subtitle: i18n("Send a link for this application")
0642                     tooltipText: application.url.toString()
0643                     visible: tooltipText.length > 0 && !appInfo.isOfflineUpgrade
0644 
0645                     Kirigami.PromptDialog {
0646                         id: shareSheet
0647                         parent: applicationWindow().overlay
0648                         title: shareButton.title
0649                         standardButtons: Dialog.NoButton
0650 
0651                         Purpose.AlternativesView {
0652                             id: alts
0653                             implicitWidth: Kirigami.Units.gridUnit
0654                             pluginType: "ShareUrl"
0655                             inputData: {
0656                                 "urls": [ application.url.toString() ],
0657                                 "title": i18nc("The subject line for an email. %1 is the name of an application", "Check out the %1 app!", application.name)
0658                             }
0659                             onFinished: {
0660                                 shareSheet.close()
0661                                 if (error !== 0) {
0662                                     console.error("job finished with error", error, message)
0663                                 }
0664                                 alts.reset()
0665                             }
0666                         }
0667                     }
0668 
0669                     onClicked: {
0670                         shareSheet.open();
0671                     }
0672                 }
0673             }
0674 
0675             Kirigami.Heading {
0676                 visible: changelogLabel.visible
0677                 text: i18n("What's New")
0678                 level: 2
0679                 type: Kirigami.Heading.Type.Primary
0680                 wrapMode: Text.Wrap
0681             }
0682 
0683             // Changelog text
0684             Label {
0685                 id: changelogLabel
0686 
0687                 Layout.fillWidth: true
0688                 Layout.bottomMargin: appInfo.internalSpacings * 2
0689 
0690                 // Some backends are known to produce empty line break as a text
0691                 visible: text !== "" && text !== "<br />"
0692                 wrapMode: Text.WordWrap
0693 
0694                 Component.onCompleted: appInfo.application.fetchChangelog()
0695                 Connections {
0696                     target: appInfo.application
0697                     function onChangelogFetched(changelog) {
0698                         changelogLabel.text = changelog
0699                     }
0700                 }
0701             }
0702 
0703             Kirigami.LoadingPlaceholder {
0704                 Layout.alignment: Qt.AlignHCenter
0705                 Layout.maximumWidth: Kirigami.Units.gridUnit * 15
0706                 Layout.bottomMargin: appInfo.internalSpacings * 2
0707                 visible: reviewsModel.fetching && !reviewsError.visible
0708                 text: i18n("Loading reviews for %1", appInfo.application.name)
0709             }
0710 
0711             Kirigami.Heading {
0712                 Layout.fillWidth: true
0713                 visible: rep.count > 0 || reviewsError.visible
0714                 text: i18n("Reviews")
0715                 level: 2
0716                 type: Kirigami.Heading.Type.Primary
0717                 wrapMode: Text.Wrap
0718             }
0719 
0720             Kirigami.InlineMessage {
0721                 id: reviewsError
0722                 type: Kirigami.MessageType.Warning
0723                 Layout.fillWidth: true
0724                 visible: reviewsModel.backend && text.length > 0
0725                 text: reviewsModel.backend ? reviewsModel.backend.errorMessage : ""
0726             }
0727 
0728             // Top three reviews
0729             Repeater {
0730                 id: rep
0731                 model: PaginateModel {
0732                     sourceModel: reviewsSheet.model
0733                     pageSize: visibleReviews
0734                 }
0735                 delegate: ReviewDelegate {
0736                     Layout.fillWidth: true
0737                     separator: false
0738                     compact: true
0739                 }
0740             }
0741 
0742             // Review-related buttons
0743             Flow {
0744                 Layout.fillWidth: true
0745                 Layout.bottomMargin: appInfo.internalSpacings *2
0746 
0747                 spacing: appInfo.internalSpacings
0748 
0749                 Button {
0750                     visible: reviewsModel.count > visibleReviews
0751 
0752                     text: i18np("Show all %1 Reviews", "Show all %1 Reviews", reviewsModel.count)
0753                     icon.name: "view-visible"
0754 
0755                     onClicked: {
0756                         reviewsSheet.open()
0757                     }
0758                 }
0759 
0760                 Button {
0761                     visible: appbutton.isStateAvailable && reviewsModel.backend && !reviewsError.visible && reviewsModel.backend.isResourceSupported(appInfo.application)
0762                     enabled: appInfo.application.isInstalled
0763 
0764                     text: appInfo.application.isInstalled ? i18n("Write a Review") : i18n("Install to Write a Review")
0765                     icon.name: "document-edit"
0766 
0767                     onClicked: {
0768                         reviewsSheet.openReviewDialog()
0769                     }
0770                 }
0771             }
0772 
0773             // "Get Involved" section
0774             Kirigami.Heading {
0775                 visible: getInvolvedLayout.visible
0776                 text: i18n("Get Involved")
0777                 level: 2
0778                 type: Kirigami.Heading.Type.Primary
0779                 wrapMode: Text.Wrap
0780             }
0781 
0782             GridLayout {
0783                 id: getInvolvedLayout
0784 
0785                 readonly property int visibleButtons: (donateButton.visible ? 1 : 0)
0786                                                     + (bugButton.visible ? 1 : 0)
0787                                                     + (contributeButton.visible ? 1 : 0)
0788                 readonly property int buttonWidth: Math.round(textualContentLayout.width / columns)
0789                 readonly property int tallestButtonHeight: Math.max(donateButton.implicitHeight,
0790                                                                     bugButton.implicitHeight,
0791                                                                     contributeButton.implicitHeight)
0792                 readonly property int minWidth: Math.max(donateButton.visible ? donateButton.implicitMinWidth : 0,
0793                                                           bugButton.visible ? bugButton.implicitMinWidth: 0,
0794                                                           contributeButton.visible ? contributeButton.implicitMinWidth : 0)
0795                 readonly property bool stackedlayout: minWidth > Math.round(textualContentLayout.width / visibleButtons) -
0796                                                         (columnSpacing * (visibleButtons + 1))
0797 
0798                 Layout.fillWidth: true
0799                 Layout.bottomMargin: appInfo.internalSpacings * 2
0800 
0801                 rows: stackedlayout ? visibleButtons : 1
0802                 columns: stackedlayout ? 1: visibleButtons
0803                 rowSpacing: Kirigami.Units.smallSpacing
0804                 columnSpacing: Kirigami.Units.smallSpacing
0805 
0806                 visible: visibleButtons > 0
0807 
0808                 ApplicationResourceButton {
0809                     id: donateButton
0810 
0811                     Layout.fillWidth: true
0812                     Layout.maximumWidth: getInvolvedLayout.buttonWidth
0813                     Layout.minimumHeight: getInvolvedLayout.tallestButtonHeight
0814 
0815                     visible: application.donationURL != ""
0816 
0817                     buttonIcon: "help-donate"
0818                     title: i18n("Donate")
0819                     subtitle: i18n("Support and thank the developers by donating to their project")
0820                     tooltipText: application.donationURL
0821 
0822                     onClicked: Qt.openUrlExternally(application.donationURL);
0823                 }
0824 
0825                 ApplicationResourceButton {
0826                     id: bugButton
0827 
0828                     Layout.fillWidth: true
0829                     Layout.maximumWidth: getInvolvedLayout.buttonWidth
0830                     Layout.minimumHeight: getInvolvedLayout.tallestButtonHeight
0831 
0832                     visible: application.bugURL != ""
0833 
0834                     buttonIcon: "tools-report-bug"
0835                     title: i18n("Report Bug")
0836                     subtitle: i18n("Log an issue you found to help get it fixed")
0837                     tooltipText: application.bugURL
0838 
0839                     onClicked: Qt.openUrlExternally(application.bugURL);
0840                 }
0841 
0842                 ApplicationResourceButton {
0843                     id: contributeButton
0844 
0845                     Layout.fillWidth: true
0846                     Layout.maximumWidth: getInvolvedLayout.buttonWidth
0847                     Layout.minimumHeight: getInvolvedLayout.tallestButtonHeight
0848 
0849                     visible: application.contributeURL != ""
0850                     title: i18n("Contribute")
0851                     subtitle: i18n("Help the developers by coding, designing, testing, or translating")
0852                     tooltipText: application.contributeURL
0853                     buttonIcon: "project-development"
0854                     onClicked: Qt.openUrlExternally(application.contributeURL);
0855                 }
0856             }
0857 
0858             Repeater {
0859                 model: application.objects
0860                 delegate: Loader {
0861                     property QtObject resource: appInfo.application
0862                     source: modelData
0863                     Layout.fillWidth: true
0864                 }
0865             }
0866         }
0867     }
0868 
0869     readonly property var addons: AddonsView {
0870         id: addonsView
0871         application: appInfo.application
0872     }
0873 
0874     Kirigami.Dialog {
0875         id: allLicensesSheet
0876         title: i18n("All Licenses")
0877         standardButtons: Kirigami.Dialog.NoButton
0878         preferredWidth: Kirigami.Units.gridUnit * 16
0879         maximumHeight: Kirigami.Units.gridUnit * 20
0880 
0881         ColumnLayout {
0882             spacing: 0
0883             
0884             Repeater {
0885                 id: listview
0886 
0887                 model: appInfo.application.licenses
0888 
0889                 delegate: Kirigami.BasicListItem {
0890                     activeBackgroundColor: "transparent"
0891                     activeTextColor: Kirigami.Theme.textColor
0892                     separatorVisible: false
0893                     contentItem: Kirigami.UrlButton {
0894                         enabled: url !== ""
0895                         text: modelData.name
0896                         url: modelData.url
0897                         horizontalAlignment: Text.AlignLeft
0898                         color: !modelData.hasFreedom ? Kirigami.Theme.neutralTextColor: enabled ? Kirigami.Theme.linkColor : Kirigami.Theme.textColor
0899                     }
0900                 }
0901             }
0902         }
0903     }
0904 
0905     Kirigami.PromptDialog {
0906         id: contentRatingDialog
0907         title: i18n("Content Rating")
0908         preferredWidth: Kirigami.Units.gridUnit * 25
0909         standardButtons: Kirigami.Dialog.NoButton
0910 
0911         Label {
0912             text: appInfo.application.contentRatingDescription
0913             textFormat: Text.MarkdownText
0914             wrapMode: Text.Wrap
0915         }
0916     }
0917 
0918     Kirigami.PromptDialog {
0919         id: properietarySoftwareRiskExplanationDialog
0920         preferredWidth: Kirigami.Units.gridUnit * 25
0921         standardButtons: Kirigami.Dialog.NoButton
0922 
0923         title: i18n("Risks of proprietary software")
0924 
0925         TextEdit {
0926             readonly property string proprietarySoftwareExplanationPage: "https://www.gnu.org/proprietary"
0927 
0928             text: homepageButton.visible ?
0929                 xi18nc("@info", "This application's source code is partially or entirely closed to public inspection and improvement. That means third parties and users like you cannot verify its operation, security, and trustworthiness, or modify and redistribute it without the authors' express permission.<nl/><nl/>The application may be perfectly safe to use, or it may be acting against you in various ways—such as harvesting your personal information, tracking your location, or transmitting the contents of your files to someone else. There is no easy way to be sure, so you should only install this application if you fully trust its authors (<link url='%1'>%2</link>).<nl/><nl/>You can learn more at <link url='%3'>%3</link>.", application.homepage, author.text, proprietarySoftwareExplanationPage) :
0930                 xi18nc("@info", "This application's source code is partially or entirely closed to public inspection and improvement. That means third parties and users like you cannot verify its operation, security, and trustworthiness, or modify and redistribute it without the authors' express permission.<nl/><nl/>The application may be perfectly safe to use, or it may be acting against you in various ways—such as harvesting your personal information, tracking your location, or transmitting the contents of your files to someone else. There is no easy way to be sure, so you should only install this application if you fully trust its authors (%1).<nl/><nl/>You can learn more at <link url='%2'>%2</link>.", author.text, proprietarySoftwareExplanationPage)
0931             wrapMode: Text.Wrap
0932             textFormat: TextEdit.RichText
0933             readOnly: true
0934 
0935             color: Kirigami.Theme.textColor
0936             selectedTextColor: Kirigami.Theme.highlightedTextColor
0937             selectionColor: Kirigami.Theme.highlightColor
0938 
0939             onLinkActivated: (url) => Qt.openUrlExternally(url)
0940 
0941             HoverHandler {
0942                 acceptedButtons: Qt.NoButton
0943                 cursorShape: parent.hoveredLink ? Qt.PointingHandCursor : Qt.ArrowCursor
0944             }
0945         }
0946     }
0947 }