1/*
2 *   SPDX-FileCopyrightText: 2012 Aleix Pol Gonzalez <aleixpol@blue-systems.com>
3 *
4 *   SPDX-License-Identifier: LGPL-2.0-or-later
5 */
6
7import QtQuick 2.15
8import QtQuick.Controls 2.3
9import QtQuick.Window 2.1
10import QtQuick.Layouts 1.1
11import QtGraphicalEffects 1.12
12import org.kde.discover 2.0
13import org.kde.discover.app 1.0
14import org.kde.kirigami 2.14 as Kirigami
15import "navigation.js" as Navigation
16
17DiscoverPage {
18    id: appInfo
19    property QtObject application: null
20    readonly property int visibleReviews: 3
21    title: appInfo.application.name
22    clip: true
23
24    // Usually this page is not the top level page, but when we are, isHome being
25    // true will ensure that the search field suggests we are searching in the list
26    // of available apps, not inside the app page itself. This will happen when
27    // Discover is launched e.g. from krunner or otherwise requested to show a
28    // specific application on launch.
29    readonly property bool isHome: true
30    function searchFor(text) {
31        if (text.length === 0)
32            return;
33        Navigation.openCategory(null, "")
34    }
35
36    ReviewsPage {
37        id: reviewsSheet
38        model: ReviewsModel {
39            id: reviewsModel
40            resource: appInfo.application
41        }
42    }
43
44    contextualActions: [originsMenuAction]
45
46    ActionGroup {
47        id: sourcesGroup
48        exclusive: true
49    }
50
51    Kirigami.Action {
52        id: originsMenuAction
53
54        text: i18n("Sources")
55        visible: children.length>1
56        children: sourcesGroup.actions
57        readonly property var r0: Instantiator {
58            model: ResourcesProxyModel {
59                id: alternativeResourcesModel
60                allBackends: true
61                resourcesUrl: appInfo.application.url
62            }
63            delegate: Action {
64                ActionGroup.group: sourcesGroup
65                text: model.application.availableVersion ? i18n("%1 - %2", displayOrigin, model.application.availableVersion) : displayOrigin
66                icon.name: sourceIcon
67                checkable: true
68                checked: appInfo.application === model.application
69                onTriggered: if(index>=0) {
70                    appInfo.application = model.application
71                }
72            }
73        }
74    }
75
76    Kirigami.Action {
77        id: invokeAction
78        visible: application.isInstalled && application.canExecute && !appbutton.isActive
79        text: application.executeLabel
80        icon.name: "media-playback-start"
81        onTriggered: application.invokeApplication()
82    }
83
84    actions {
85        main: appbutton.isActive ? appbutton.cancelAction : appbutton.action
86        right: invokeAction
87    }
88
89    InstallApplicationButton {
90        id: appbutton
91        Layout.rightMargin: Kirigami.Units.smallSpacing
92        application: appInfo.application
93        visible: false
94    }
95
96    topPadding: 0
97    bottomPadding: 0
98    leftPadding: 0
99    rightPadding: 0
100
101    // Icon, name, caption, screenshots, description and reviews
102    ColumnLayout {
103        spacing: 0
104        Kirigami.FlexColumn {
105            Layout.fillWidth: true
106            maximumWidth: Kirigami.Units.gridUnit * 40
107            RowLayout {
108                Layout.margins: Kirigami.Units.largeSpacing
109                Kirigami.Icon {
110                    Layout.preferredHeight: 80
111                    Layout.preferredWidth: 80
112                    source: appInfo.application.icon
113                    Layout.rightMargin: Kirigami.Units.smallSpacing * 2
114                }
115                ColumnLayout {
116                    spacing: 0
117                    Kirigami.Heading {
118                        text: appInfo.application.name
119                        lineHeight: 1.0
120                        maximumLineCount: 1
121                        font.weight: Font.DemiBold
122                        elide: Text.ElideRight
123                        Layout.fillWidth: true
124                        Layout.alignment: Text.AlignBottom
125                    }
126                    RowLayout {
127                        spacing: Kirigami.Units.largeSpacing
128                        Rating {
129                            rating: appInfo.application.rating ? appInfo.application.rating.sortableRating : 0
130                            starSize: summary.font.pointSize
131                        }
132                        Label {
133                            text: appInfo.application.rating ? i18np("%1 rating", "%1 ratings", appInfo.application.rating.ratingCount) : i18n("No ratings yet")
134                            opacity: 0.6
135                        }
136                    }
137                    Kirigami.Heading {
138                        id: summary
139                        level: 4
140                        text: appInfo.application.author
141                        opacity: 0.7
142                        maximumLineCount: 2
143                        lineHeight: lineCount > 1 ? 0.75 : 1.2
144                        elide: Text.ElideRight
145                        Layout.fillWidth: true
146                        Layout.alignment: Qt.AlignTop
147                    }
148                }
149            }
150        }
151
152
153        ScrollView {
154            visible: screenshots.count > 0
155            Layout.topMargin: Kirigami.Units.gridUnit * 2
156            Layout.fillWidth: true
157            Layout.preferredHeight: Math.min(Kirigami.Units.gridUnit * 20, Window.height * 0.4)
158            Layout.bottomMargin: Kirigami.Units.gridUnit * 2
159
160            ScrollBar.vertical.policy: ScrollBar.AlwaysOff
161
162            ApplicationScreenshots {
163                id: screenshots
164                resource: appInfo.application
165            }
166        }
167
168        Kirigami.FlexColumn {
169            Layout.fillWidth: true
170            Layout.margins: Kirigami.Units.largeSpacing
171            maximumWidth: Kirigami.Units.gridUnit * 40
172            Kirigami.Heading {
173                text: appInfo.application.comment
174                level: 2
175                font.weight: Font.DemiBold
176                Layout.fillWidth: true
177                wrapMode: Text.WordWrap
178            }
179
180            Label {
181                Layout.fillWidth: true
182                wrapMode: Text.WordWrap
183                text: appInfo.application.longDescription
184                onLinkActivated: Qt.openUrlExternally(link);
185            }
186
187            Kirigami.Heading {
188                Layout.topMargin: visible ? Kirigami.Units.largeSpacing : 0
189                text: i18n("What's New")
190                level: 2
191                visible: changelogLabel.text.length > 0
192            }
193
194            Label {
195                id: changelogLabel
196                Layout.topMargin: text.length > 0 ? Kirigami.Units.largeSpacing : 0
197                Layout.fillWidth: true
198                wrapMode: Text.WordWrap
199
200                Component.onCompleted: appInfo.application.fetchChangelog()
201                Connections {
202                    target: appInfo.application
203                    function onChangelogFetched(changelog) {
204                        changelogLabel.text = changelog
205                    }
206                }
207            }
208
209            Kirigami.LinkButton {
210                id: addonsButton
211                text: i18n("Addons")
212                visible: addonsView.containsAddons
213                onClicked: if (addonsView.addonsCount === 0) {
214                    Navigation.openExtends(application.appstreamId)
215                } else {
216                    addonsView.sheetOpen = true
217                }
218            }
219
220            Kirigami.Heading {
221                Layout.fillWidth: true
222                Layout.topMargin: Kirigami.Units.largeSpacing
223                font.weight: Font.DemiBold
224                text: i18n("Reviews")
225                Layout.alignment: Qt.AlignLeft | Qt.AlignBottom
226                level: 2
227                visible: rep.count > 0
228            }
229
230            Repeater {
231                id: rep
232                model: PaginateModel {
233                    sourceModel: reviewsSheet.model
234                    pageSize: visibleReviews
235                }
236                delegate: ReviewDelegate {
237                    Layout.topMargin: Kirigami.Units.largeSpacing
238                    Layout.fillWidth: true
239                    separator: false
240                    compact: true
241                }
242            }
243
244            RowLayout {
245                Layout.topMargin: Kirigami.Units.largeSpacing
246                Layout.bottomMargin: Kirigami.Units.largeSpacing
247                spacing: Kirigami.Units.largeSpacing
248
249                Button {
250                    visible: reviewsModel.count > visibleReviews
251
252                    text: i18np("Show %1 Review…", "Show All %1 Reviews…", reviewsModel.count)
253                    icon.name: "view-visible"
254
255                    onClicked: {
256                        reviewsSheet.open()
257                    }
258                }
259
260                Button {
261                    visible: appbutton.isStateAvailable && reviewsModel.backend && reviewsModel.backend.isResourceSupported(appInfo.application)
262                    enabled: appInfo.application.isInstalled
263
264                    text: appInfo.application.isInstalled ? i18n("Write a Review") : i18n("Install to Write a Review")
265                    icon.name: "document-edit"
266
267                    onClicked: {
268                        reviewsSheet.openReviewDialog()
269                    }
270                }
271            }
272
273            Repeater {
274                model: application.objects
275                delegate: Loader {
276                    property QtObject resource: appInfo.application
277                    source: modelData
278                }
279            }
280
281
282            // Details/metadata
283            Kirigami.Separator {
284                Layout.fillWidth: true
285                Layout.bottomMargin: Kirigami.Units.largeSpacing
286            }
287
288            Kirigami.FormLayout {
289                Layout.fillWidth: true
290
291                // Category row
292                Label {
293                    Kirigami.FormData.label: i18n("Category:")
294                    visible: text.length > 0
295                    Layout.fillWidth: true
296                    elide: Text.ElideRight
297                    text: appInfo.application.categoryDisplay
298                }
299
300                // Version row
301                Label {
302                    Kirigami.FormData.label: i18n("Version:")
303                    visible: text.length > 0
304                    Layout.fillWidth: true
305                    elide: Text.ElideRight
306                    text: appInfo.application.versionString
307                }
308
309                // Author row
310                Label {
311                    Kirigami.FormData.label: i18n("Author:")
312                    Layout.fillWidth: true
313                    elide: Text.ElideRight
314                    visible: text.length>0
315                    text: appInfo.application.author
316                }
317
318                // Size row
319                Label {
320                    Kirigami.FormData.label: i18n("Size:")
321                    Layout.fillWidth: true
322                    Layout.alignment: Text.AlignTop
323                    elide: Text.ElideRight
324                    text: appInfo.application.sizeDescription
325                }
326
327                // Source row
328                Label {
329                    Kirigami.FormData.label: i18n("Source:")
330                    Layout.fillWidth: true
331                    horizontalAlignment: Text.AlignLeft
332                    text: appInfo.application.displayOrigin
333                    elide: Text.ElideRight
334                }
335
336                // License row
337                RowLayout {
338                    Kirigami.FormData.label: i18n("License:")
339                    visible: appInfo.application.licenses.length>0
340                    Layout.fillWidth: true
341                    Repeater {
342                        model: appInfo.application.licenses
343                        delegate: Kirigami.UrlButton {
344                            id: licenseButton
345                            horizontalAlignment: Text.AlignLeft
346                            ToolTip.text: i18n("See full license terms")
347                            ToolTip.visible: licenseButton.mouseArea.containsMouse
348                            text: modelData.name
349                            url: modelData.url
350                            enabled: url !== ""
351                        }
352                    }
353                }
354
355                // "User Guide" row
356                Kirigami.UrlButton {
357                    visible: application.helpURL != ""
358                    Kirigami.FormData.label: i18n ("Documentation:")
359                    text: i18n("Read the user guide")
360                    url: application.helpURL
361                    Layout.fillWidth: true
362                    horizontalAlignment: Text.AlignLeft
363                }
364
365                // Homepage row
366                Kirigami.UrlButton {
367                    visible: application.homepage != ""
368                    Kirigami.FormData.label: i18n("Get involved:")
369                    text: i18n("Visit the app's website")
370                    url: application.homepage
371                    Layout.fillWidth: true
372                    horizontalAlignment: Text.AlignLeft
373                }
374
375                // Donate row
376                Kirigami.UrlButton {
377                    visible: application.donationURL != ""
378                    text: i18n("Make a donation")
379                    url: application.donationURL
380                    Layout.fillWidth: true
381                    horizontalAlignment: Text.AlignLeft
382                }
383
384                // "Report a Problem" row
385                Kirigami.UrlButton {
386                    visible: application.bugURL != ""
387                    text: i18n("Report a problem")
388                    url: application.bugURL
389                    Layout.fillWidth: true
390                    horizontalAlignment: Text.AlignLeft
391                }
392            }
393        }
394    }
395
396    readonly property var addons: AddonsView {
397        id: addonsView
398        application: appInfo.application
399    }
400}
401