diff --git a/Helpers/Updates.qml b/Helpers/Updates.qml index fb1bbab..cbb83bd 100644 --- a/Helpers/Updates.qml +++ b/Helpers/Updates.qml @@ -11,10 +11,13 @@ Singleton { id: root property int availableUpdates: 0 + property string cmd: "" property bool commandReady property bool loaded property double now: Date.now() property var updates: ({}) + property bool updating + property string updatingPackage: "" function formatUpdateTime(timestamp) { const diffMs = root.now - timestamp; @@ -34,6 +37,22 @@ Singleton { return Qt.formatDateTime(new Date(timestamp), "dd hh:mm"); } + function performPackageUpdate(pkg: string): void { + if (root.cmd === "pacman") + pkgUpdateProc.command = ["pkexec", root.cmd, "--noconfirm", "-Sy", pkg]; + else + pkgUpdateProc.command = [root.cmd, "--noconfirm", "--sudo", "pkexec", "-Sy", pkg]; + pkgUpdateProc.running = true; + } + + function performSystemUpdate(): void { + if (root.cmd === "pacman") + sysUpdateProc.command = ["pkexec", root.cmd, "--noconfirm", "-Syu"]; + else + sysUpdateProc.command = [root.cmd, "--noconfirm", "--sudo", "pkexec", "-Syu"]; + sysUpdateProc.running = true; + } + onUpdatesChanged: { if (!root.loaded) return; @@ -92,6 +111,28 @@ Singleton { } } + Process { + id: updateCmdDetect + + command: ["sh", "-c", "command -v yay || command -v paru"] + running: true + + stdout: StdioCollector { + onStreamFinished: { + const cmd = this.text.trim(); + let helper; + + if (cmd.length > 0) { + helper = cmd.split("/").pop(); + } else { + helper = "pacman"; + } + + root.cmd = helper; + } + } + } + Process { id: updatesProc @@ -115,6 +156,44 @@ Singleton { } } + Process { + id: sysUpdateProc + + command: [] + running: false + + stdout: StdioCollector { + onStreamFinished: { + root.updating = false; + } + } + + onRunningChanged: { + if (running) + root.updating = true; + } + } + + Process { + id: pkgUpdateProc + + command: [] + running: false + + stdout: StdioCollector { + onStreamFinished: { + root.updating = false; + } + } + + onRunningChanged: { + if (running) { + root.updatingPackage = command[command.length - 1]; + root.updating = true; + } + } + } + Timer { id: saveTimer diff --git a/Modules/Settings/Categories/SystemUpdates.qml b/Modules/Settings/Categories/SystemUpdates.qml index 4114863..c3b0ba6 100644 --- a/Modules/Settings/Categories/SystemUpdates.qml +++ b/Modules/Settings/Categories/SystemUpdates.qml @@ -6,127 +6,192 @@ import qs.Helpers import qs.Components import qs.Modules.Settings.Controls -SettingsPage { - SettingsSection { - sectionId: "Updates" +ColumnLayout { + id: root - SettingsHeader { - name: "System updates" + RowLayout { + Layout.fillWidth: true + Layout.margins: Appearance.padding.large + spacing: Appearance.spacing.large + + MaterialIcon { + font.pointSize: Appearance.font.size.larger * 4 + text: "update" } - CustomListView { - id: view + ColumnLayout { + CustomText { + font.pointSize: Appearance.font.size.large * 2 + text: "System updates" + } - readonly property int itemHeight: 50 + Appearance.padding.smaller * 2 + RowLayout { + id: row - Layout.fillWidth: true - Layout.preferredHeight: contentHeight - spacing: Appearance.spacing.normal - - delegate: CustomRect { - id: update - - required property var modelData - readonly property list sections: modelData.update.split(" ") + // Layout.fillHeight: true + Layout.fillWidth: true // anchors.left: parent.left + // anchors.margins: Appearance.padding.small // anchors.right: parent.right - color: DynamicColors.tPalette.m3surfaceContainer - implicitHeight: view.itemHeight - implicitWidth: parent.width - radius: Appearance.rounding.small - Appearance.padding.small + // anchors.verticalCenter: parent.verticalCenter - RowLayout { - anchors.fill: parent - anchors.leftMargin: Appearance.padding.smaller - anchors.rightMargin: Appearance.padding.smaller + CustomText { + id: text - MaterialIcon { - font.pointSize: Appearance.font.size.large * 2 - text: "package_2" - } + Layout.alignment: Qt.AlignLeft | Qt.AlignVCenter + Layout.fillWidth: true + font.pointSize: Appearance.font.size.larger + text: `${Updates.availableUpdates} available updates` + } - ColumnLayout { - Layout.fillWidth: true - - CustomText { - Layout.fillWidth: true - Layout.preferredHeight: 25 - elide: Text.ElideRight - font.pointSize: Appearance.font.size.large - text: update.sections[0] - } - - CustomText { - Layout.fillWidth: true - color: DynamicColors.palette.m3onSurfaceVariant - text: Updates.formatUpdateTime(update.modelData.timestamp) - } - } + CustomRect { + Layout.preferredHeight: 40 + Layout.preferredWidth: 150 + color: Updates.updating ? DynamicColors.layer(DynamicColors.palette.m3outline, 2) : DynamicColors.palette.m3primary + radius: Appearance.rounding.full RowLayout { - Layout.fillHeight: true - Layout.preferredWidth: 500 - - MarqueeText { - id: versionFrom - - Layout.fillHeight: true - Layout.preferredWidth: 225 - animate: true - color: DynamicColors.palette.m3tertiary - font.pointSize: Appearance.font.size.large - horizontalAlignment: Text.AlignHCenter - marqueeEnabled: true - pauseMs: 4000 - text: update.sections[1] - width: 225 - } + anchors.centerIn: parent MaterialIcon { - Layout.fillHeight: true - color: DynamicColors.palette.m3secondary - font.pointSize: Appearance.font.size.extraLarge - horizontalAlignment: Text.AlignHCenter - text: "arrow_right_alt" - verticalAlignment: Text.AlignVCenter + animate: true + color: DynamicColors.palette.m3onPrimary + font.pointSize: Appearance.font.size.large + text: Updates.updating ? "update" : "download" } - MarqueeText { - id: versionTo - - Layout.fillHeight: true - Layout.preferredWidth: 225 - animate: true - color: DynamicColors.palette.m3primary - font.pointSize: Appearance.font.size.large - horizontalAlignment: Text.AlignHCenter - marqueeEnabled: true - pauseMs: 4000 - text: update.sections[3] - width: 225 + CustomText { + color: Updates.updating ? DynamicColors.palette.m3onSurface : DynamicColors.palette.m3onPrimary + text: "Update all" } } - IconButton { - Layout.preferredHeight: width - icon: "download" + StateLayer { + color: DynamicColors.palette.m3onPrimary + disabled: Updates.updating - onClicked: { - Quickshell.execDetached(["pkexec", "yay", "-Sy", update.sections[0], "--noconfirm"]); - } + onClicked: Updates.performSystemUpdate() } } } - model: ScriptModel { - id: script + } + } - objectProp: "update" - values: Object.entries(Updates.updates).sort((a, b) => b[1] - a[1]).map(([update, timestamp]) => ({ - update, - timestamp - })) + CustomListView { + id: view + + readonly property int itemHeight: 50 + Appearance.padding.smaller * 2 + + Layout.fillHeight: true + Layout.fillWidth: true + clip: true + contentHeight: height + spacing: Appearance.spacing.normal + + delegate: CustomRect { + id: update + + required property var modelData + readonly property list sections: modelData.update.split(" ") + + // anchors.left: parent.left + // anchors.right: parent.right + color: DynamicColors.tPalette.m3surfaceContainer + implicitHeight: view.itemHeight + implicitWidth: parent.width + radius: Appearance.rounding.small - Appearance.padding.small + + RowLayout { + anchors.fill: parent + anchors.leftMargin: Appearance.padding.smaller + anchors.rightMargin: Appearance.padding.smaller + + MaterialIcon { + font.pointSize: Appearance.font.size.large * 2 + text: "package_2" + } + + ColumnLayout { + Layout.fillWidth: true + + CustomText { + Layout.fillWidth: true + Layout.preferredHeight: 25 + elide: Text.ElideRight + font.pointSize: Appearance.font.size.large + text: update.sections[0] + } + + CustomText { + Layout.fillWidth: true + color: DynamicColors.palette.m3onSurfaceVariant + text: Updates.formatUpdateTime(update.modelData.timestamp) + } + } + + RowLayout { + Layout.fillHeight: true + Layout.preferredWidth: 500 + + MarqueeText { + id: versionFrom + + Layout.fillHeight: true + Layout.preferredWidth: 225 + animate: true + color: DynamicColors.palette.m3tertiary + font.pointSize: Appearance.font.size.large + horizontalAlignment: Text.AlignHCenter + marqueeEnabled: true + pauseMs: 4000 + text: update.sections[1] + width: 225 + } + + MaterialIcon { + Layout.fillHeight: true + color: DynamicColors.palette.m3secondary + font.pointSize: Appearance.font.size.extraLarge + horizontalAlignment: Text.AlignHCenter + text: "arrow_right_alt" + verticalAlignment: Text.AlignVCenter + } + + MarqueeText { + id: versionTo + + Layout.fillHeight: true + Layout.preferredWidth: 225 + animate: true + color: DynamicColors.palette.m3primary + font.pointSize: Appearance.font.size.large + horizontalAlignment: Text.AlignHCenter + marqueeEnabled: true + pauseMs: 4000 + text: update.sections[3] + width: 225 + } + } + + IconButton { + Layout.preferredHeight: width + icon: "download" + + onClicked: { + Updates.performPackageUpdate(update.sections[0]); + } + } } } + model: ScriptModel { + id: script + + objectProp: "update" + values: Object.entries(Updates.updates).sort((a, b) => b[1] - a[1]).map(([update, timestamp]) => ({ + update, + timestamp + })) + } } }