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