diff --git a/Modals/SettingsModal.qml b/Modals/SettingsModal.qml index f8b382fe..9b6c7e86 100644 --- a/Modals/SettingsModal.qml +++ b/Modals/SettingsModal.qml @@ -5,6 +5,7 @@ import Quickshell import Quickshell.Io import qs.Common import qs.Modules.Settings +import qs.Services import qs.Widgets pragma ComponentBehavior @@ -63,7 +64,10 @@ DankModal { Column { anchors.fill: parent - anchors.margins: Theme.spacingM + anchors.leftMargin: Theme.spacingM + anchors.rightMargin: Theme.spacingM + anchors.topMargin: Theme.spacingM + anchors.bottomMargin: Theme.spacingXL spacing: Theme.spacingS // Header row with title and close button @@ -127,6 +131,212 @@ DankModal { anchors.topMargin: Theme.spacingM + 2 spacing: Theme.spacingXS + // Profile header box + Rectangle { + width: parent.width - Theme.spacingS * 2 + height: 110 + radius: Theme.cornerRadius + color: "transparent" + border.width: 0 + + Row { + anchors.left: parent.left + anchors.verticalCenter: parent.verticalCenter + anchors.leftMargin: Theme.spacingM + anchors.rightMargin: Theme.spacingM + spacing: Theme.spacingM + + // Profile image container with hover overlay + Item { + id: profileImageContainer + width: 80 + height: 80 + anchors.verticalCenter: parent.verticalCenter + + property bool hasImage: profileImageSource.status === Image.Ready + + Rectangle { + anchors.fill: parent + radius: width / 2 + color: "transparent" + border.color: Theme.primary + border.width: 1 + visible: parent.hasImage + } + + Image { + id: profileImageSource + source: { + if (PortalService.profileImage === "") + return ""; + if (PortalService.profileImage.startsWith("/")) + return "file://" + PortalService.profileImage; + return PortalService.profileImage; + } + smooth: true + asynchronous: true + mipmap: true + cache: true + visible: false + } + + MultiEffect { + anchors.fill: parent + anchors.margins: 5 + source: profileImageSource + maskEnabled: true + maskSource: profileCircularMask + visible: profileImageContainer.hasImage + maskThresholdMin: 0.5 + maskSpreadAtMin: 1 + } + + Item { + id: profileCircularMask + width: 70 + height: 70 + layer.enabled: true + layer.smooth: true + visible: false + + Rectangle { + anchors.fill: parent + radius: width / 2 + color: "black" + antialiasing: true + } + } + + Rectangle { + anchors.fill: parent + radius: width / 2 + color: Theme.primary + visible: !parent.hasImage + + DankIcon { + anchors.centerIn: parent + name: "person" + size: Theme.iconSizeLarge + color: Theme.primaryText + } + } + + DankIcon { + anchors.centerIn: parent + name: "warning" + size: Theme.iconSizeLarge + color: Theme.error + visible: PortalService.profileImage !== "" && profileImageSource.status === Image.Error + } + + // Hover overlay with edit and clear buttons + Rectangle { + anchors.fill: parent + radius: width / 2 + color: Qt.rgba(0, 0, 0, 0.7) + visible: profileMouseArea.containsMouse + + Row { + anchors.centerIn: parent + spacing: 4 + + Rectangle { + width: 28 + height: 28 + radius: 14 + color: Qt.rgba(255, 255, 255, 0.9) + + DankIcon { + anchors.centerIn: parent + name: "edit" + size: 16 + color: "black" + } + + MouseArea { + anchors.fill: parent + cursorShape: Qt.PointingHandCursor + onClicked: { + settingsModal.allowFocusOverride = true; + settingsModal.shouldHaveFocus = false; + profileBrowser.open(); + } + } + } + + Rectangle { + width: 28 + height: 28 + radius: 14 + color: Qt.rgba(255, 255, 255, 0.9) + visible: profileImageContainer.hasImage + + DankIcon { + anchors.centerIn: parent + name: "close" + size: 16 + color: "black" + } + + MouseArea { + anchors.fill: parent + cursorShape: Qt.PointingHandCursor + onClicked: { + PortalService.setProfileImage(""); + } + } + } + } + } + + MouseArea { + id: profileMouseArea + anchors.fill: parent + hoverEnabled: true + cursorShape: Qt.PointingHandCursor + propagateComposedEvents: true + acceptedButtons: Qt.NoButton + } + } + + // User info column + Column { + width: 120 + anchors.verticalCenter: parent.verticalCenter + spacing: Theme.spacingXS + + StyledText { + text: UserInfoService.fullName || "User" + font.pixelSize: Theme.fontSizeLarge + font.weight: Font.Medium + color: Theme.surfaceText + elide: Text.ElideRight + width: parent.width + } + + StyledText { + text: DgopService.distribution || "Linux" + font.pixelSize: Theme.fontSizeMedium + color: Theme.surfaceVariantText + elide: Text.ElideRight + width: parent.width + } + } + } + } + + Rectangle { + width: parent.width - Theme.spacingS * 2 + height: 1 + color: Theme.outline + opacity: 0.2 + } + + Item { + width: parent.width + height: Theme.spacingL + } + Repeater { id: sidebarRepeater @@ -345,24 +555,88 @@ DankModal { // Footer Row { anchors.horizontalCenter: parent.horizontalCenter - spacing: Theme.spacingS + spacing: Theme.spacingXS - StyledText { - text: `DankMaterialShellniri edition •` - font.pixelSize: Theme.fontSizeSmall - color: Theme.surfaceVariantText - linkColor: Theme.primary + // Dank logo + Item { + width: 68 + height: 16 anchors.verticalCenter: parent.verticalCenter - onLinkActivated: link => Qt.openUrlExternally(link) - + + Image { + anchors.fill: parent + source: Qt.resolvedUrl(".").toString().replace("file://", "").replace("/Modals/", "") + "/assets/dank.svg" + sourceSize: Qt.size(68, 16) + smooth: true + fillMode: Image.PreserveAspectFit + layer.enabled: true + + layer.effect: MultiEffect { + colorization: 1 + colorizationColor: Theme.primary + } + } + MouseArea { anchors.fill: parent - cursorShape: parent.hoveredLink ? Qt.PointingHandCursor : Qt.ArrowCursor - acceptedButtons: Qt.NoButton - propagateComposedEvents: true + cursorShape: Qt.PointingHandCursor + onClicked: Qt.openUrlExternally("https://github.com/AvengeMedia/DankMaterialShell") } } + StyledText { + text: "•" + font.pixelSize: Theme.fontSizeSmall + color: Theme.surfaceVariantText + anchors.verticalCenter: parent.verticalCenter + } + + Rectangle { + width: Theme.spacingXS + height: 1 + color: "transparent" + } + + // Niri logo + Item { + width: 24 + height: 24 + anchors.verticalCenter: parent.verticalCenter + + Image { + anchors.fill: parent + source: Qt.resolvedUrl(".").toString().replace("file://", "").replace("/Modals/", "") + "/assets/niri.svg" + sourceSize: Qt.size(24, 24) + smooth: true + fillMode: Image.PreserveAspectFit + } + + MouseArea { + anchors.fill: parent + cursorShape: Qt.PointingHandCursor + onClicked: Qt.openUrlExternally("https://github.com/YaLTeR/niri") + } + } + + Rectangle { + width: Theme.spacingXS + height: 1 + color: "transparent" + } + + StyledText { + text: "•" + font.pixelSize: Theme.fontSizeSmall + color: Theme.surfaceVariantText + anchors.verticalCenter: parent.verticalCenter + } + + Rectangle { + width: Theme.spacingM + height: 1 + color: "transparent" + } + // Matrix button Item { width: 32 @@ -390,6 +664,12 @@ DankModal { } } + Rectangle { + width: Theme.spacingM + height: 1 + color: "transparent" + } + StyledText { text: "•" font.pixelSize: Theme.fontSizeSmall @@ -397,6 +677,12 @@ DankModal { anchors.verticalCenter: parent.verticalCenter } + Rectangle { + width: Theme.spacingM + height: 1 + color: "transparent" + } + // Discord button Item { width: 16 @@ -417,6 +703,46 @@ DankModal { onClicked: Qt.openUrlExternally("https://discord.gg/vT8Sfjy7sx") } } + + Rectangle { + width: Theme.spacingM + height: 1 + color: "transparent" + } + + StyledText { + text: "•" + font.pixelSize: Theme.fontSizeSmall + color: Theme.surfaceVariantText + anchors.verticalCenter: parent.verticalCenter + } + + Rectangle { + width: Theme.spacingM + height: 1 + color: "transparent" + } + + // Reddit button + Item { + width: 18 + height: 18 + anchors.verticalCenter: parent.verticalCenter + + Image { + anchors.fill: parent + source: Qt.resolvedUrl(".").toString().replace("file://", "").replace("/Modals/", "") + "/assets/reddit.svg" + sourceSize: Qt.size(18, 18) + smooth: true + fillMode: Image.PreserveAspectFit + } + + MouseArea { + anchors.fill: parent + cursorShape: Qt.PointingHandCursor + onClicked: Qt.openUrlExternally("https://reddit.com/r/niri") + } + } } } @@ -425,4 +751,25 @@ DankModal { } + FileBrowserModal { + id: profileBrowser + + browserTitle: "Select Profile Image" + browserIcon: "person" + browserType: "profile" + fileExtensions: ["*.jpg", "*.jpeg", "*.png", "*.bmp", "*.gif", "*.webp"] + onFileSelected: (path) => { + PortalService.setProfileImage(path); + close(); + } + onDialogClosed: { + if (settingsModal) { + settingsModal.allowFocusOverride = false; + settingsModal.shouldHaveFocus = Qt.binding(() => { + return settingsModal.shouldBeVisible; + }); + } + } + } + } diff --git a/Modules/Settings/PersonalizationTab.qml b/Modules/Settings/PersonalizationTab.qml index 553bb27e..d0287d66 100644 --- a/Modules/Settings/PersonalizationTab.qml +++ b/Modules/Settings/PersonalizationTab.qml @@ -10,7 +10,6 @@ import qs.Widgets Item { id: personalizationTab - property alias profileBrowser: profileBrowser property alias wallpaperBrowser: wallpaperBrowser property var parentModal: null property var cachedFontFamilies: [] @@ -84,269 +83,6 @@ Item { width: parent.width spacing: Theme.spacingXL - // Profile Image Section - StyledRect { - width: parent.width - height: profileSection.implicitHeight + Theme.spacingL * 2 - radius: Theme.cornerRadius - color: Qt.rgba(Theme.surfaceVariant.r, Theme.surfaceVariant.g, Theme.surfaceVariant.b, 0.3) - border.color: Qt.rgba(Theme.outline.r, Theme.outline.g, Theme.outline.b, 0.2) - border.width: 1 - - Column { - id: profileSection - - anchors.fill: parent - anchors.margins: Theme.spacingL - spacing: Theme.spacingM - - Row { - width: parent.width - spacing: Theme.spacingM - - DankIcon { - name: "person" - size: Theme.iconSize - color: Theme.primary - anchors.verticalCenter: parent.verticalCenter - } - - StyledText { - text: "Profile Image" - font.pixelSize: Theme.fontSizeLarge - font.weight: Font.Medium - color: Theme.surfaceText - anchors.verticalCenter: parent.verticalCenter - } - - } - - Row { - width: parent.width - spacing: Theme.spacingL - - Item { - id: avatarContainer - - property bool hasImage: avatarImageSource.status === Image.Ready - - width: 80 - height: 80 - - Rectangle { - anchors.fill: parent - radius: width / 2 - color: "transparent" - border.color: Theme.primary - border.width: 1 - visible: parent.hasImage - } - - Image { - id: avatarImageSource - - source: { - if (PortalService.profileImage === "") - return ""; - - if (PortalService.profileImage.startsWith("/")) - return "file://" + PortalService.profileImage; - - return PortalService.profileImage; - } - smooth: true - asynchronous: true - mipmap: true - cache: true - visible: false - } - - MultiEffect { - anchors.fill: parent - anchors.margins: 5 - source: avatarImageSource - maskEnabled: true - maskSource: settingsCircularMask - visible: avatarContainer.hasImage - maskThresholdMin: 0.5 - maskSpreadAtMin: 1 - } - - Item { - id: settingsCircularMask - - width: 70 - height: 70 - layer.enabled: true - layer.smooth: true - visible: false - - Rectangle { - anchors.fill: parent - radius: width / 2 - color: "black" - antialiasing: true - } - - } - - Rectangle { - anchors.fill: parent - radius: width / 2 - color: Theme.primary - visible: !parent.hasImage - - DankIcon { - anchors.centerIn: parent - name: "person" - size: Theme.iconSizeLarge + 8 - color: Theme.primaryText - } - - } - - DankIcon { - anchors.centerIn: parent - name: "warning" - size: Theme.iconSizeLarge - color: Theme.error - visible: PortalService.profileImage !== "" && avatarImageSource.status === Image.Error - } - - } - - Column { - width: parent.width - 80 - Theme.spacingL - spacing: Theme.spacingS - anchors.verticalCenter: parent.verticalCenter - - StyledText { - text: PortalService.profileImage ? PortalService.profileImage.split('/').pop() : "No profile image selected" - font.pixelSize: Theme.fontSizeLarge - color: Theme.surfaceText - elide: Text.ElideMiddle - width: parent.width - } - - StyledText { - text: PortalService.profileImage ? PortalService.profileImage : "" - font.pixelSize: Theme.fontSizeSmall - color: Theme.surfaceVariantText - elide: Text.ElideMiddle - width: parent.width - visible: PortalService.profileImage !== "" - } - - Row { - spacing: Theme.spacingXS - visible: !PortalService.accountsServiceAvailable - - DankIcon { - name: "error" - size: Theme.iconSizeSmall - color: Theme.error - anchors.verticalCenter: parent.verticalCenter - } - - StyledText { - text: "accountsservice missing or not accessible" - font.pixelSize: Theme.fontSizeSmall - color: Theme.error - anchors.verticalCenter: parent.verticalCenter - } - - } - - Row { - spacing: Theme.spacingS - - StyledRect { - width: 100 - height: 32 - radius: Theme.cornerRadius - color: Theme.primary - - Row { - anchors.centerIn: parent - spacing: Theme.spacingXS - - DankIcon { - name: "folder_open" - size: Theme.iconSizeSmall - color: Theme.primaryText - anchors.verticalCenter: parent.verticalCenter - } - - StyledText { - text: "Browse" - color: Theme.primaryText - font.pixelSize: Theme.fontSizeSmall - anchors.verticalCenter: parent.verticalCenter - } - - } - - MouseArea { - anchors.fill: parent - cursorShape: Qt.PointingHandCursor - onClicked: { - if (parentModal) { - parentModal.allowFocusOverride = true; - parentModal.shouldHaveFocus = false; - } - profileBrowser.open(); - } - } - - } - - StyledRect { - width: 80 - height: 32 - radius: Theme.cornerRadius - color: Theme.surfaceVariant - opacity: PortalService.profileImage !== "" ? 1 : 0.5 - - Row { - anchors.centerIn: parent - spacing: Theme.spacingXS - - DankIcon { - name: "clear" - size: Theme.iconSizeSmall - color: Theme.surfaceVariantText - anchors.verticalCenter: parent.verticalCenter - } - - StyledText { - text: "Clear" - color: Theme.surfaceVariantText - font.pixelSize: Theme.fontSizeSmall - anchors.verticalCenter: parent.verticalCenter - } - - } - - MouseArea { - anchors.fill: parent - enabled: PortalService.profileImage !== "" - cursorShape: enabled ? Qt.PointingHandCursor : Qt.ArrowCursor - onClicked: { - PortalService.setProfileImage(""); - } - } - - } - - } - - } - - } - - } - - } // Wallpaper Section StyledRect { @@ -1148,26 +884,6 @@ Item { } - FileBrowserModal { - id: profileBrowser - - browserTitle: "Select Profile Image" - browserIcon: "person" - browserType: "profile" - fileExtensions: ["*.jpg", "*.jpeg", "*.png", "*.bmp", "*.gif", "*.webp"] - onFileSelected: (path) => { - PortalService.setProfileImage(path); - close(); - } - onDialogClosed: { - if (parentModal) { - parentModal.allowFocusOverride = false; - parentModal.shouldHaveFocus = Qt.binding(() => { - return parentModal.shouldBeVisible; - }); - } - } - } FileBrowserModal { id: wallpaperBrowser diff --git a/Services/DgopService.qml b/Services/DgopService.qml index bb4f6fb3..fe44834e 100644 --- a/Services/DgopService.qml +++ b/Services/DgopService.qml @@ -632,7 +632,47 @@ Singleton { } } + Process { + id: osReleaseProcess + command: ["cat", "/etc/os-release"] + running: false + onExited: exitCode => { + if (exitCode !== 0) { + console.warn("Failed to read /etc/os-release") + } + } + stdout: StdioCollector { + onStreamFinished: { + if (text.trim()) { + try { + const lines = text.trim().split('\n') + let prettyName = "" + let name = "" + + for (const line of lines) { + const trimmedLine = line.trim() + if (trimmedLine.startsWith('PRETTY_NAME=')) { + prettyName = trimmedLine.substring(12).replace(/^["']|["']$/g, '') + } else if (trimmedLine.startsWith('NAME=')) { + name = trimmedLine.substring(5).replace(/^["']|["']$/g, '') + } + } + + // Prefer PRETTY_NAME, fallback to NAME + const distroName = prettyName || name || "Linux" + distribution = distroName + console.log("Detected distribution:", distroName) + } catch (e) { + console.warn("Failed to parse /etc/os-release:", e) + distribution = "Linux" + } + } + } + } + } + Component.onCompleted: { dgopCheckProcess.running = true + osReleaseProcess.running = true } } \ No newline at end of file diff --git a/assets/dank.svg b/assets/dank.svg new file mode 100644 index 00000000..a78609e7 --- /dev/null +++ b/assets/dank.svg @@ -0,0 +1,49 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/assets/niri.svg b/assets/niri.svg new file mode 100644 index 00000000..5bd2de04 --- /dev/null +++ b/assets/niri.svg @@ -0,0 +1,405 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/assets/reddit.svg b/assets/reddit.svg new file mode 100644 index 00000000..114d4255 --- /dev/null +++ b/assets/reddit.svg @@ -0,0 +1,20 @@ + + + + + + + + \ No newline at end of file