mirror of
https://github.com/AvengeMedia/DankMaterialShell.git
synced 2026-10-15 15:54:00 -04:00
813 lines
37 KiB
QML
813 lines
37 KiB
QML
pragma ComponentBehavior: Bound
|
|
|
|
import QtQuick
|
|
import qs.Common
|
|
import qs.Services
|
|
import qs.DCommon.Widgets
|
|
import qs.Modals.Common
|
|
import qs.Modules.Settings.Widgets
|
|
import qs.Modules.SystemUpdate
|
|
import "../../Common/Format.js" as Format
|
|
|
|
Item {
|
|
id: root
|
|
|
|
property var parentModal: null
|
|
|
|
LayoutMirroring.enabled: I18n.isRtl
|
|
LayoutMirroring.childrenInherit: true
|
|
|
|
Ref {
|
|
service: SystemUpdateService
|
|
modules: ["releases"]
|
|
}
|
|
|
|
readonly property var intervalOptions: [
|
|
{
|
|
label: I18n.tr("Every %1", "update check interval option, %1 is a duration such as 30 minutes").arg(I18n.duration(900)),
|
|
seconds: 900
|
|
},
|
|
{
|
|
label: I18n.tr("Every %1", "update check interval option, %1 is a duration such as 30 minutes").arg(I18n.duration(1800)),
|
|
seconds: 1800
|
|
},
|
|
{
|
|
label: I18n.tr("Every hour"),
|
|
seconds: 3600
|
|
},
|
|
{
|
|
label: I18n.tr("Every %1", "update check interval option, %1 is a duration such as 30 minutes").arg(I18n.duration(14400)),
|
|
seconds: 14400
|
|
},
|
|
{
|
|
label: I18n.tr("Once a day"),
|
|
seconds: 86400
|
|
}
|
|
]
|
|
|
|
readonly property string customIntervalLabel: I18n.tr("Custom")
|
|
property bool customIntervalSelected: false
|
|
property bool packagesExpanded: false
|
|
property int nowUnix: Math.floor(Date.now() / 1000)
|
|
|
|
readonly property int packageListCap: 60
|
|
readonly property int logTailLines: 20
|
|
readonly property real logViewHeight: Theme.listItemHeight * 5
|
|
readonly property bool upgradeRunsInTerminal: SystemUpdateService.useCustomCommand || (SystemUpdateService.backends || []).some(b => b.runsInTerminal === true)
|
|
readonly property bool upgradeInWindow: SettingsData.updaterUpgradeInWindow && !upgradeRunsInTerminal
|
|
readonly property int systemCount: SystemUpdateService.systemUpdates.length
|
|
readonly property int flatpakCount: SystemUpdateService.systemUpdates.filter(p => p.repo === "flatpak").length
|
|
readonly property bool busy: SystemUpdateService.isChecking || SystemUpdateService.isUpgrading
|
|
// A refresh clears the log, so a log next to an error means the upgrade itself failed.
|
|
readonly property bool upgradeFailed: SystemUpdateService.hasError && !busy && (SystemUpdateService.recentLog || []).length > 0
|
|
readonly property var installablePackages: SystemUpdateService.availableUpdates.filter(p => SettingsData.updaterIncludeFlatpak || p.repo !== "flatpak")
|
|
readonly property bool anythingToInstall: installablePackages.length > 0 && SystemUpdateService.helperAvailable
|
|
readonly property var notesRelease: SystemUpdateService.notesRelease
|
|
readonly property int serviceLastCheckUnix: SystemUpdateService.lastCheckUnix
|
|
|
|
onServiceLastCheckUnixChanged: nowUnix = Math.floor(Date.now() / 1000)
|
|
|
|
Component.onCompleted: {
|
|
customIntervalSelected = !intervalOptions.some(o => o.seconds === SettingsData.updaterIntervalSeconds);
|
|
nowUnix = Math.floor(Date.now() / 1000);
|
|
}
|
|
|
|
readonly property var notifyOptions: [
|
|
{
|
|
label: I18n.tr("Every check", "update notification frequency option, notify on every check"),
|
|
seconds: 0
|
|
},
|
|
{
|
|
label: I18n.tr("Every %1", "update check interval option, %1 is a duration such as 30 minutes").arg(I18n.duration(3600)),
|
|
seconds: 3600
|
|
},
|
|
{
|
|
label: I18n.tr("Every %1", "update check interval option, %1 is a duration such as 30 minutes").arg(I18n.duration(4 * 3600)),
|
|
seconds: 4 * 3600
|
|
},
|
|
{
|
|
label: I18n.tr("Once a day"),
|
|
seconds: 86400
|
|
},
|
|
{
|
|
label: I18n.tr("Once a week", "update notification frequency option"),
|
|
seconds: 7 * 86400
|
|
}
|
|
]
|
|
|
|
function intervalLabelFor(seconds) {
|
|
for (const opt of intervalOptions) {
|
|
if (opt.seconds === seconds)
|
|
return opt.label;
|
|
}
|
|
return customIntervalLabel;
|
|
}
|
|
|
|
function intervalSecondsFor(label) {
|
|
for (const opt of intervalOptions) {
|
|
if (opt.label === label)
|
|
return opt.seconds;
|
|
}
|
|
return 86400;
|
|
}
|
|
|
|
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", "system update last check time, %1 is minutes").arg(Math.round(delta / 60));
|
|
if (delta < 86400)
|
|
return I18n.tr("checked %1h ago", "system update last check time, %1 is hours").arg(Math.round(delta / 3600));
|
|
return I18n.tr("checked %1d ago", "system update last check time, %1 is days").arg(Math.round(delta / 86400));
|
|
}
|
|
|
|
function countText(count) {
|
|
return count === 1 ? I18n.tr("%1 update", "singular, %1 is 1, available system update count").arg(count) : I18n.tr("%1 updates", "plural, %1 is a count of available system updates").arg(count);
|
|
}
|
|
|
|
function installMethodLabel() {
|
|
switch (SystemUpdateService.shellInstallMethod) {
|
|
case "pacman":
|
|
case "rpm":
|
|
case "dpkg":
|
|
case "xbps":
|
|
return SystemUpdateService.shellPackageName || SystemUpdateService.shellInstallMethod;
|
|
case "nix":
|
|
return "Nix";
|
|
default:
|
|
return "";
|
|
}
|
|
}
|
|
|
|
function channelLabel() {
|
|
switch (SystemUpdateService.shellChannel) {
|
|
case "stable":
|
|
return I18n.tr("Stable", "release channel");
|
|
case "git":
|
|
return I18n.tr("Git", "release channel that follows the master branch");
|
|
default:
|
|
return I18n.tr("Unknown");
|
|
}
|
|
}
|
|
|
|
function statusTitle() {
|
|
switch (true) {
|
|
case !SystemUpdateService.sysupdateAvailable:
|
|
return I18n.tr("Unavailable");
|
|
case SystemUpdateService.isUpgrading:
|
|
return I18n.tr("Upgrading...", "system update popout status while packages upgrade");
|
|
case SystemUpdateService.isChecking:
|
|
return I18n.tr("Checking for updates...");
|
|
case SystemUpdateService.hasError:
|
|
return I18n.tr("Failed: %1", "system update error status, %1 is the error message").arg(SystemUpdateService.errorMessage);
|
|
case !SystemUpdateService.helperAvailable:
|
|
return I18n.tr("No supported package manager found.");
|
|
case SystemUpdateService.shellUpdateAvailable:
|
|
{
|
|
const shell = I18n.tr("DMS %1 available", "software updates hero, %1 is the new DMS version").arg(SystemUpdateService.shellUpdateVersion);
|
|
return root.systemCount > 0 ? shell + " · " + root.countText(root.systemCount) : shell;
|
|
}
|
|
case root.systemCount > 0:
|
|
return root.countText(root.systemCount);
|
|
default:
|
|
return I18n.tr("Up to date");
|
|
}
|
|
}
|
|
|
|
function statusSubtitle() {
|
|
if (SystemUpdateService.rebootRecommended)
|
|
return I18n.tr("Reboot recommended", "chip shown after a kernel or systemd upgrade") + " · " + SystemUpdateService.rebootPackages.join(", ");
|
|
if (SystemUpdateService.restartPending)
|
|
return I18n.tr("Requires restart");
|
|
return root.lastCheckedText();
|
|
}
|
|
|
|
readonly property bool offerReboot: SystemUpdateService.rebootRecommended && !SystemUpdateService.isUpgrading && !root.anythingToInstall
|
|
readonly property bool needsRestart: SystemUpdateService.restartPending || SystemUpdateService.rebootRecommended
|
|
readonly property bool primaryIsCheck: !SystemUpdateService.isUpgrading && !root.anythingToInstall && !root.needsRestart
|
|
|
|
function primaryLabel() {
|
|
switch (true) {
|
|
case SystemUpdateService.isUpgrading:
|
|
return I18n.tr("Cancel");
|
|
case root.anythingToInstall:
|
|
return I18n.tr("Install", "install action button");
|
|
case root.offerReboot:
|
|
return I18n.tr("Reboot", "verb, power menu action");
|
|
case SystemUpdateService.restartPending:
|
|
return I18n.tr("Restart DMS");
|
|
default:
|
|
return I18n.tr("Check for updates");
|
|
}
|
|
}
|
|
|
|
function primaryIcon() {
|
|
switch (true) {
|
|
case SystemUpdateService.isUpgrading:
|
|
return "stop";
|
|
case root.anythingToInstall:
|
|
return "system_update_alt";
|
|
case root.offerReboot:
|
|
case SystemUpdateService.restartPending:
|
|
return "restart_alt";
|
|
default:
|
|
return "refresh";
|
|
}
|
|
}
|
|
|
|
function primaryAction() {
|
|
switch (true) {
|
|
case SystemUpdateService.isUpgrading:
|
|
SystemUpdateService.cancelUpdates();
|
|
return;
|
|
case root.anythingToInstall:
|
|
installConfirm.showWithOptions({
|
|
title: I18n.tr("Install system updates?", "confirmation dialog title before running the system upgrade"),
|
|
message: root.installSummary(),
|
|
confirmText: I18n.tr("Install", "install action button"),
|
|
onConfirm: () => root.runUpdateAll()
|
|
});
|
|
return;
|
|
case root.offerReboot:
|
|
installConfirm.showWithOptions({
|
|
title: I18n.tr("Reboot now?", "confirmation dialog title, reboot to finish a system update"),
|
|
message: I18n.tr("Save your work first. Open apps will close.", "reboot confirmation after a system update"),
|
|
confirmText: I18n.tr("Reboot", "verb, power menu action"),
|
|
onConfirm: () => SessionService.reboot()
|
|
});
|
|
return;
|
|
case SystemUpdateService.restartPending:
|
|
SystemUpdateService.restartShell();
|
|
return;
|
|
default:
|
|
SystemUpdateService.checkForUpdates();
|
|
}
|
|
}
|
|
|
|
function installSummary() {
|
|
const pkgs = root.installablePackages;
|
|
const bytes = pkgs.reduce((sum, p) => sum + (p.sizeBytes || 0), 0);
|
|
const via = SystemUpdateService.useCustomCommand ? SettingsData.updaterCustomCommand.trim().split(/\s+/)[0] : (SystemUpdateService.backends || []).filter(b => pkgs.some(p => p.backend === b.id)).map(b => b.displayName).join(", ");
|
|
let text;
|
|
if (bytes > 0)
|
|
text = I18n.tr("%1 will be downloaded and installed via %2.", "system update confirmation, %1 is a download size, %2 the package managers").arg(Format.formatBytes(bytes)).arg(via);
|
|
else if (pkgs.length === 1)
|
|
text = I18n.tr("%1 package will be installed via %2.", "singular, %1 is 1, %2 the package managers").arg(1).arg(via);
|
|
else
|
|
text = I18n.tr("%1 packages will be installed via %2.", "plural, %1 is a package count, %2 the package managers").arg(pkgs.length).arg(via);
|
|
if (!root.upgradeRunsInTerminal)
|
|
return text;
|
|
return text + " " + I18n.tr("Runs in a terminal.", "system update confirmation, the upgrade opens a terminal window");
|
|
}
|
|
|
|
function runUpdateAll(interactive) {
|
|
SystemUpdateService.runUpdates({
|
|
includeFlatpak: SettingsData.updaterIncludeFlatpak,
|
|
includeAUR: SettingsData.updaterAllowAUR,
|
|
terminal: SessionData.terminalOverride,
|
|
interactive: interactive === true
|
|
});
|
|
packagesExpanded = false;
|
|
if (upgradeInWindow && interactive !== true)
|
|
PopoutService.showSystemUpdateModal();
|
|
}
|
|
|
|
SettingsPage {
|
|
id: mainColumn
|
|
|
|
SettingsHeroCard {
|
|
M3WaveProgress {
|
|
width: parent.width
|
|
height: Theme.spacingM
|
|
visible: SystemUpdateService.isUpgrading
|
|
isPlaying: visible
|
|
value: 1
|
|
trackColor: "transparent"
|
|
playheadColor: "transparent"
|
|
}
|
|
}
|
|
|
|
SettingsCard {
|
|
width: parent.width
|
|
title: "DankMaterialShell"
|
|
iconName: "deployed_code"
|
|
settingKey: "softwareUpdatesShell"
|
|
tags: ["dms", "shell", "version", "channel", "restart"]
|
|
|
|
SettingsRow {
|
|
readonly property bool failed: SystemUpdateService.hasError && !root.busy
|
|
|
|
title: root.statusTitle()
|
|
titleColor: failed ? Theme.error : contentColor
|
|
subtitle: root.statusSubtitle()
|
|
iconName: failed ? "error_outline" : root.needsRestart ? "restart_alt" : "system_update_alt"
|
|
iconColor: failed ? Theme.error : root.needsRestart ? Theme.warning : Theme.primary
|
|
|
|
DActionButton {
|
|
anchors.verticalCenter: parent.verticalCenter
|
|
visible: SystemUpdateService.sysupdateAvailable && !root.primaryIsCheck
|
|
enabled: !root.busy
|
|
buttonSize: Theme.buttonHeightS
|
|
iconName: "refresh"
|
|
iconColor: Theme.surfaceText
|
|
backgroundColor: SettingsMetrics.controlSurface
|
|
tooltipText: I18n.tr("Check for updates")
|
|
onClicked: SystemUpdateService.checkForUpdates()
|
|
}
|
|
|
|
DButton {
|
|
anchors.verticalCenter: parent.verticalCenter
|
|
visible: SystemUpdateService.sysupdateAvailable
|
|
buttonHeight: Theme.buttonHeightS
|
|
text: root.primaryLabel()
|
|
iconName: root.primaryIcon()
|
|
busy: SystemUpdateService.isChecking
|
|
enabled: !SystemUpdateService.isChecking
|
|
backgroundColor: Theme.primary
|
|
textColor: Theme.onPrimary
|
|
onClicked: root.primaryAction()
|
|
}
|
|
}
|
|
|
|
SettingsRow {
|
|
title: I18n.tr("Version")
|
|
iconName: "tag"
|
|
subtitle: {
|
|
const running = SystemUpdateService.shellRunning || ShellVersionService.shellVersion;
|
|
const to = SystemUpdateService.shellUpdateVersion;
|
|
if (to)
|
|
return running + " → " + to;
|
|
return running;
|
|
}
|
|
trailingBadge: root.installMethodLabel()
|
|
}
|
|
|
|
SettingsRow {
|
|
title: I18n.tr("Channel")
|
|
iconName: "alt_route"
|
|
subtitle: root.channelLabel()
|
|
trailingBadge: SystemUpdateService.shellManagedExternally ? I18n.tr("Managed by Nix", "badge on the DMS update row when the shell is installed through Nix") : ""
|
|
}
|
|
|
|
SettingsRow {
|
|
readonly property var master: SystemUpdateService.releases?.master ?? null
|
|
|
|
visible: SystemUpdateService.shellChannel === "git" && SystemUpdateService.commitsBehind >= 0
|
|
title: I18n.tr("Master branch", "git channel row, the branch the build tracks")
|
|
subtitle: {
|
|
const date = new Date(master?.date ?? NaN);
|
|
return isNaN(date) ? "" : I18n.tr("Last commit %1", "git channel row, %1 is a localized date").arg(date.toLocaleDateString(Qt.locale(), Locale.ShortFormat));
|
|
}
|
|
iconName: "commit"
|
|
|
|
// Drawn in the slot so it precedes the button; trailingBadge would land after it
|
|
StyledText {
|
|
anchors.verticalCenter: parent.verticalCenter
|
|
text: SystemUpdateService.commitsBehind === 0 ? I18n.tr("Up to date") : I18n.tr("%1 behind", "git channel badge, %1 is the number of commits master is ahead").arg(SystemUpdateService.commitsBehind)
|
|
font.pixelSize: Theme.fontSizeSmall
|
|
color: SystemUpdateService.commitsBehind === 0 ? Theme.surfaceVariantText : Theme.primary
|
|
}
|
|
|
|
DActionButton {
|
|
anchors.verticalCenter: parent.verticalCenter
|
|
buttonSize: Theme.buttonHeightS
|
|
iconName: "open_in_browser"
|
|
iconColor: Theme.primary
|
|
backgroundColor: SettingsMetrics.controlSurface
|
|
tooltipText: I18n.tr("View on GitHub", "link to the release on GitHub")
|
|
onClicked: Qt.openUrlExternally("https://github.com/AvengeMedia/DankMaterialShell/commits/master")
|
|
}
|
|
}
|
|
|
|
SettingsNavRow {
|
|
visible: root.notesRelease !== null
|
|
title: I18n.tr("Release notes", "settings page and card title, notes for a DMS release")
|
|
iconName: "auto_awesome"
|
|
hint: root.notesRelease?.codename ? "v" + root.notesRelease.version + " · " + root.notesRelease.codename : "v" + (root.notesRelease?.version ?? "")
|
|
onClicked: keyboard => root.parentModal?.navigateTo("updater_changelog", keyboard)
|
|
}
|
|
|
|
SettingsRow {
|
|
visible: SystemUpdateService.restartPending
|
|
title: I18n.tr("Requires restart")
|
|
iconName: "restart_alt"
|
|
iconColor: Theme.warning
|
|
subtitle: SystemUpdateService.shellInstalled ? I18n.tr("Installed %1, still running %2", "%1 is the installed version, %2 the running one").arg(SystemUpdateService.shellInstalled).arg(SystemUpdateService.shellRunning) : I18n.tr("A newer dms binary is installed.", "software updates row subtitle, the running shell is older than the installed binary")
|
|
}
|
|
}
|
|
|
|
SettingsCard {
|
|
width: parent.width
|
|
visible: SystemUpdateService.sysupdateAvailable
|
|
title: I18n.tr("System packages", "software updates card title, package manager updates")
|
|
iconName: "inventory_2"
|
|
settingKey: "softwareUpdatesSystem"
|
|
tags: ["system", "packages", "flatpak", "aur"]
|
|
|
|
SettingsRow {
|
|
title: {
|
|
if (!SystemUpdateService.helperAvailable)
|
|
return I18n.tr("No supported package manager found.");
|
|
if (root.systemCount === 0)
|
|
return I18n.tr("Up to date");
|
|
const base = root.countText(root.systemCount);
|
|
return root.flatpakCount > 0 ? base + " · " + I18n.tr("%1 Flatpak", "count of flatpak updates, %1 is a number").arg(root.flatpakCount) : base;
|
|
}
|
|
subtitle: {
|
|
const distro = SystemUpdateService.distributionPretty || SystemUpdateService.distribution;
|
|
const names = (SystemUpdateService.backends || []).map(b => b.displayName).join(", ");
|
|
return distro && names ? distro + " · " + names : distro || names;
|
|
}
|
|
iconName: !SystemUpdateService.helperAvailable ? "error_outline" : root.systemCount === 0 ? "check_circle" : "package_2"
|
|
iconColor: SystemUpdateService.helperAvailable ? Theme.primary : Theme.error
|
|
clickable: root.systemCount > 0
|
|
onClicked: root.packagesExpanded = !root.packagesExpanded
|
|
|
|
DIcon {
|
|
anchors.verticalCenter: parent.verticalCenter
|
|
visible: root.systemCount > 0
|
|
name: "expand_more"
|
|
size: Theme.iconSize
|
|
color: Theme.surfaceVariantText
|
|
rotation: root.packagesExpanded ? 180 : 0
|
|
|
|
Behavior on rotation {
|
|
enabled: Theme.currentAnimationSpeed !== SettingsData.AnimationSpeed.None
|
|
NumberAnimation {
|
|
duration: Theme.expressiveDurations.expressiveFastSpatial
|
|
easing.type: Easing.BezierSpline
|
|
easing.bezierCurve: Theme.expressiveCurves.expressiveDefaultSpatial
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
Column {
|
|
width: parent?.width ?? 0
|
|
spacing: Theme.groupedListGap
|
|
|
|
Repeater {
|
|
model: root.packagesExpanded && !SystemUpdateService.isUpgrading ? SystemUpdateService.systemUpdates.slice(0, root.packageListCap) : []
|
|
|
|
delegate: SettingsRow {
|
|
id: packageRow
|
|
required property var modelData
|
|
|
|
title: modelData.name || ""
|
|
subtitle: {
|
|
const from = modelData.fromVersion || "";
|
|
const to = modelData.toVersion || "";
|
|
const version = from && to ? from + " → " + to : to || from;
|
|
const repo = modelData.repo || "";
|
|
return repo && version ? repo + " · " + version : repo || version;
|
|
}
|
|
|
|
DActionButton {
|
|
anchors.verticalCenter: parent.verticalCenter
|
|
iconName: "visibility_off"
|
|
visible: SystemUpdateService.canIgnorePackage(packageRow.modelData)
|
|
tooltipText: I18n.tr("Ignore package", "tooltip, exclude a package from system updates")
|
|
onClicked: SystemUpdateService.ignorePackage(packageRow.modelData.name)
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
SettingsRow {
|
|
visible: root.packagesExpanded && !SystemUpdateService.isUpgrading && root.systemCount > root.packageListCap
|
|
title: I18n.tr("and %1 more", "%1 is the number of update rows not shown").arg(root.systemCount - root.packageListCap)
|
|
titleColor: Theme.surfaceVariantText
|
|
}
|
|
|
|
SettingsRow {
|
|
visible: root.upgradeInWindow && SystemUpdateService.isUpgrading
|
|
title: I18n.tr("Upgrading...", "system update popout status while packages upgrade")
|
|
iconName: "open_in_new"
|
|
|
|
DButton {
|
|
anchors.verticalCenter: parent.verticalCenter
|
|
buttonHeight: Theme.buttonHeightS
|
|
text: I18n.tr("Show window", "button, brings back the floating system update window")
|
|
iconName: "open_in_new"
|
|
backgroundColor: SettingsMetrics.controlSurface
|
|
textColor: Theme.surfaceText
|
|
onClicked: PopoutService.showSystemUpdateModal()
|
|
}
|
|
}
|
|
|
|
SettingsRow {
|
|
visible: (SystemUpdateService.isUpgrading && !root.upgradeInWindow) || (root.upgradeFailed && !root.upgradeRunsInTerminal)
|
|
body: Item {
|
|
readonly property real lineHeight: logText.implicitHeight / Math.max(1, logText.lineCount)
|
|
|
|
width: parent.width
|
|
height: Math.min(logText.implicitHeight, Math.floor(root.logViewHeight / lineHeight) * lineHeight)
|
|
clip: true
|
|
|
|
UpgradeLogText {
|
|
id: logText
|
|
anchors.bottom: parent.bottom
|
|
width: parent.width
|
|
placeholder: root.upgradeRunsInTerminal ? I18n.tr("Running in terminal") : ""
|
|
lines: (SystemUpdateService.recentLog || []).slice(-root.logTailLines)
|
|
}
|
|
}
|
|
}
|
|
|
|
SettingsRow {
|
|
visible: SystemUpdateService.hasError && !root.busy
|
|
title: I18n.tr("Failed: %1", "system update error status, %1 is the error message").arg(SystemUpdateService.errorMessage)
|
|
titleColor: Theme.error
|
|
subtitle: SystemUpdateService.errorHint
|
|
iconName: "error_outline"
|
|
iconColor: Theme.error
|
|
|
|
DButton {
|
|
anchors.verticalCenter: parent.verticalCenter
|
|
visible: root.upgradeFailed && !root.upgradeRunsInTerminal
|
|
text: I18n.tr("Open in terminal")
|
|
iconName: "terminal"
|
|
backgroundColor: SettingsMetrics.controlSurface
|
|
textColor: Theme.surfaceText
|
|
onClicked: root.runUpdateAll(true)
|
|
}
|
|
}
|
|
}
|
|
|
|
SettingsCard {
|
|
width: parent.width
|
|
title: I18n.tr("Options")
|
|
iconName: "tune"
|
|
settingKey: "systemUpdater"
|
|
collapsible: true
|
|
expanded: false
|
|
|
|
SettingsDropdownRow {
|
|
settingKey: "systemUpdaterCheckInterval"
|
|
resetKeys: ["updaterIntervalSeconds"]
|
|
resetByKeys: false
|
|
onResetRequested: {
|
|
root.customIntervalSelected = false;
|
|
SettingsData.resetToDefault(["updaterIntervalSeconds"]);
|
|
SystemUpdateService.setInterval(SettingsData.updaterIntervalSeconds);
|
|
}
|
|
tags: ["interval", "poll", "frequency"]
|
|
text: I18n.tr("Check interval")
|
|
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);
|
|
}
|
|
}
|
|
|
|
SettingsTextFieldRow {
|
|
id: customIntervalField
|
|
leftIconName: "timer"
|
|
visible: root.customIntervalSelected
|
|
text: I18n.tr("Custom interval in minutes (minimum 5)")
|
|
placeholderText: "1440"
|
|
value: Math.round(SettingsData.updaterIntervalSeconds / 60).toString()
|
|
validator: IntValidator {
|
|
bottom: 5
|
|
}
|
|
onValueEdited: value => {
|
|
const minutes = parseInt(value, 10);
|
|
if (isNaN(minutes) || minutes < 5)
|
|
return;
|
|
const seconds = minutes * 60;
|
|
SettingsData.set("updaterIntervalSeconds", seconds);
|
|
SystemUpdateService.setInterval(seconds);
|
|
}
|
|
}
|
|
|
|
SettingsToggleRow {
|
|
settingKey: "systemUpdaterCheckOnStart"
|
|
tags: ["startup", "check", "boot"]
|
|
text: I18n.tr("Check on startup")
|
|
checked: SettingsData.updaterCheckOnStart
|
|
onToggled: checked => SettingsData.set("updaterCheckOnStart", checked)
|
|
}
|
|
|
|
SettingsToggleRow {
|
|
settingKey: "systemUpdaterPauseOnBattery"
|
|
tags: ["battery", "power", "pause"]
|
|
visible: BatteryService.batteryAvailable
|
|
text: I18n.tr("Pause checks on battery", "software updates toggle, skip background checks on battery power")
|
|
checked: SettingsData.updaterPauseOnBattery
|
|
onToggled: checked => SettingsData.set("updaterPauseOnBattery", checked)
|
|
}
|
|
|
|
SettingsToggleRow {
|
|
settingKey: "systemUpdaterNotify"
|
|
tags: ["notify", "notification", "alert"]
|
|
text: I18n.tr("Notify me on new updates", "software updates toggle")
|
|
description: I18n.tr("Checks in the background at the check interval. Notifies only when the count grows.", "notify on new updates toggle description")
|
|
checked: SettingsData.updaterNotify
|
|
onToggled: checked => SettingsData.set("updaterNotify", checked)
|
|
}
|
|
|
|
SettingsDropdownRow {
|
|
settingKey: "systemUpdaterNotifyMin"
|
|
tags: ["notify", "notification", "frequency", "throttle"]
|
|
resetKeys: ["updaterNotifyMinSeconds"]
|
|
visible: SettingsData.updaterNotify
|
|
text: I18n.tr("Notify at most", "software updates dropdown label, how often update notifications may repeat")
|
|
options: root.notifyOptions.map(o => o.label)
|
|
currentValue: (root.notifyOptions.find(o => o.seconds === SettingsData.updaterNotifyMinSeconds) ?? root.notifyOptions[root.notifyOptions.length - 1]).label
|
|
onValueChanged: label => {
|
|
const opt = root.notifyOptions.find(o => o.label === label);
|
|
if (opt)
|
|
SettingsData.set("updaterNotifyMinSeconds", opt.seconds);
|
|
}
|
|
}
|
|
|
|
SettingsToggleRow {
|
|
settingKey: "systemUpdaterFlatpak"
|
|
tags: ["flatpak", "include"]
|
|
text: I18n.tr("Include Flatpak updates")
|
|
visible: (SystemUpdateService.backends || []).some(b => b.repo === "flatpak")
|
|
checked: SettingsData.updaterIncludeFlatpak
|
|
onToggled: checked => SettingsData.set("updaterIncludeFlatpak", checked)
|
|
}
|
|
|
|
SettingsToggleRow {
|
|
settingKey: "systemUpdaterAUR"
|
|
tags: ["aur", "paru", "yay", "shelly"]
|
|
text: I18n.tr("Include AUR updates")
|
|
visible: (SystemUpdateService.backends || []).some(b => ["paru", "yay", "shelly"].includes(b.id))
|
|
checked: SettingsData.updaterAllowAUR
|
|
onToggled: checked => SettingsData.set("updaterAllowAUR", checked)
|
|
}
|
|
|
|
SettingsToggleRow {
|
|
settingKey: "systemUpdaterReopenAfterUpgrade"
|
|
tags: ["reopen", "popout", "terminal", "upgrade"]
|
|
text: I18n.tr("Reopen panel after update")
|
|
visible: root.upgradeRunsInTerminal
|
|
checked: SettingsData.updaterReopenAfterUpgrade
|
|
onToggled: checked => SettingsData.set("updaterReopenAfterUpgrade", checked)
|
|
}
|
|
|
|
SettingsToggleRow {
|
|
settingKey: "systemUpdaterUpgradeInWindow"
|
|
tags: ["window", "popout", "floating", "log", "output"]
|
|
text: I18n.tr("Show upgrade in a window", "software updates toggle")
|
|
description: I18n.tr("Opens a floating window with the live output", "show upgrade in a window toggle description")
|
|
visible: !root.upgradeRunsInTerminal
|
|
checked: SettingsData.updaterUpgradeInWindow
|
|
onToggled: checked => SettingsData.set("updaterUpgradeInWindow", checked)
|
|
}
|
|
|
|
TerminalPickerRow {}
|
|
}
|
|
|
|
SettingsCard {
|
|
id: ignoredPackagesCard
|
|
width: parent.width
|
|
iconName: "visibility_off"
|
|
title: I18n.tr("Ignored packages")
|
|
settingKey: "systemUpdaterIgnoredPackages"
|
|
tags: ["system", "update", "package", "ignore"]
|
|
collapsible: true
|
|
expanded: false
|
|
|
|
property bool errorIsInvalidName: false
|
|
|
|
function addIgnoredPackage() {
|
|
const name = newIgnoredPackageField.value.trim();
|
|
if (name === "")
|
|
return;
|
|
errorIsInvalidName = !SystemUpdateService.isValidIgnoredName(name);
|
|
if (errorIsInvalidName) {
|
|
ignoredPackageError.visible = true;
|
|
return;
|
|
}
|
|
ignoredPackageError.visible = !SystemUpdateService.ignorePackage(name);
|
|
if (ignoredPackageError.visible)
|
|
return;
|
|
newIgnoredPackageField.value = "";
|
|
}
|
|
|
|
SettingsTextFieldRow {
|
|
id: newIgnoredPackageField
|
|
leftIconName: "inventory_2"
|
|
text: I18n.tr("Name")
|
|
description: {
|
|
if (SettingsData.updaterUseCustomCommand)
|
|
return I18n.tr("Ignored packages only apply to the built-in updater. Your custom command controls its own exclusions.");
|
|
if (SystemUpdateService.pkgManager === "shelly")
|
|
return I18n.tr("With Shelly, only Flatpak packages in the current update list can be ignored.");
|
|
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.");
|
|
}
|
|
placeholderText: I18n.tr("Package name (e.g., docker)")
|
|
onAccepted: ignoredPackagesCard.addIgnoredPackage()
|
|
onValueEdited: ignoredPackageError.visible = false
|
|
|
|
actions: DIconButton {
|
|
variant: "filled"
|
|
iconName: "add"
|
|
tooltipText: I18n.tr("Ignore package", "tooltip, exclude a package from system updates")
|
|
enabled: newIgnoredPackageField.value.trim() !== ""
|
|
onClicked: ignoredPackagesCard.addIgnoredPackage()
|
|
}
|
|
}
|
|
|
|
SettingsRow {
|
|
id: ignoredPackageError
|
|
visible: false
|
|
title: ignoredPackagesCard.errorIsInvalidName ? I18n.tr("Invalid package name — letters, digits and @._+:/- only.", "ignored packages validation error") : I18n.tr("With Shelly, only Flatpak packages in the current update list can be ignored.")
|
|
titleColor: Theme.error
|
|
}
|
|
|
|
Repeater {
|
|
model: SettingsData.updaterIgnoredPackages
|
|
|
|
delegate: SettingsRow {
|
|
id: ignoredRow
|
|
required property string modelData
|
|
required property int index
|
|
|
|
title: modelData
|
|
iconName: "visibility_off"
|
|
|
|
DActionButton {
|
|
anchors.verticalCenter: parent.verticalCenter
|
|
iconName: "delete"
|
|
iconColor: Theme.error
|
|
tooltipText: I18n.tr("Stop ignoring %1", "system updater button tooltip, %1 is the package name").arg(ignoredRow.modelData)
|
|
onClicked: SystemUpdateService.unignorePackage(ignoredRow.modelData)
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
SettingsCard {
|
|
width: parent.width
|
|
iconName: "terminal"
|
|
title: I18n.tr("Advanced")
|
|
settingKey: "systemUpdaterAdvanced"
|
|
collapsible: true
|
|
expanded: false
|
|
|
|
SettingsToggleRow {
|
|
settingKey: "systemUpdaterCustomCommand"
|
|
tags: ["custom", "command", "terminal"]
|
|
text: I18n.tr("Use custom command")
|
|
checked: SettingsData.updaterUseCustomCommand
|
|
onToggled: checked => {
|
|
if (!checked) {
|
|
updaterCustomCommand.value = "";
|
|
updaterTerminalCustomClass.value = "";
|
|
SettingsData.set("updaterCustomCommand", "");
|
|
SettingsData.set("updaterTerminalAdditionalParams", "");
|
|
}
|
|
SettingsData.set("updaterUseCustomCommand", checked);
|
|
}
|
|
}
|
|
|
|
SettingsNoteRow {
|
|
enabled: SettingsData.updaterUseCustomCommand
|
|
text: I18n.tr("Custom command and terminal params are split on whitespace; paths with spaces will break.")
|
|
}
|
|
|
|
SettingsTextFieldRow {
|
|
id: updaterCustomCommand
|
|
leftIconName: "terminal"
|
|
visible: SettingsData.updaterUseCustomCommand
|
|
resetKeys: ["updaterCustomCommand"]
|
|
text: I18n.tr("Custom update command")
|
|
placeholderText: "topgrade --no-retry"
|
|
value: SettingsData.updaterCustomCommand
|
|
onValueEdited: value => SettingsData.set("updaterCustomCommand", value.trim())
|
|
}
|
|
|
|
SettingsTextFieldRow {
|
|
id: updaterTerminalCustomClass
|
|
leftIconName: "terminal"
|
|
visible: SettingsData.updaterUseCustomCommand
|
|
resetKeys: ["updaterTerminalAdditionalParams"]
|
|
text: I18n.tr("Terminal additional parameters")
|
|
placeholderText: "-T updater"
|
|
value: SettingsData.updaterTerminalAdditionalParams
|
|
onValueEdited: value => SettingsData.set("updaterTerminalAdditionalParams", value.trim())
|
|
}
|
|
}
|
|
}
|
|
|
|
ConfirmDialogOverlay {
|
|
id: installConfirm
|
|
parent: root.parentModal?.modalFocusScope ?? root
|
|
}
|
|
}
|