diff --git a/LOTUS_FEATURES.md b/LOTUS_FEATURES.md index 8bb25f6d8..bd8ad7690 100644 --- a/LOTUS_FEATURES.md +++ b/LOTUS_FEATURES.md @@ -294,6 +294,7 @@ A warm orange overlay rendered over the entire UI to reduce blue light emission. - CSS: `position: fixed; inset: 0; pointer-events: none; z-index: 9998` - Orange tint color with configurable opacity - **Controls:** Toggle to enable/disable + intensity slider ranging from 5% to 80% opacity +- **Schedule (auto on at night):** an optional schedule with From/To time inputs; when on, the overlay only shows during that window and turns itself on/off automatically (the overlay re-checks every minute, no reload). Overnight windows that wrap midnight (e.g. 21:00 → 07:00) are handled. Window logic is the pure, unit-tested `isWithinTimeWindow`/`parseHHMM` in `src/app/utils/timeWindow.ts` (`timeWindow.test.ts`). Defaults: 21:00–07:00. - Settings persisted via the standard Lotus settings store --- diff --git a/src/app/features/settings/general/General.tsx b/src/app/features/settings/general/General.tsx index 8d660baf2..193cd1e9a 100644 --- a/src/app/features/settings/general/General.tsx +++ b/src/app/features/settings/general/General.tsx @@ -96,6 +96,7 @@ import { useThemes, } from '../../../hooks/useTheme'; import { stopPropagation } from '../../../utils/keyboard'; +import { pickerInputStyle } from '../../../utils/datetimeInput'; import { BG_OPTIONS, getChatBg } from '../../lotus/chatBackground'; import { resetBootSequence, runLotusBootSequence } from '../../../../lotus-boot'; import { useMessageLayoutItems } from '../../../hooks/useMessageLayout'; @@ -435,6 +436,12 @@ function Appearance() { const [lotusTerminal, setLotusTerminal] = useSetting(settingsAtom, 'lotusTerminal'); const [nightLightEnabled, setNightLightEnabled] = useSetting(settingsAtom, 'nightLightEnabled'); const [nightLightOpacity, setNightLightOpacity] = useSetting(settingsAtom, 'nightLightOpacity'); + const [nightLightSchedule, setNightLightSchedule] = useSetting( + settingsAtom, + 'nightLightSchedule', + ); + const [nightLightStart, setNightLightStart] = useSetting(settingsAtom, 'nightLightStart'); + const [nightLightEnd, setNightLightEnd] = useSetting(settingsAtom, 'nightLightEnd'); const [glassmorphismSidebar, setGlassmorphismSidebar] = useSetting( settingsAtom, 'glassmorphismSidebar', @@ -567,23 +574,66 @@ function Appearance() { {nightLightEnabled && ( - - Intensity: {nightLightOpacity}% - - ) => - setNightLightOpacity(parseInt(e.target.value, 10)) - } - style={{ width: '100%', accentColor: color.Primary.Main }} - /> + + + Intensity: {nightLightOpacity}% + + ) => + setNightLightOpacity(parseInt(e.target.value, 10)) + } + style={{ width: '100%', accentColor: color.Primary.Main }} + /> + + + + Schedule + + Only tint during the hours below + + + + + {nightLightSchedule && ( + + + + From + + setNightLightStart(e.target.value)} + style={pickerInputStyle(color, config)} + /> + + + + To + + setNightLightEnd(e.target.value)} + style={pickerInputStyle(color, config)} + /> + + + )} )} diff --git a/src/app/pages/App.tsx b/src/app/pages/App.tsx index 0632a2777..36abe4ae4 100644 --- a/src/app/pages/App.tsx +++ b/src/app/pages/App.tsx @@ -1,4 +1,4 @@ -import React, { ReactNode, useEffect } from 'react'; +import React, { ReactNode, useEffect, useState } from 'react'; import { ErrorBoundary } from 'react-error-boundary'; import { Provider as JotaiProvider, useAtomValue } from 'jotai'; import { @@ -23,6 +23,7 @@ import { createRouter } from './Router'; import { ScreenSizeProvider, useScreenSize } from '../hooks/useScreenSize'; import { useCompositionEndTracking } from '../hooks/useComposingCheck'; import { settingsAtom } from '../state/settings'; +import { isWithinTimeWindow } from '../utils/timeWindow'; import { LotusToastContainer } from '../features/toast/LotusToastContainer'; import { useTauriNotificationBadge } from '../hooks/useTauriNotificationBadge'; import { useTauriWindowChrome } from '../hooks/useTauriWindowChrome'; @@ -128,7 +129,23 @@ function DesktopChrome({ children }: { children: ReactNode }) { function NightLightOverlay() { const settings = useAtomValue(settingsAtom); + const scheduled = settings.nightLightEnabled && settings.nightLightSchedule; + // When scheduled, re-evaluate the active window every minute so the overlay + // turns on/off automatically at the start/end times without a reload. + const [, forceTick] = useState(0); + useEffect(() => { + if (!scheduled) return undefined; + const id = setInterval(() => forceTick((n) => n + 1), 60_000); + return () => clearInterval(id); + }, [scheduled]); + if (!settings.nightLightEnabled) return null; + if ( + settings.nightLightSchedule && + !isWithinTimeWindow(settings.nightLightStart, settings.nightLightEnd) + ) { + return null; + } return (