mirror of
https://github.com/zoriya/noctalia-shell.git
synced 2025-12-06 06:36:15 +00:00
Merge pull request #482 from luleyleo/auto-night-mode
Implement scheduled light/dark mode switching
This commit is contained in:
@@ -500,10 +500,6 @@
|
||||
"label": "Farbquelle",
|
||||
"description": "Haupteinstellungen für Noctalias Farben."
|
||||
},
|
||||
"dark-mode": {
|
||||
"label": "Dunkler Modus",
|
||||
"description": "Wechselt zu einem dunkleren Theme für einfachere Betrachtung bei Nacht."
|
||||
},
|
||||
"use-wallpaper-colors": {
|
||||
"label": "Hintergrundbild-Farben verwenden",
|
||||
"description": "Farbschemata aus Ihrem Hintergrundbild mit Matugen generieren. Extrahiert automatisch Farben für ein kohärentes Design."
|
||||
@@ -513,6 +509,19 @@
|
||||
"description": "Wähle einen Farbstil für Matugen aus."
|
||||
}
|
||||
},
|
||||
"dark-mode": {
|
||||
"switch": {
|
||||
"label": "Dunkler Modus",
|
||||
"description": "Wechselt zu einem dunkleren Theme für einfachere Betrachtung bei Nacht."
|
||||
},
|
||||
"mode": {
|
||||
"label": "Automatischer dunkler Modus",
|
||||
"description": "Ermöglicht automatisches Wechseln zwischen dem hellen und dunklen Modus.",
|
||||
"off": "Aus",
|
||||
"manual": "Manuell",
|
||||
"location": "Standort"
|
||||
}
|
||||
},
|
||||
"predefined": {
|
||||
"section": {
|
||||
"label": "Vordefinierte Farbschemata",
|
||||
|
||||
@@ -500,10 +500,6 @@
|
||||
"label": "Color source",
|
||||
"description": "Main settings for Noctalia's colors."
|
||||
},
|
||||
"dark-mode": {
|
||||
"label": "Dark mode",
|
||||
"description": "Switches to a darker theme for easier viewing at night."
|
||||
},
|
||||
"use-wallpaper-colors": {
|
||||
"label": "Use wallpaper colors",
|
||||
"description": "Generate color schemes from your wallpaper using Matugen. Automatically extracts colors to create a cohesive theme."
|
||||
@@ -513,6 +509,19 @@
|
||||
"description": "Choose the color scheme generation algorithm for Matugen."
|
||||
}
|
||||
},
|
||||
"dark-mode": {
|
||||
"switch": {
|
||||
"label": "Dark mode",
|
||||
"description": "Switches to a darker theme for easier viewing at night."
|
||||
},
|
||||
"mode": {
|
||||
"label": "Dark mode schedule",
|
||||
"description": "Enables automatic switching between light and dark mode.",
|
||||
"off": "Off",
|
||||
"manual": "Manual",
|
||||
"location": "Location"
|
||||
}
|
||||
},
|
||||
"predefined": {
|
||||
"section": {
|
||||
"label": "Predefined color schemes",
|
||||
|
||||
@@ -500,10 +500,6 @@
|
||||
"label": "Fuente de color",
|
||||
"description": "Configuración principal de los colores de Noctalia."
|
||||
},
|
||||
"dark-mode": {
|
||||
"label": "Modo oscuro",
|
||||
"description": "Cambia a un tema más oscuro para una visualización más fácil por la noche."
|
||||
},
|
||||
"use-wallpaper-colors": {
|
||||
"label": "Usar colores del fondo de pantalla",
|
||||
"description": "Generar esquemas de color desde tu fondo de pantalla usando Matugen. Extrae automáticamente colores para crear un tema cohesivo."
|
||||
@@ -513,6 +509,12 @@
|
||||
"description": "Elige el algoritmo de generación de esquema de colores para Matugen."
|
||||
}
|
||||
},
|
||||
"dark-mode": {
|
||||
"switch": {
|
||||
"label": "Modo oscuro",
|
||||
"description": "Cambia a un tema más oscuro para una visualización más fácil por la noche."
|
||||
}
|
||||
},
|
||||
"predefined": {
|
||||
"section": {
|
||||
"label": "Esquemas de colores predefinidos",
|
||||
|
||||
@@ -500,10 +500,6 @@
|
||||
"label": "Source des couleurs",
|
||||
"description": "Paramètres principaux pour les couleurs de Noctalia."
|
||||
},
|
||||
"dark-mode": {
|
||||
"label": "Mode sombre",
|
||||
"description": "Passe à un thème plus sombre pour une visualisation plus facile la nuit."
|
||||
},
|
||||
"use-wallpaper-colors": {
|
||||
"label": "Utiliser les couleurs du fond d'écran",
|
||||
"description": "Générer des schémas de couleurs à partir de votre fond d'écran avec Matugen. Extrait automatiquement les couleurs pour créer un thème cohérent."
|
||||
@@ -513,6 +509,12 @@
|
||||
"description": "Choisissez l'algorithme de génération de schéma de couleurs pour Matugen."
|
||||
}
|
||||
},
|
||||
"dark-mode": {
|
||||
"switch": {
|
||||
"label": "Mode sombre",
|
||||
"description": "Passe à un thème plus sombre pour une visualisation plus facile la nuit."
|
||||
}
|
||||
},
|
||||
"predefined": {
|
||||
"section": {
|
||||
"label": "Jeux de couleurs prédéfinis",
|
||||
|
||||
@@ -462,10 +462,6 @@
|
||||
"label": "Fonte de cor",
|
||||
"description": "Configurações principais para as cores do Noctalia."
|
||||
},
|
||||
"dark-mode": {
|
||||
"label": "Modo escuro",
|
||||
"description": "Muda para um tema mais escuro para facilitar a visualização à noite."
|
||||
},
|
||||
"use-wallpaper-colors": {
|
||||
"label": "Usar cores do papel de parede",
|
||||
"description": "Gerar esquemas de cores do seu papel de parede usando Matugen. Extrai automaticamente cores para criar um tema coeso."
|
||||
@@ -475,6 +471,12 @@
|
||||
"description": "Escolha o algoritmo de geração de esquema de cores para Matugen."
|
||||
}
|
||||
},
|
||||
"dark-mode": {
|
||||
"switch": {
|
||||
"label": "Modo escuro",
|
||||
"description": "Muda para um tema mais escuro para facilitar a visualização à noite."
|
||||
}
|
||||
},
|
||||
"predefined": {
|
||||
"section": {
|
||||
"label": "Esquemas de cores predefinidos",
|
||||
|
||||
@@ -500,10 +500,6 @@
|
||||
"label": "颜色来源",
|
||||
"description": "Noctalia 颜色的主要设置。"
|
||||
},
|
||||
"dark-mode": {
|
||||
"label": "深色模式",
|
||||
"description": "切换到更暗的主题,便于夜间观看。"
|
||||
},
|
||||
"use-wallpaper-colors": {
|
||||
"label": "使用壁纸颜色",
|
||||
"description": "使用 Matugen 从壁纸生成颜色方案。自动提取颜色以创建一致的主题。"
|
||||
@@ -513,6 +509,12 @@
|
||||
"description": "为 Matugen 选择配色方案生成算法。"
|
||||
}
|
||||
},
|
||||
"dark-mode": {
|
||||
"switch": {
|
||||
"label": "深色模式",
|
||||
"description": "切换到更暗的主题,便于夜间观看。"
|
||||
}
|
||||
},
|
||||
"predefined": {
|
||||
"section": {
|
||||
"label": "预定义配色方案",
|
||||
|
||||
@@ -357,6 +357,9 @@ Singleton {
|
||||
property bool useWallpaperColors: false
|
||||
property string predefinedScheme: "Noctalia (default)"
|
||||
property bool darkMode: true
|
||||
property string schedulingMode: "off"
|
||||
property string manualSunrise: "06:30"
|
||||
property string manualSunset: "18:30"
|
||||
property string matugenSchemeType: "scheme-fruit-salad"
|
||||
property bool generateTemplatesForPredefined: true
|
||||
}
|
||||
|
||||
@@ -13,6 +13,24 @@ ColumnLayout {
|
||||
property var schemeColorsCache: ({})
|
||||
property int cacheVersion: 0 // Increment to trigger UI updates
|
||||
|
||||
// Time dropdown options (00:00 .. 23:30)
|
||||
ListModel {
|
||||
id: timeOptions
|
||||
}
|
||||
Component.onCompleted: {
|
||||
for (var h = 0; h < 24; h++) {
|
||||
for (var m = 0; m < 60; m += 30) {
|
||||
var hh = ("0" + h).slice(-2)
|
||||
var mm = ("0" + m).slice(-2)
|
||||
var key = hh + ":" + mm
|
||||
timeOptions.append({
|
||||
"key": key,
|
||||
"name": key
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
spacing: Style.marginL
|
||||
|
||||
// Helper function to extract scheme name from path
|
||||
@@ -138,8 +156,8 @@ ColumnLayout {
|
||||
|
||||
// Dark Mode Toggle
|
||||
NToggle {
|
||||
label: I18n.tr("settings.color-scheme.color-source.dark-mode.label")
|
||||
description: I18n.tr("settings.color-scheme.color-source.dark-mode.description")
|
||||
label: I18n.tr("settings.color-scheme.dark-mode.switch.label")
|
||||
description: I18n.tr("settings.color-scheme.dark-mode.switch.description")
|
||||
checked: Settings.data.colorSchemes.darkMode
|
||||
enabled: true
|
||||
onToggled: checked => {
|
||||
@@ -148,6 +166,77 @@ ColumnLayout {
|
||||
}
|
||||
}
|
||||
|
||||
NComboBox {
|
||||
label: I18n.tr("settings.color-scheme.dark-mode.mode.label")
|
||||
description: I18n.tr("settings.color-scheme.dark-mode.mode.description")
|
||||
|
||||
model: [{
|
||||
"name": I18n.tr("settings.color-scheme.dark-mode.mode.off"),
|
||||
"key": "off"
|
||||
}, {
|
||||
"name": I18n.tr("settings.color-scheme.dark-mode.mode.manual"),
|
||||
"key": "manual"
|
||||
}, {
|
||||
"name": I18n.tr("settings.color-scheme.dark-mode.mode.location"),
|
||||
"key": "location"
|
||||
}]
|
||||
|
||||
currentKey: Settings.data.colorSchemes.schedulingMode
|
||||
|
||||
onSelected: key => {
|
||||
Settings.data.colorSchemes.schedulingMode = key
|
||||
AppThemeService.generate()
|
||||
}
|
||||
}
|
||||
|
||||
// Manual scheduling
|
||||
ColumnLayout {
|
||||
spacing: Style.marginS
|
||||
visible: Settings.data.colorSchemes.schedulingMode === "manual"
|
||||
|
||||
NLabel {
|
||||
label: I18n.tr("settings.display.night-light.manual-schedule.label")
|
||||
description: I18n.tr("settings.display.night-light.manual-schedule.description")
|
||||
}
|
||||
|
||||
RowLayout {
|
||||
Layout.fillWidth: false
|
||||
spacing: Style.marginS
|
||||
|
||||
NText {
|
||||
text: I18n.tr("settings.display.night-light.manual-schedule.sunrise")
|
||||
pointSize: Style.fontSizeM
|
||||
color: Color.mOnSurfaceVariant
|
||||
}
|
||||
|
||||
NComboBox {
|
||||
model: timeOptions
|
||||
currentKey: Settings.data.colorSchemes.manualSunrise
|
||||
placeholder: I18n.tr("settings.display.night-light.manual-schedule.select-start")
|
||||
onSelected: key => Settings.data.colorSchemes.manualSunrise = key
|
||||
minimumWidth: 120
|
||||
}
|
||||
|
||||
Item {
|
||||
Layout.preferredWidth: 20
|
||||
}
|
||||
|
||||
NText {
|
||||
text: I18n.tr("settings.display.night-light.manual-schedule.sunset")
|
||||
pointSize: Style.fontSizeM
|
||||
color: Color.mOnSurfaceVariant
|
||||
}
|
||||
|
||||
NComboBox {
|
||||
model: timeOptions
|
||||
currentKey: Settings.data.colorSchemes.manualSunset
|
||||
placeholder: I18n.tr("settings.display.night-light.manual-schedule.select-stop")
|
||||
onSelected: key => Settings.data.colorSchemes.manualSunset = key
|
||||
minimumWidth: 120
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Use Wallpaper Colors
|
||||
NToggle {
|
||||
label: I18n.tr("settings.color-scheme.color-source.use-wallpaper-colors.label")
|
||||
|
||||
169
Services/DarkModeService.qml
Normal file
169
Services/DarkModeService.qml
Normal file
@@ -0,0 +1,169 @@
|
||||
pragma Singleton
|
||||
|
||||
import QtQuick
|
||||
import Quickshell
|
||||
import qs.Commons
|
||||
import qs.Services
|
||||
|
||||
Singleton {
|
||||
id: root
|
||||
|
||||
property bool initComplete: false
|
||||
property bool nextDarkModeState: false
|
||||
|
||||
Connections {
|
||||
target: LocationService.data
|
||||
enabled: Settings.data.colorSchemes.schedulingMode == "location"
|
||||
function onWeatherChanged() {
|
||||
if (LocationService.data.weather !== null) {
|
||||
const changes = root.collectWeatherChanges(LocationService.data.weather)
|
||||
if (!root.initComplete) {
|
||||
root.initComplete = true
|
||||
root.applyCurrentMode(changes)
|
||||
}
|
||||
root.scheduleNextMode(changes)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Connections {
|
||||
target: Settings.data.colorSchemes
|
||||
enabled: Settings.data.colorSchemes.schedulingMode == "manual"
|
||||
function onManualSunriseChanged() {
|
||||
const changes = root.collectManualChanges()
|
||||
root.applyCurrentMode(changes)
|
||||
root.scheduleNextMode(changes)
|
||||
}
|
||||
function onManualSunsetChanged() {
|
||||
const changes = root.collectManualChanges()
|
||||
root.applyCurrentMode(changes)
|
||||
root.scheduleNextMode(changes)
|
||||
}
|
||||
}
|
||||
|
||||
Connections {
|
||||
target: Settings.data.colorSchemes
|
||||
function onSchedulingModeChanged() {
|
||||
root.init()
|
||||
}
|
||||
}
|
||||
|
||||
Timer {
|
||||
id: timer
|
||||
onTriggered: {
|
||||
Settings.data.colorSchemes.darkMode = root.nextDarkModeState
|
||||
if (LocationService.data.weather !== null) {
|
||||
const changes = root.collectWeatherChanges(LocationService.data.weather)
|
||||
root.scheduleNextMode(changes)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function init() {
|
||||
Logger.i("DarkModeService", "Service started")
|
||||
|
||||
if (Settings.data.colorSchemes.schedulingMode == "manual") {
|
||||
const changes = collectManualChanges()
|
||||
initComplete = true
|
||||
applyCurrentMode(changes)
|
||||
scheduleNextMode(changes)
|
||||
}
|
||||
|
||||
if (Settings.data.colorSchemes.schedulingMode == "location" && LocationService.data.weather) {
|
||||
const changes = collectWeatherChanges(LocationService.data.weather)
|
||||
initComplete = true
|
||||
applyCurrentMode(changes)
|
||||
scheduleNextMode(changes)
|
||||
}
|
||||
}
|
||||
|
||||
function parseTime(timeString) {
|
||||
const parts = timeString.split(":").map(Number)
|
||||
return {
|
||||
"hour": parts[0],
|
||||
"minute": parts[1]
|
||||
}
|
||||
}
|
||||
|
||||
function collectManualChanges() {
|
||||
const sunriseTime = parseTime(Settings.data.colorSchemes.manualSunrise)
|
||||
const sunsetTime = parseTime(Settings.data.colorSchemes.manualSunset)
|
||||
|
||||
const now = new Date()
|
||||
const year = now.getFullYear()
|
||||
const month = now.getMonth()
|
||||
const day = now.getDate()
|
||||
|
||||
const yesterdaysSunset = new Date(year, month, day - 1, sunsetTime.hour, sunsetTime.minute)
|
||||
const todaysSunrise = new Date(year, month, day, sunriseTime.hour, sunriseTime.minute)
|
||||
const todaysSunset = new Date(year, month, day, sunsetTime.hour, sunsetTime.minute)
|
||||
const tomorrowsSunrise = new Date(year, month, day + 1, sunriseTime.hour, sunriseTime.minute)
|
||||
|
||||
return [{
|
||||
"time": yesterdaysSunset.getTime(),
|
||||
"darkMode": true
|
||||
}, {
|
||||
"time": todaysSunrise.getTime(),
|
||||
"darkMode": false
|
||||
}, {
|
||||
"time": todaysSunset.getTime(),
|
||||
"darkMode": true
|
||||
}, {
|
||||
"time": tomorrowsSunrise.getTime(),
|
||||
"darkMode": false
|
||||
}]
|
||||
}
|
||||
|
||||
function collectWeatherChanges(weather) {
|
||||
const changes = []
|
||||
|
||||
if (Date.now() < Date.parse(weather.daily.sunrise[0])) {
|
||||
// The sun has not risen yet
|
||||
changes.push({
|
||||
"time": Date.now() - 1,
|
||||
"darkMode": true
|
||||
})
|
||||
}
|
||||
|
||||
for (var i = 0; i < weather.daily.sunrise.length; i++) {
|
||||
changes.push({
|
||||
"time": Date.parse(weather.daily.sunrise[i]),
|
||||
"darkMode": false
|
||||
})
|
||||
changes.push({
|
||||
"time": Date.parse(weather.daily.sunset[i]),
|
||||
"darkMode": true
|
||||
})
|
||||
}
|
||||
|
||||
return changes
|
||||
}
|
||||
|
||||
function applyCurrentMode(changes) {
|
||||
const now = Date.now()
|
||||
|
||||
// changes.findLast(change => change.time < now) // not available in QML...
|
||||
let lastChange = null
|
||||
for (var i = 0; i < changes.length; i++) {
|
||||
if (changes[i].time < now) {
|
||||
lastChange = changes[i]
|
||||
}
|
||||
}
|
||||
|
||||
if (lastChange) {
|
||||
Settings.data.colorSchemes.darkMode = lastChange.darkMode
|
||||
Logger.d("DarkModeService", `Reset: darkmode=${lastChange.darkMode}`)
|
||||
}
|
||||
}
|
||||
|
||||
function scheduleNextMode(changes) {
|
||||
const now = Date.now()
|
||||
const nextChange = changes.find(change => change.time > now)
|
||||
if (nextChange) {
|
||||
root.nextDarkModeState = nextChange.darkMode
|
||||
timer.interval = nextChange.time - now
|
||||
timer.restart()
|
||||
Logger.d("DarkModeService", `Scheduled: darkmode=${nextChange.darkMode} in ${timer.interval} ms`)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -69,7 +69,7 @@ Singleton {
|
||||
Timer {
|
||||
id: updateTimer
|
||||
interval: 20 * 1000
|
||||
running: Settings.data.location.weatherEnabled
|
||||
running: Settings.data.location.weatherEnabled || Settings.data.colorSchemes.schedulingMode == "location"
|
||||
repeat: true
|
||||
onTriggered: {
|
||||
updateWeather()
|
||||
@@ -190,7 +190,7 @@ Singleton {
|
||||
// --------------------------------
|
||||
function _fetchWeather(latitude, longitude, errorCallback) {
|
||||
Logger.d("Location", "Fetching weather from api.open-meteo.com")
|
||||
var url = "https://api.open-meteo.com/v1/forecast?latitude=" + latitude + "&longitude=" + longitude + "¤t_weather=true¤t=relativehumidity_2m,surface_pressure&daily=temperature_2m_max,temperature_2m_min,weathercode&timezone=auto"
|
||||
var url = "https://api.open-meteo.com/v1/forecast?latitude=" + latitude + "&longitude=" + longitude + "¤t_weather=true¤t=relativehumidity_2m,surface_pressure&daily=temperature_2m_max,temperature_2m_min,weathercode,sunset,sunrise&timezone=auto"
|
||||
var xhr = new XMLHttpRequest()
|
||||
xhr.onreadystatechange = function () {
|
||||
if (xhr.readyState === XMLHttpRequest.DONE) {
|
||||
|
||||
Reference in New Issue
Block a user