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

0001 pragma ComponentBehavior: Bound
0002 
0003 import QtQuick
0004 import QtQuick.Controls as QQC2
0005 import QtQuick.Layouts
0006 import org.kde.discover as Discover
0007 import org.kde.discover.app as DiscoverApp
0008 import org.kde.kirigami as Kirigami
0009 
0010 DiscoverPage {
0011     id: page
0012 
0013     title: i18n("Updates")
0014 
0015     property string footerLabel: ""
0016     property int footerProgress: 0
0017     property bool busy: false
0018     readonly property string name: title
0019 
0020     Discover.ResourcesUpdatesModel {
0021         id: resourcesUpdatesModel
0022         onPassiveMessage: {
0023             sheet.errorMessage = message;
0024             sheet.visible = true;
0025         }
0026         onIsProgressingChanged: {
0027             if (!isProgressing) {
0028                 resourcesUpdatesModel.prepare()
0029             }
0030         }
0031 
0032         Component.onCompleted: {
0033             if (!isProgressing) {
0034                 resourcesUpdatesModel.prepare()
0035             }
0036         }
0037     }
0038 
0039     Kirigami.OverlaySheet {
0040         id: sheet
0041 
0042         property string errorMessage: ""
0043 
0044         parent: page.QQC2.Overlay.overlay
0045 
0046         title: contentLoader.sourceComponent === friendlyMessageComponent ? i18n("Update Issue") :  i18n("Technical details")
0047 
0048         Loader {
0049             id: contentLoader
0050             active: true
0051             sourceComponent: friendlyMessageComponent
0052 
0053             Component {
0054                 id: friendlyMessageComponent
0055 
0056                 ColumnLayout {
0057                     QQC2.Label {
0058                         id: friendlyMessage
0059                         Layout.fillWidth: true
0060                         Layout.maximumWidth: Math.round(page.width * 0.75)
0061                         Layout.bottomMargin: Kirigami.Units.largeSpacing * 2
0062                         text: i18n("There was an issue installing this update. Please try again later.")
0063                         wrapMode: Text.WordWrap
0064                     }
0065                     QQC2.Button {
0066                         id: seeDetailsAndreportIssueButton
0067                         Layout.alignment: Qt.AlignRight
0068                         text: i18n("See Technical Details")
0069                         icon.name: "view-process-system"
0070                         onClicked: {
0071                             contentLoader.sourceComponent = nerdyDetailsComponent;
0072                         }
0073                     }
0074                 }
0075             }
0076 
0077             Component {
0078                 id: nerdyDetailsComponent
0079 
0080                 ColumnLayout {
0081                     spacing: Kirigami.Units.smallSpacing
0082 
0083                     QQC2.TextArea {
0084                         Layout.fillWidth: true
0085                         text: sheet.errorMessage
0086                         textFormat: TextEdit.RichText
0087                         wrapMode: TextEdit.Wrap
0088                     }
0089 
0090                     QQC2.Label {
0091                         Layout.fillWidth: true
0092                         Layout.maximumWidth: Math.round(page.width*0.75)
0093                         Layout.topMargin: Kirigami.Units.largeSpacing
0094                         Layout.bottomMargin: Kirigami.Units.largeSpacing
0095                         text: i18nc("@info %1 is the name of the user's distro/OS", "If the error indicated above looks like a real issue and not a temporary network error, please report it to %1, not KDE.", Discover.ResourcesModel.distroName)
0096                         wrapMode: Text.WordWrap
0097                     }
0098 
0099                     RowLayout {
0100                         Layout.fillWidth: true
0101 
0102                         QQC2.Button {
0103                             text: i18n("Copy Text")
0104                             icon.name: "edit-copy"
0105                             onClicked: {
0106                                 app.copyTextToClipboard(sheet.errorMessage);
0107                                 window.showPassiveNotification(i18nc("@info %1 is the name of the user's distro/OS", "Error message copied. Remember to report it to %1, not KDE!", Discover.ResourcesModel.distroName));
0108                             }
0109                         }
0110 
0111                         Item { Layout.fillWidth: true}
0112 
0113                         QQC2.Button {
0114                             text: i18nc("@action:button %1 is the name of the user's distro/OS", "Report Issue to %1", Discover.ResourcesModel.distroName)
0115                             icon.name: "tools-report-bug"
0116                             onClicked: {
0117                                 Qt.openUrlExternally(Discover.ResourcesModel.distroBugReportUrl())
0118                                 sheet.visible = false
0119                             }
0120                         }
0121                     }
0122                 }
0123             }
0124         }
0125 
0126         // Ensure that friendly message is shown if the user closes the sheet and
0127         // then opens it again
0128         onVisibleChanged: if (visible) {
0129             contentLoader.sourceComponent = friendlyMessageComponent;
0130         }
0131     }
0132 
0133     Discover.UpdateModel {
0134         id: updateModel
0135         backend: resourcesUpdatesModel
0136     }
0137 
0138     Kirigami.Action {
0139         id: updateAction
0140         text: page.unselected > 0 ? i18nc("@action:button as in, 'update the selected items' ", "Update Selected") : i18nc("@action:button as in, 'update all items'", "Update All")
0141         visible: updateModel.toUpdateCount
0142         icon.name: "update-none"
0143 
0144         function anyPageHeaderChildrenVisible() {
0145             return page.header.children.some(item => item?.visible && item instanceof Kirigami.InlineMessage);
0146         }
0147 
0148         enabled: !resourcesUpdatesModel.isProgressing && !Discover.ResourcesModel.isFetching && !anyPageHeaderChildrenVisible()
0149         onTriggered: resourcesUpdatesModel.updateAll()
0150     }
0151 
0152     header: ColumnLayout {
0153         id: errorsColumn
0154 
0155         spacing: Kirigami.Units.smallSpacing
0156 
0157         DiscoverInlineMessage {
0158             Layout.fillWidth: true
0159             Layout.margins: Kirigami.Units.smallSpacing
0160             inlineMessage: Discover.ResourcesModel.inlineMessage
0161         }
0162 
0163         Repeater {
0164             model: resourcesUpdatesModel.errorMessages
0165             delegate: Kirigami.InlineMessage {
0166                 id: inline
0167 
0168                 required property string modelData
0169 
0170                 Layout.fillWidth: true
0171                 Layout.margins: Kirigami.Units.smallSpacing
0172                 text: modelData
0173                 visible: true
0174                 type: Kirigami.MessageType.Error
0175                 onVisibleChanged: errorsColumn.childrenChanged()
0176 
0177                 actions: [
0178                     Kirigami.Action {
0179                         icon.name: "dialog-cancel"
0180                         text: i18n("Ignore")
0181                         onTriggered: {
0182                             inline.visible = false
0183                         }
0184                     }
0185                 ]
0186             }
0187         }
0188     }
0189 
0190     footer: ColumnLayout {
0191         width: parent.width
0192         spacing: 0
0193 
0194         QQC2.ScrollView {
0195             id: scv
0196             Layout.fillWidth: true
0197             Layout.preferredHeight: visible ? Kirigami.Units.gridUnit * 10 : 0
0198             visible: log.contents.length > 0
0199             QQC2.TextArea {
0200                 readOnly: true
0201                 text: log.contents
0202                 wrapMode: TextEdit.Wrap
0203 
0204                 cursorPosition: text.length - 1
0205                 font.family: "monospace"
0206 
0207                 Discover.ReadFile {
0208                     id: log
0209                     filter: ".*ALPM-SCRIPTLET\\] .*"
0210                     path: "/var/log/pacman.log"
0211                 }
0212             }
0213         }
0214 
0215         QQC2.ToolBar {
0216             id: footerToolbar
0217             Layout.fillWidth: true
0218             visible: (updateModel.totalUpdatesCount > 0 && resourcesUpdatesModel.isProgressing) || updateModel.hasUpdates
0219 
0220             position: QQC2.ToolBar.Footer
0221 
0222             contentItem: RowLayout {
0223                 QQC2.ToolButton {
0224                     enabled: page.unselected > 0 && updateAction.enabled && !Discover.ResourcesModel.isFetching
0225                     visible: updateModel.totalUpdatesCount > 1 && !resourcesUpdatesModel.isProgressing
0226                     icon.name: "edit-select-all"
0227                     text: i18n("Select All")
0228                     onClicked: { updateModel.checkAll(); }
0229                 }
0230 
0231                 QQC2.ToolButton {
0232                     enabled: page.unselected !== updateModel.totalUpdatesCount && updateAction.enabled && !Discover.ResourcesModel.isFetching
0233                     visible: updateModel.totalUpdatesCount > 1 && !resourcesUpdatesModel.isProgressing
0234                     icon.name: "edit-select-none"
0235                     text: i18n("Select None")
0236                     onClicked: { updateModel.uncheckAll(); }
0237                 }
0238 
0239                 QQC2.CheckBox {
0240                     id: rebootAtEnd
0241                     visible: resourcesUpdatesModel.needsReboot && resourcesUpdatesModel.isProgressing
0242                     text: i18n("Restart automatically after update has completed");
0243                 }
0244 
0245                 QQC2.Label {
0246                     Layout.fillWidth: true
0247                     Layout.rightMargin: Kirigami.Units.largeSpacing
0248                     horizontalAlignment: Text.AlignRight
0249                     text: i18n("Total size: %1", updateModel.updateSize)
0250                     elide: Text.ElideLeft
0251                 }
0252             }
0253         }
0254     }
0255 
0256     Kirigami.Action {
0257         id: cancelUpdateAction
0258         icon.name: "dialog-cancel"
0259         text: i18n("Cancel")
0260         enabled: resourcesUpdatesModel.transaction && resourcesUpdatesModel.transaction.isCancellable
0261         onTriggered: resourcesUpdatesModel.transaction.cancel()
0262     }
0263 
0264     readonly property int unselected: (updateModel.totalUpdatesCount - updateModel.toUpdateCount)
0265 
0266     supportsRefreshing: true
0267     onRefreshingChanged: {
0268         Discover.ResourcesModel.updateAction.triggered()
0269         refreshing = false
0270     }
0271 
0272     readonly property Item report: ColumnLayout {
0273         parent: page
0274         anchors.fill: parent
0275         anchors.margins: Kirigami.Units.largeSpacing * 2
0276         Item {
0277             Layout.fillHeight: true
0278             width: 1
0279         }
0280 
0281         Kirigami.Action {
0282             id: restartAction
0283             icon.name: "system-reboot"
0284             text: i18n("Restart Now")
0285             visible: false
0286             onTriggered: app.reboot()
0287         }
0288         Kirigami.LoadingPlaceholder {
0289             id: statusLabel
0290             icon.name: {
0291                 if (page.footerProgress === 0 && page.footerLabel !== "" && !page.busy) {
0292                     return "update-none"
0293                 } else {
0294                     return ""
0295                 }
0296             }
0297             text: page.footerLabel
0298             determinate: true
0299             progressBar.value: page.footerProgress
0300         }
0301 
0302         Item {
0303             Layout.fillHeight: true
0304             width: 1
0305         }
0306     }
0307     ListView {
0308         id: updatesView
0309         currentIndex: -1
0310         reuseItems: true
0311         clip: true
0312 
0313         model: DiscoverApp.QSortFilterProxyModel {
0314             sourceModel: updateModel
0315             sortRole: Discover.UpdateModel.SectionResourceProgressRole
0316         }
0317 
0318         section {
0319             property: "section"
0320             delegate: Kirigami.ListSectionHeader {
0321                 required property string section
0322 
0323                 width: updatesView.width
0324                 label: section
0325             }
0326         }
0327 
0328         delegate: QQC2.ItemDelegate {
0329             id: listItem
0330 
0331             // type: roles of Discover.UpdateModel
0332             required property var model
0333             required property int index
0334             required property bool extended
0335 
0336             width: updatesView.width
0337 
0338             highlighted: false
0339 
0340             onEnabledChanged: if (!enabled) {
0341                 model.extended = false;
0342             }
0343 
0344             visible: model.resourceState < Discover.AbstractBackendUpdater.Done
0345 
0346             Keys.onReturnPressed: event => {
0347                 itemChecked.clicked();
0348             }
0349             Keys.onPressed: event => {
0350                 if (event.key === Qt.Key_Alt) {
0351                     model.extended = true;
0352                 }
0353             }
0354             Keys.onReleased: event => {
0355                 if (event.key === Qt.Key_Alt) {
0356                     model.extended = false;
0357                 }
0358             }
0359 
0360             onExtendedChanged: if (extended) {
0361                 updateModel.fetchUpdateDetails(index)
0362             }
0363 
0364             contentItem: ColumnLayout {
0365                 spacing: Kirigami.Units.smallSpacing
0366 
0367                 RowLayout {
0368                     spacing: Kirigami.Units.smallSpacing
0369                     Layout.fillWidth: true
0370                     Layout.fillHeight: true
0371 
0372                     QQC2.CheckBox {
0373                         id: itemChecked
0374                         Layout.alignment: Qt.AlignVCenter
0375                         checked: listItem.model.checked === Qt.Checked
0376                         onClicked: listItem.model.checked = (listItem.model.checked === Qt.Checked ? Qt.Unchecked : Qt.Checked)
0377                         enabled: !resourcesUpdatesModel.isProgressing
0378                     }
0379 
0380                     Kirigami.Icon {
0381                         width: Kirigami.Units.gridUnit * 2
0382                         Layout.preferredHeight: width
0383                         source: listItem.model.decoration
0384                         smooth: true
0385                     }
0386 
0387                     ColumnLayout {
0388                         Layout.fillWidth: true
0389                         Layout.leftMargin: Kirigami.Units.smallSpacing
0390                         Layout.alignment: Qt.AlignVCenter
0391 
0392                         spacing: 0
0393 
0394                         // App name
0395                         Kirigami.Heading {
0396                             Layout.fillWidth: true
0397                             text: listItem.model.display
0398                             level: 3
0399                             elide: Text.ElideRight
0400                         }
0401 
0402                         // Version numbers
0403                         QQC2.Label {
0404                             Layout.fillWidth: true
0405                             elide: truncated ? Text.ElideLeft : Text.ElideRight
0406                             text: listItem.model.resource.upgradeText
0407                             opacity: listItem.hovered ? 0.8 : 0.6
0408                         }
0409                     }
0410 
0411                     LabelBackground {
0412                         Layout.minimumWidth: Kirigami.Units.gridUnit * 6
0413                         text: listItem.model.resourceState === 2 ? i18n("Installing") : listItem.model.size
0414 
0415                         progress: listItem.model.resourceProgress / 100
0416                     }
0417                 }
0418 
0419                 QQC2.Frame {
0420                     Layout.fillWidth: true
0421                     implicitHeight: view.contentHeight
0422                     visible: listItem.model.extended && listItem.model.changelog.length > 0
0423                     QQC2.Label {
0424                         id: view
0425                         anchors {
0426                             right: parent.right
0427                             left: parent.left
0428                         }
0429                         text: listItem.model.changelog
0430                         textFormat: Text.StyledText
0431                         wrapMode: Text.WordWrap
0432                         onLinkActivated: link => Qt.openUrlExternally(link)
0433 
0434                     }
0435                 }
0436 
0437                 RowLayout {
0438                     Layout.fillWidth: true
0439                     spacing: Kirigami.Units.smallSpacing
0440                     visible: listItem.model.extended
0441 
0442                     QQC2.Label {
0443                         Layout.leftMargin: Kirigami.Units.gridUnit
0444                         text: i18n("Update from:")
0445                     }
0446                     // Backend icon
0447                     Kirigami.Icon {
0448                         source: listItem.model.resource.sourceIcon
0449                         implicitWidth: Kirigami.Units.iconSizes.smallMedium
0450                         implicitHeight: Kirigami.Units.iconSizes.smallMedium
0451                     }
0452                     // Backend label and origin/remote
0453                     QQC2.Label {
0454                         Layout.fillWidth: true
0455                         text: listItem.model.resource.origin.length === 0 ? listItem.model.resource.backend.displayName
0456                                 : i18nc("%1 is the backend that provides this app, %2 is the specific repository or address within that backend","%1 (%2)",
0457                                         listItem.model.resource.backend.displayName, listItem.model.resource.origin)
0458                         elide: Text.ElideRight
0459                     }
0460 
0461                     QQC2.Button {
0462                         Layout.alignment: Qt.AlignRight
0463                         text: i18n("More Information…")
0464                         enabled: !resourcesUpdatesModel.isProgressing
0465                         onClicked: Navigation.openApplication(listItem.model.resource)
0466                     }
0467                 }
0468             }
0469 
0470             onClicked: {
0471                 model.extended = !model.extended
0472             }
0473         }
0474     }
0475 
0476     readonly property alias secSinceUpdate: resourcesUpdatesModel.secsToLastUpdate
0477     state:  ( resourcesUpdatesModel.isProgressing        ? "progressing"
0478             : Discover.ResourcesModel.isFetching         ? "fetching"
0479             : updateModel.hasUpdates                     ? "has-updates"
0480             : resourcesUpdatesModel.needsReboot          ? "reboot"
0481             : secSinceUpdate < 0                         ? "unknown"
0482             : secSinceUpdate === 0                       ? "now-uptodate"
0483             : secSinceUpdate < 1000 * 60 * 60 * 24       ? "uptodate"
0484             : secSinceUpdate < 1000 * 60 * 60 * 24 * 7   ? "medium"
0485             :                                              "low"
0486             )
0487 
0488     states: [
0489         State {
0490             name: "fetching"
0491             PropertyChanges { target: page; footerLabel: i18nc("@info", "Fetching updates…") }
0492             PropertyChanges { target: page; footerProgress: Discover.ResourcesModel.fetchingUpdatesProgress }
0493             PropertyChanges { target: page; busy: true }
0494             PropertyChanges { target: updatesView; opacity: 0 }
0495         },
0496         State {
0497             name: "progressing"
0498             PropertyChanges { target: page; supportsRefreshing: false }
0499             PropertyChanges { target: page; actions: [cancelUpdateAction] }
0500             PropertyChanges { target: statusLabel; visible: false }
0501         },
0502         State {
0503             name: "has-updates"
0504             PropertyChanges { target: page; title: i18nc("@info", "Updates") }
0505             // On mobile, we want "Update" to be the primary action so it's in
0506             // the center, but on desktop this feels a bit awkward and it would
0507             // be better to have "Update" be the right-most action
0508             PropertyChanges { target: page; actions: [ updateAction, refreshAction ] }
0509             PropertyChanges { target: statusLabel; visible: false }
0510         },
0511         State {
0512             name: "reboot"
0513             PropertyChanges { target: page; footerLabel: i18nc("@info", "Restart the system to complete the update process") }
0514             PropertyChanges { target: statusLabel; helpfulAction: restartAction }
0515             PropertyChanges { target: statusLabel; explanation: "" }
0516             PropertyChanges { target: statusLabel.progressBar; visible: false }
0517             StateChangeScript {
0518                 script: if (rebootAtEnd.checked && resourcesUpdatesModel.readyToReboot) {
0519                     app.rebootNow()
0520                 }
0521             }
0522         },
0523         State {
0524             name: "now-uptodate"
0525             PropertyChanges { target: page; footerLabel: i18nc("@info", "Up to date") }
0526             PropertyChanges { target: page; actions: [refreshAction] }
0527             PropertyChanges { target: statusLabel; explanation: "" }
0528             PropertyChanges { target: statusLabel.progressBar; visible: false }
0529         },
0530         State {
0531             name: "uptodate"
0532             PropertyChanges { target: page; footerLabel: i18nc("@info", "Up to date") }
0533             PropertyChanges { target: page; actions: [refreshAction] }
0534             PropertyChanges { target: statusLabel; explanation: "" }
0535             PropertyChanges { target: statusLabel.progressBar; visible: false }
0536         },
0537         State {
0538             name: "medium"
0539             PropertyChanges { target: page; title: i18nc("@info", "Up to date") }
0540             PropertyChanges { target: page; actions: [refreshAction] }
0541             PropertyChanges { target: statusLabel; explanation: "" }
0542             PropertyChanges { target: statusLabel.progressBar; visible: false }
0543         },
0544         State {
0545             name: "low"
0546             PropertyChanges { target: page; title: i18nc("@info", "Should check for updates") }
0547             PropertyChanges { target: page; actions: [refreshAction] }
0548             PropertyChanges { target: statusLabel; explanation: "" }
0549             PropertyChanges { target: statusLabel.progressBar; visible: false }
0550         },
0551         State {
0552             name: "unknown"
0553             PropertyChanges { target: page; title: i18nc("@info", "Time of last update unknown") }
0554             PropertyChanges { target: page; actions: [refreshAction] }
0555             PropertyChanges { target: statusLabel; explanation: "" }
0556             PropertyChanges { target: statusLabel.progressBar; visible: false }
0557         }
0558     ]
0559 }