pragma Singleton import Quickshell import QtQuick import qs.config import "ThemeProfileModel.js" as ThemeProfileModel Singleton { id: root readonly property var customProfiles: ThemeProfileModel.validCustomProfiles( DesktopPreferences.get("themeProfiles")) readonly property var curatedAccents: ThemeProfileModel.curatedAccents() readonly property var profiles: ThemeProfileModel.profileCatalog(root.customProfiles) readonly property string activeId: DesktopPreferences.get("themeProfileId") || "moon" readonly property var activeProfile: ThemeProfileModel.findProfile( root.customProfiles, root.activeId) || ThemeProfileModel.shippedProfiles()[0] readonly property string activeAccentName: ThemeProfileModel.curatedNameForProfile( root.activeProfile) property bool reconciling: false function commitActive(profile: var): bool { if (!profile) return false; // Scheme first: if another control changed it, the reconciliation hook // may briefly select Day/Moon before this final stable profile id lands. const schemeAccepted = SystemSettings.commitPreference("colorScheme", profile.scheme); const profileAccepted = SystemSettings.commitPreference("themeProfileId", profile.id); return schemeAccepted && profileAccepted; } function selectProfile(id: string): bool { return root.commitActive(ThemeProfileModel.findProfile(root.customProfiles, id)); } // Editing a shipped profile creates a saved custom copy. Editing a custom // profile updates only that record. ThemeProfileModel enforces both rules, // leaving this singleton responsible only for validated preference routing. function setAccentPair(accent: string, secondary: string): bool { const result = ThemeProfileModel.editProfile(root.customProfiles, root.activeProfile, { accent: accent, secondary: secondary }); if (!result.profile) return false; if (!SystemSettings.commitPreference("themeProfiles", result.profiles)) return false; return SystemSettings.commitPreference("themeProfileId", result.profile.id); } function saveProfile(name: string): bool { const result = ThemeProfileModel.createCustomProfile(root.customProfiles, { name: name, scheme: root.activeProfile.scheme, accent: root.activeProfile.accent, secondary: root.activeProfile.secondary }); if (!result.profile) return false; if (!SystemSettings.commitPreference("themeProfiles", result.profiles)) return false; return SystemSettings.commitPreference("themeProfileId", result.profile.id); } function deleteProfile(id: string): bool { const result = ThemeProfileModel.deleteProfile(root.customProfiles, id); if (!result.removed) return false; if (!SystemSettings.commitPreference("themeProfiles", result.profiles)) return false; if (root.activeId === id) return root.selectProfile(DesktopPreferences.get("colorScheme") === "light" ? "day" : "moon"); return true; } function useCuratedAccent(name: string): bool { const scheme = DesktopPreferences.get("colorScheme") === "light" ? "light" : "dark"; const pair = ThemeProfileModel.curatedPair(name, scheme); const shipped = ThemeProfileModel.matchingShippedProfile( scheme, pair.accent, pair.secondary); const accepted = shipped ? root.commitActive(shipped) : root.setAccentPair(pair.accent, pair.secondary); if (accepted) SystemSettings.commitPreference("accentName", name); return accepted; } function reconcileScheme(): void { if (root.reconciling) return; const scheme = DesktopPreferences.get("colorScheme") === "light" ? "light" : "dark"; if (root.activeProfile.scheme === scheme) return; root.reconciling = true; SystemSettings.commitPreference("themeProfileId", scheme === "light" ? "day" : "moon"); root.reconciling = false; } Component.onCompleted: root.reconcileScheme() Connections { target: DesktopPreferences function onRevisionChanged(): void { root.reconcileScheme(); } } }