1
0
mirror of https://github.com/AvengeMedia/DankMaterialShell.git synced 2026-05-02 18:42:06 -04:00

Compare commits

..

3 Commits

6 changed files with 274 additions and 18 deletions

View File

@@ -51,6 +51,15 @@ Item {
}
}
onSearchModeChanged: {
if (searchMode === "apps") {
_loadAppCategories();
} else {
appCategory = "";
appCategories = [];
}
}
Connections {
target: SettingsData
function onSortAppsAlphabeticallyChanged() {
@@ -65,8 +74,12 @@ Item {
if (!active)
return;
_clearModeCache();
if (!searchQuery && searchMode === "all")
if (searchMode === "apps") {
_loadAppCategories();
performSearch();
} else if (!searchQuery && searchMode === "all") {
performSearch();
}
}
}
@@ -171,6 +184,8 @@ Item {
property string activePluginName: ""
property var activePluginCategories: []
property string activePluginCategory: ""
property string appCategory: ""
property var appCategories: []
function getSectionViewMode(sectionId) {
if (sectionId === "browse_plugins")
@@ -364,6 +379,8 @@ Item {
activePluginName = "";
activePluginCategories = [];
activePluginCategory = "";
appCategory = "";
appCategories = [];
pluginFilter = "";
collapsedSections = {};
_clearModeCache();
@@ -408,6 +425,19 @@ Item {
performSearch();
}
function setAppCategory(category) {
if (appCategory === category)
return;
appCategory = category;
_queryDrivenSearch = true;
_clearModeCache();
performSearch();
}
function _loadAppCategories() {
appCategories = AppSearchService.getAllCategories();
}
function setFileSearchType(type) {
if (fileSearchType === type)
return;
@@ -592,8 +622,9 @@ Item {
}
if (searchMode === "apps") {
var isCategoryFiltered = appCategory && appCategory !== I18n.tr("All");
var cachedSections = AppSearchService.getCachedDefaultSections();
if (cachedSections && !searchQuery) {
if (cachedSections && !searchQuery && !isCategoryFiltered) {
var modeCache = _getCachedModeData("apps");
if (modeCache) {
_applyHighlights(modeCache.sections, "");
@@ -623,9 +654,23 @@ Item {
return;
}
var apps = searchApps(searchQuery);
for (var i = 0; i < apps.length; i++) {
allItems.push(apps[i]);
if (isCategoryFiltered) {
var rawApps = AppSearchService.getAppsInCategory(appCategory);
for (var i = 0; i < rawApps.length; i++) {
allItems.push(getOrTransformApp(rawApps[i]));
}
// Also include core apps (DMS Settings etc.) that match this category
var allCoreApps = AppSearchService.getCoreApps("");
for (var i = 0; i < allCoreApps.length; i++) {
var coreAppCats = AppSearchService.getCategoriesForApp(allCoreApps[i]);
if (coreAppCats.indexOf(appCategory) !== -1)
allItems.push(transformCoreApp(allCoreApps[i]));
}
} else {
var apps = searchApps(searchQuery);
for (var i = 0; i < apps.length; i++) {
allItems.push(apps[i]);
}
}
var scoredItems = Scorer.scoreItems(allItems, searchQuery, getFrecencyForItem);

View File

@@ -496,8 +496,9 @@ FocusScope {
Row {
id: categoryRow
width: parent.width
height: controller.activePluginCategories.length > 0 ? 36 : 0
visible: controller.activePluginCategories.length > 0
readonly property bool showPluginCategories: controller.activePluginCategories.length > 0
height: showPluginCategories ? 36 : 0
visible: showPluginCategories
spacing: Theme.spacingS
clip: true
@@ -511,6 +512,7 @@ FocusScope {
DankDropdown {
id: categoryDropdown
visible: categoryRow.showPluginCategories
width: Math.min(200, parent.width)
compactMode: true
dropdownWidth: 200
@@ -546,6 +548,7 @@ FocusScope {
}
}
}
}
Item {

View File

@@ -1,7 +1,9 @@
pragma ComponentBehavior: Bound
import QtQuick
import QtQuick.Controls
import qs.Common
import qs.Services
import qs.Widgets
Rectangle {
@@ -35,21 +37,190 @@ Rectangle {
anchors.verticalCenter: parent.verticalCenter
spacing: Theme.spacingS
// Whether the apps category picker should replace the plain title
readonly property bool hasAppCategories: root.section?.id === "apps" && (root.controller?.appCategories?.length ?? 0) > 0
DankIcon {
anchors.verticalCenter: parent.verticalCenter
// Hide section icon when the category chip already shows one
visible: !leftContent.hasAppCategories
name: root.section?.icon ?? "folder"
size: 16
color: Theme.surfaceVariantText
}
// Plain title — hidden when the category chip is shown
StyledText {
anchors.verticalCenter: parent.verticalCenter
visible: !leftContent.hasAppCategories
text: root.section?.title ?? ""
font.pixelSize: Theme.fontSizeSmall
font.weight: Font.Medium
color: Theme.surfaceVariantText
}
// Compact inline category chip — only visible on the apps section
Item {
id: categoryChip
visible: leftContent.hasAppCategories
anchors.verticalCenter: parent.verticalCenter
// Size to content with a fixed-min width so it doesn't jump around
width: chipRow.implicitWidth + Theme.spacingM * 2
height: 24
readonly property string currentCategory: root.controller?.appCategory || (root.controller?.appCategories?.length > 0 ? root.controller.appCategories[0] : "")
readonly property var iconMap: {
const cats = root.controller?.appCategories ?? [];
const m = {};
cats.forEach(c => { m[c] = AppSearchService.getCategoryIcon(c); });
return m;
}
Rectangle {
anchors.fill: parent
radius: Theme.cornerRadius
color: chipArea.containsMouse || categoryPopup.visible ? Theme.surfaceContainerHigh : "transparent"
border.color: categoryPopup.visible ? Theme.primary : Theme.outlineMedium
border.width: categoryPopup.visible ? 2 : 1
}
Row {
id: chipRow
anchors.centerIn: parent
spacing: Theme.spacingXS
DankIcon {
anchors.verticalCenter: parent.verticalCenter
name: categoryChip.iconMap[categoryChip.currentCategory] ?? "apps"
size: 14
color: Theme.surfaceText
}
StyledText {
anchors.verticalCenter: parent.verticalCenter
text: categoryChip.currentCategory
font.pixelSize: Theme.fontSizeSmall
color: Theme.surfaceText
}
DankIcon {
anchors.verticalCenter: parent.verticalCenter
name: categoryPopup.visible ? "expand_less" : "expand_more"
size: 14
color: Theme.surfaceVariantText
}
}
MouseArea {
id: chipArea
anchors.fill: parent
hoverEnabled: true
cursorShape: Qt.PointingHandCursor
onClicked: {
if (categoryPopup.visible) {
categoryPopup.close();
} else {
const pos = categoryChip.mapToItem(Overlay.overlay, 0, 0);
categoryPopup.x = pos.x;
categoryPopup.y = pos.y + categoryChip.height + 4;
categoryPopup.open();
}
}
}
Popup {
id: categoryPopup
parent: Overlay.overlay
width: Math.max(categoryChip.width, 180)
padding: 0
modal: true
dim: false
closePolicy: Popup.CloseOnEscape | Popup.CloseOnPressOutside
background: Rectangle { color: "transparent" }
contentItem: Rectangle {
radius: Theme.cornerRadius
color: Qt.rgba(Theme.surfaceContainer.r, Theme.surfaceContainer.g, Theme.surfaceContainer.b, 1)
border.color: Theme.primary
border.width: 2
ElevationShadow {
anchors.fill: parent
z: -1
level: Theme.elevationLevel2
fallbackOffset: 4
targetRadius: parent.radius
targetColor: parent.color
borderColor: parent.border.color
borderWidth: parent.border.width
shadowEnabled: Theme.elevationEnabled && SettingsData.popoutElevationEnabled
}
ListView {
id: categoryList
anchors.fill: parent
anchors.margins: Theme.spacingS
model: root.controller?.appCategories ?? []
spacing: 2
clip: true
interactive: contentHeight > height
implicitHeight: contentHeight
delegate: Rectangle {
id: catDelegate
required property string modelData
required property int index
width: categoryList.width
height: 32
radius: Theme.cornerRadius
readonly property bool isCurrent: categoryChip.currentCategory === modelData
color: isCurrent ? Theme.primaryHover : catArea.containsMouse ? Theme.primaryHoverLight : "transparent"
Row {
anchors.left: parent.left
anchors.right: parent.right
anchors.verticalCenter: parent.verticalCenter
anchors.leftMargin: Theme.spacingS
anchors.rightMargin: Theme.spacingS
spacing: Theme.spacingS
DankIcon {
anchors.verticalCenter: parent.verticalCenter
name: categoryChip.iconMap[catDelegate.modelData] ?? "apps"
size: 16
color: catDelegate.isCurrent ? Theme.primary : Theme.surfaceText
}
StyledText {
anchors.verticalCenter: parent.verticalCenter
text: catDelegate.modelData
font.pixelSize: Theme.fontSizeMedium
color: catDelegate.isCurrent ? Theme.primary : Theme.surfaceText
font.weight: catDelegate.isCurrent ? Font.Medium : Font.Normal
}
}
MouseArea {
id: catArea
anchors.fill: parent
hoverEnabled: true
cursorShape: Qt.PointingHandCursor
onClicked: {
if (root.controller)
root.controller.setAppCategory(catDelegate.modelData);
categoryPopup.close();
}
}
}
}
}
// Size to list content, cap at 10 visible items
height: Math.min((root.controller?.appCategories?.length ?? 0) * 34, 10 * 34) + Theme.spacingS * 2 + 4
}
}
StyledText {
anchors.verticalCenter: parent.verticalCenter
text: root.section?.items?.length ?? 0

View File

@@ -566,8 +566,9 @@ PanelWindow {
readonly property var _leftSection: topBarContent ? (barWindow.isVertical ? topBarContent.vLeftSection : topBarContent.hLeftSection) : null
readonly property var _centerSection: topBarContent ? (barWindow.isVertical ? topBarContent.vCenterSection : topBarContent.hCenterSection) : null
readonly property var _rightSection: topBarContent ? (barWindow.isVertical ? topBarContent.vRightSection : topBarContent.hRightSection) : null
readonly property real _revealProgress: topBarSlide.x + topBarSlide.y
function sectionRect(section, isCenter) {
function sectionRect(section, isCenter, _dep) {
if (!section)
return {
"x": 0,
@@ -596,7 +597,7 @@ PanelWindow {
item: clickThroughEnabled ? null : inputMask
Region {
readonly property var r: barWindow.clickThroughEnabled ? barWindow.sectionRect(barWindow._leftSection, false) : {
readonly property var r: barWindow.clickThroughEnabled ? barWindow.sectionRect(barWindow._leftSection, false, barWindow._revealProgress) : {
"x": 0,
"y": 0,
"w": 0,
@@ -609,7 +610,7 @@ PanelWindow {
}
Region {
readonly property var r: barWindow.clickThroughEnabled ? barWindow.sectionRect(barWindow._centerSection, true) : {
readonly property var r: barWindow.clickThroughEnabled ? barWindow.sectionRect(barWindow._centerSection, true, barWindow._revealProgress) : {
"x": 0,
"y": 0,
"w": 0,
@@ -622,7 +623,7 @@ PanelWindow {
}
Region {
readonly property var r: barWindow.clickThroughEnabled ? barWindow.sectionRect(barWindow._rightSection, false) : {
readonly property var r: barWindow.clickThroughEnabled ? barWindow.sectionRect(barWindow._rightSection, false, barWindow._revealProgress) : {
"x": 0,
"y": 0,
"w": 0,
@@ -633,6 +634,14 @@ PanelWindow {
width: r.w
height: r.h
}
Region {
readonly property bool active: barWindow.clickThroughEnabled && !inputMask.showing
x: active ? inputMask.x : 0
y: active ? inputMask.y : 0
width: active ? inputMask.width : 0
height: active ? inputMask.height : 0
}
}
Item {
@@ -645,7 +654,7 @@ PanelWindow {
Timer {
id: revealHold
interval: barConfig?.autoHideDelay ?? 250
interval: barWindow.clickThroughEnabled ? Math.max((barConfig?.autoHideDelay ?? 250) * 6, 1500) : (barConfig?.autoHideDelay ?? 250)
repeat: false
onTriggered: {
if (!topBarMouseArea.containsMouse && !topBarCore.hasActivePopout) {
@@ -703,7 +712,6 @@ PanelWindow {
Connections {
function onBarConfigChanged() {
topBarCore.autoHide = barConfig?.autoHide ?? false;
revealHold.interval = barConfig?.autoHideDelay ?? 250;
}
target: rootWindow

View File

@@ -687,6 +687,12 @@ Singleton {
appCategories.forEach(cat => categories.add(cat));
}
// Include categories from core apps (e.g. DMS Settings)
for (const app of coreApps) {
const appCategories = getCategoriesForApp(app);
appCategories.forEach(cat => categories.add(cat));
}
const pluginCategories = getPluginCategories();
pluginCategories.forEach(cat => categories.add(cat));

View File

@@ -19,6 +19,7 @@ Singleton {
readonly property string historyFile: Paths.strip(Paths.cache) + "/notification_history.json"
readonly property string imageCacheDir: Paths.strip(Paths.cache) + "/notification_images"
property bool historyLoaded: false
property int historyEntryCounter: 0
property list<NotifWrapper> notificationQueue: []
property list<NotifWrapper> visibleNotifications: []
@@ -73,6 +74,12 @@ Singleton {
onTriggered: root.performSaveHistory()
}
function _makeHistoryEntryId(sourceId, timestamp) {
historyEntryCounter += 1;
const safeSource = sourceId && sourceId !== "" ? sourceId : "notification";
return safeSource + "_" + (timestamp || Date.now()) + "_" + historyEntryCounter;
}
function getImageCachePath(wrapper) {
const ts = wrapper.time ? wrapper.time.getTime() : Date.now();
const id = wrapper.notification?.id?.toString() || "0";
@@ -80,12 +87,13 @@ Singleton {
}
function updateHistoryImage(wrapperId, imagePath) {
const idx = historyList.findIndex(n => n.id === wrapperId);
const idx = historyList.findIndex(n => n.sourceNotificationId === wrapperId || n.id === wrapperId);
if (idx < 0)
return;
const item = historyList[idx];
const updated = {
id: item.id,
sourceNotificationId: item.sourceNotificationId || item.id,
summary: item.summary,
body: item.body,
htmlBody: item.htmlBody,
@@ -113,8 +121,11 @@ Singleton {
} else if (imageUrl && !imageUrl.startsWith("image://qsimage/")) {
persistableImage = imageUrl;
}
const sourceNotificationId = wrapper.notification?.id?.toString() || "";
const timestamp = wrapper.time.getTime();
const data = {
id: wrapper.notification?.id?.toString() || Date.now().toString(),
id: _makeHistoryEntryId(sourceNotificationId, timestamp),
sourceNotificationId: sourceNotificationId,
summary: wrapper.summary || "",
body: wrapper.body || "",
htmlBody: wrapper.htmlBody || wrapper.body || "",
@@ -122,7 +133,7 @@ Singleton {
appIcon: wrapper.appIcon || "",
image: persistableImage,
urgency: urg,
timestamp: wrapper.time.getTime(),
timestamp: timestamp,
desktopEntry: wrapper.desktopEntry || ""
};
let newList = [data, ...historyList];
@@ -152,6 +163,8 @@ Singleton {
const now = Date.now();
const maxAgeMs = maxAgeDays > 0 ? maxAgeDays * 24 * 60 * 60 * 1000 : 0;
const loaded = [];
const seenIds = {};
let needsRewrite = false;
for (const item of historyAdapter.notifications || []) {
if (maxAgeMs > 0 && (now - item.timestamp) > maxAgeMs)
@@ -162,8 +175,18 @@ Singleton {
if (htmlBody) {
htmlBody = htmlBody.replace(/<img\b[^>]*>/gi, "");
}
const sourceNotificationId = (item.sourceNotificationId || item.id || "").toString();
let historyId = (item.id || "").toString();
if (!historyId || seenIds[historyId]) {
historyId = _makeHistoryEntryId(sourceNotificationId, item.timestamp || now);
needsRewrite = true;
}
if (!item.sourceNotificationId)
needsRewrite = true;
seenIds[historyId] = true;
loaded.push({
id: item.id || "",
id: historyId,
sourceNotificationId: sourceNotificationId,
summary: item.summary || "",
body: body,
htmlBody: htmlBody,
@@ -177,7 +200,7 @@ Singleton {
}
historyList = loaded;
historyLoaded = true;
if (maxAgeMs > 0 && loaded.length !== (historyAdapter.notifications || []).length)
if ((maxAgeMs > 0 && loaded.length !== (historyAdapter.notifications || []).length) || needsRewrite)
saveHistory();
} catch (e) {
console.warn("NotificationService: load history failed:", e);