1
0
mirror of https://github.com/AvengeMedia/DankMaterialShell.git synced 2026-08-05 21:18:30 -04:00

feat(dms updater): add support for ignoring specific packages during system updates

- Added UI component & popout settings to manage ignored packages
- Added CLI support available in danklinux docs

Fixes: #2827
Closes: #2344, #1741
Port 1.5
This commit is contained in:
purian23
2026-07-13 12:02:13 -04:00
parent 197d17ac4e
commit e4657aa5f9
19 changed files with 674 additions and 40 deletions
@@ -76,6 +76,47 @@ DankPopout {
readonly property bool hasTerminalBackend: (SystemUpdateService.backends || []).some(b => b.runsInTerminal === true)
property int nowUnix: Math.floor(Date.now() / 1000)
Connections {
target: systemUpdatePopout
function onShouldBeVisibleChanged() {
if (systemUpdatePopout.shouldBeVisible) {
updaterPanel.nowUnix = Math.floor(Date.now() / 1000);
}
}
}
function distroLabel() {
const pretty = (SystemUpdateService.distributionPretty || "").trim();
if (pretty) {
return pretty.split(/\s+/)[0];
}
const id = (SystemUpdateService.distribution || "").trim();
if (id) {
return id.charAt(0).toUpperCase() + id.slice(1);
}
return I18n.tr("System");
}
function lastCheckedText() {
const last = SystemUpdateService.lastCheckUnix;
if (!last) {
return "";
}
const delta = Math.max(0, nowUnix - last);
if (delta < 90) {
return I18n.tr("checked just now");
}
if (delta < 3600) {
return I18n.tr("checked %1m ago").arg(Math.round(delta / 60));
}
if (delta < 86400) {
return I18n.tr("checked %1h ago").arg(Math.round(delta / 3600));
}
return I18n.tr("checked %1d ago").arg(Math.round(delta / 86400));
}
Keys.onPressed: event => {
if (event.key === Qt.Key_Escape) {
systemUpdatePopout.close();
@@ -171,8 +212,17 @@ DankPopout {
anchors.topMargin: Theme.spacingS
visible: SystemUpdateService.backends.length > 0 && !SystemUpdateService.isUpgrading
text: {
const names = (SystemUpdateService.backends || []).map(b => b.displayName).join(", ");
return I18n.tr("Backends: %1").arg(names);
const kinds = [];
for (const b of SystemUpdateService.backends || []) {
const label = b.repo === "flatpak" ? I18n.tr("Flatpak") : I18n.tr("System");
if (!kinds.includes(label)) {
kinds.push(label);
}
}
const distro = updaterPanel.distroLabel();
const checked = updaterPanel.lastCheckedText();
const base = `${distro}: ${kinds.join(", ")}`;
return checked ? `${base} · ${checked}` : base;
}
font.pixelSize: Theme.fontSizeSmall
color: Theme.surfaceVariantText
@@ -180,6 +230,21 @@ DankPopout {
elide: Text.ElideRight
}
StyledText {
id: hiddenRow
anchors.left: parent.left
anchors.right: parent.right
anchors.top: backendsRow.visible ? backendsRow.bottom : header.bottom
anchors.leftMargin: Theme.spacingL
anchors.rightMargin: Theme.spacingL
anchors.topMargin: Theme.spacingXS
visible: SystemUpdateService.hiddenUpdateCount > 0 && !SystemUpdateService.isUpgrading
text: I18n.tr("%1 hidden (AUR disabled or ignored)").arg(SystemUpdateService.hiddenUpdateCount)
font.pixelSize: Theme.fontSizeSmall
color: Theme.surfaceVariantText
wrapMode: Text.WordWrap
}
Row {
id: buttonsRow
anchors.left: parent.left
@@ -275,7 +340,7 @@ DankPopout {
id: bodyArea
anchors.left: parent.left
anchors.right: parent.right
anchors.top: backendsRow.visible ? backendsRow.bottom : header.bottom
anchors.top: hiddenRow.visible ? hiddenRow.bottom : (backendsRow.visible ? backendsRow.bottom : header.bottom)
anchors.bottom: buttonsRow.top
anchors.leftMargin: Theme.spacingL
anchors.rightMargin: Theme.spacingL
@@ -294,7 +359,8 @@ DankPopout {
text: {
switch (true) {
case SystemUpdateService.hasError:
return I18n.tr("Failed: %1").arg(SystemUpdateService.errorMessage);
const msg = I18n.tr("Failed: %1").arg(SystemUpdateService.errorMessage);
return SystemUpdateService.errorHint ? `${msg}\n\n${SystemUpdateService.errorHint}` : msg;
case !SystemUpdateService.helperAvailable:
return I18n.tr("No supported package manager found.");
case SystemUpdateService.isChecking:
@@ -318,13 +384,18 @@ DankPopout {
model: SystemUpdateService.availableUpdates
delegate: Rectangle {
id: packageRow
width: ListView.view.width
height: 48
radius: Theme.cornerRadius
color: packageMouseArea.containsMouse ? Theme.primaryHoverLight : Theme.withAlpha(Theme.primaryHoverLight, 0)
color: rowHoverHandler.hovered ? Theme.primaryHoverLight : Theme.withAlpha(Theme.primaryHoverLight, 0)
required property var modelData
HoverHandler {
id: rowHoverHandler
}
Row {
anchors.left: parent.left
anchors.right: parent.right
@@ -350,7 +421,7 @@ DankPopout {
Column {
anchors.verticalCenter: parent.verticalCenter
width: parent.width - 64 - Theme.spacingS
width: parent.width - 64 - Theme.spacingS * 2 - 28
spacing: Theme.spacingXXS
StyledText {
@@ -395,13 +466,26 @@ DankPopout {
id: packageMouseArea
anchors.fill: parent
hoverEnabled: true
cursorShape: modelData.changelogUrl ? Qt.PointingHandCursor : Qt.ArrowCursor
cursorShape: packageRow.modelData.changelogUrl ? Qt.PointingHandCursor : Qt.ArrowCursor
onClicked: {
if (modelData.changelogUrl) {
Qt.openUrlExternally(modelData.changelogUrl);
if (packageRow.modelData.changelogUrl) {
Qt.openUrlExternally(packageRow.modelData.changelogUrl);
}
}
}
DankActionButton {
anchors.right: packageRow.right
anchors.rightMargin: Theme.spacingS
anchors.verticalCenter: packageRow.verticalCenter
buttonSize: 24
iconName: "visibility_off"
iconSize: 16
iconColor: Theme.surfaceVariantText
visible: rowHoverHandler.hovered
tooltipText: I18n.tr("Ignore this package")
onClicked: SystemUpdateService.ignorePackage(packageRow.modelData.name)
}
}
}
@@ -30,13 +30,20 @@ Item {
}
]
readonly property string customIntervalLabel: I18n.tr("Custom")
property bool customIntervalSelected: false
Component.onCompleted: {
customIntervalSelected = !intervalOptions.some(o => o.seconds === SettingsData.updaterIntervalSeconds);
}
function intervalLabelFor(seconds) {
for (const opt of intervalOptions) {
if (opt.seconds === seconds) {
return opt.label;
}
}
return intervalOptions[1].label;
return customIntervalLabel;
}
function intervalSecondsFor(label) {
@@ -84,15 +91,71 @@ Item {
SettingsDropdownRow {
text: I18n.tr("Check interval")
description: I18n.tr("How often the server polls for new updates.")
options: root.intervalOptions.map(o => o.label)
currentValue: root.intervalLabelFor(SettingsData.updaterIntervalSeconds)
options: root.intervalOptions.map(o => o.label).concat([root.customIntervalLabel])
currentValue: root.customIntervalSelected ? root.customIntervalLabel : root.intervalLabelFor(SettingsData.updaterIntervalSeconds)
onValueChanged: label => {
if (label === root.customIntervalLabel) {
root.customIntervalSelected = true;
return;
}
root.customIntervalSelected = false;
const secs = root.intervalSecondsFor(label);
SettingsData.set("updaterIntervalSeconds", secs);
SystemUpdateService.setInterval(secs);
}
}
FocusScope {
width: parent.width - Theme.spacingM * 2
height: customIntervalColumn.implicitHeight
anchors.left: parent.left
anchors.leftMargin: Theme.spacingM
visible: root.customIntervalSelected
Column {
id: customIntervalColumn
width: parent.width
spacing: Theme.spacingXS
StyledText {
text: I18n.tr("Custom interval in minutes (minimum 5)")
font.pixelSize: Theme.fontSizeSmall
color: Theme.surfaceVariantText
}
DankTextField {
id: customIntervalField
width: parent.width
placeholderText: "30"
backgroundColor: Theme.surfaceContainerHighest
normalBorderColor: Theme.outlineMedium
focusedBorderColor: Theme.primary
Component.onCompleted: {
text = Math.round(SettingsData.updaterIntervalSeconds / 60).toString();
}
onTextEdited: {
const minutes = parseInt(text, 10);
if (isNaN(minutes) || minutes < 5) {
return;
}
const secs = minutes * 60;
SettingsData.set("updaterIntervalSeconds", secs);
SystemUpdateService.setInterval(secs);
}
MouseArea {
anchors.fill: parent
onPressed: mouse => {
customIntervalField.forceActiveFocus();
mouse.accepted = false;
}
}
}
}
}
SettingsToggleRow {
text: I18n.tr("Check on startup")
description: I18n.tr("When enabled, checks updates on startup. When disabled, only the interval above or a manual refresh runs a check.")
@@ -119,6 +182,145 @@ Item {
TerminalPickerRow {}
}
SettingsCard {
id: ignoredPackagesCard
width: parent.width
iconName: "inventory_2"
title: I18n.tr("Ignored Packages")
settingKey: "systemUpdaterIgnoredPackages"
tags: ["system", "update", "package", "ignore"]
function addIgnoredPackage() {
const name = newIgnoredPackageField.text.trim();
if (name === "") {
return;
}
if (!/^[A-Za-z0-9@._+:-]+$/.test(name)) {
ignoredPackageError.visible = true;
return;
}
ignoredPackageError.visible = false;
SystemUpdateService.ignorePackage(name);
newIgnoredPackageField.text = "";
}
Column {
width: parent.width
spacing: Theme.spacingS
StyledText {
width: parent.width
text: {
if (SettingsData.updaterUseCustomCommand) {
return I18n.tr("Ignored packages only apply to the built-in updater. Your custom command controls its own exclusions.");
}
return (SettingsData.updaterIgnoredPackages || []).length > 0 ? I18n.tr("Ignored packages are hidden from the updater and skipped by 'Update All'.") : I18n.tr("No packages ignored. Add one here or hover an update in the popout and click the hide button.");
}
font.pixelSize: Theme.fontSizeSmall
color: Theme.surfaceVariantText
wrapMode: Text.WordWrap
bottomPadding: Theme.spacingS
}
Row {
width: parent.width
spacing: Theme.spacingS
DankTextField {
id: newIgnoredPackageField
width: parent.width - addIgnoredBtn.width - Theme.spacingS
height: 36
placeholderText: I18n.tr("Package name (e.g., docker)")
font.pixelSize: Theme.fontSizeSmall
onAccepted: ignoredPackagesCard.addIgnoredPackage()
onTextEdited: ignoredPackageError.visible = false
}
DankActionButton {
id: addIgnoredBtn
buttonSize: 36
iconName: "add"
iconSize: 20
backgroundColor: Theme.primary
iconColor: Theme.onPrimary
tooltipText: I18n.tr("Ignore package")
onClicked: ignoredPackagesCard.addIgnoredPackage()
}
}
StyledText {
id: ignoredPackageError
visible: false
text: I18n.tr("Invalid package name — letters, digits and @._+:- only.")
font.pixelSize: Theme.fontSizeSmall
color: Theme.error
}
SettingsCard {
width: parent.width
iconName: "visibility_off"
title: I18n.tr("Ignored (%1)").arg((SettingsData.updaterIgnoredPackages || []).length)
collapsible: true
expanded: false
visible: (SettingsData.updaterIgnoredPackages || []).length > 0
color: Theme.withAlpha(Theme.surfaceContainer, 0.5)
Repeater {
model: SettingsData.updaterIgnoredPackages
delegate: Rectangle {
required property string modelData
required property int index
width: parent.width
height: 40
radius: Theme.cornerRadius
color: Theme.withAlpha(Theme.surfaceContainer, 0.5)
DankIcon {
id: ignoredIcon
anchors.left: parent.left
anchors.leftMargin: Theme.spacingM
anchors.verticalCenter: parent.verticalCenter
name: "visibility_off"
size: 18
color: Theme.surfaceVariantText
}
StyledText {
anchors.left: ignoredIcon.right
anchors.leftMargin: Theme.spacingS
anchors.right: removeIgnoredBtn.left
anchors.verticalCenter: parent.verticalCenter
text: parent.modelData
font.pixelSize: Theme.fontSizeSmall
color: Theme.surfaceText
elide: Text.ElideRight
}
DankActionButton {
id: removeIgnoredBtn
anchors.right: parent.right
anchors.rightMargin: Theme.spacingXS
anchors.verticalCenter: parent.verticalCenter
buttonSize: 32
iconName: "delete"
iconSize: 18
iconColor: Theme.error
backgroundColor: "transparent"
tooltipText: I18n.tr("Stop ignoring %1").arg(parent.modelData)
onClicked: {
const list = (SettingsData.updaterIgnoredPackages || []).slice();
list.splice(parent.index, 1);
SettingsData.set("updaterIgnoredPackages", list);
}
}
}
}
}
}
}
SettingsCard {
width: parent.width
iconName: "tune"