Files
cbsnexora/src/app/shared/theme-toggle/theming.service.ts
T

89 lines
3.2 KiB
TypeScript

/**
* Copyright since 2025 Mifos Initiative
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
*/
import { ApplicationRef, Injectable, inject } from '@angular/core';
import { BehaviorSubject } from 'rxjs';
@Injectable({
providedIn: 'root'
})
export class ThemingService {
private ref = inject(ApplicationRef);
private darkModeOn = false;
themes = [
'dark-theme',
'light-theme'
]; // <- list all themes in this array
theme = new BehaviorSubject('light-theme'); // <- initial theme
constructor() {
// The user's own choice, kept in localStorage by the theme toggle, is the only thing that
// decides the theme.
//
// The OS preference used to decide it instead, and a media-query listener kept it in step with
// Windows. That is what turned a page black after printing: Chrome re-evaluates
// prefers-color-scheme around the print dialog, the listener fired on the way back, and the app
// flipped to dark against the user's saved choice — on every screen, and only after a print,
// which is exactly how it was reported. A member of staff who set the app light does not expect
// printing a report to change it, and the toggle they used had no way to win against the OS.
//
// Nothing here reads the OS any more, so there is no listener to fire and nothing to fight the
// toggle. isDarkMode() still reports the OS preference for the one caller that offers it as a
// default on first use.
this.darkModeOn = !!this.storedPreference();
this.setDarkMode(this.darkModeOn);
}
/** The saved choice, or null when the user has never set one. */
private storedPreference(): boolean | null {
try {
const raw = localStorage.getItem('mifosXThemeDarkEnabled');
return raw === null ? null : JSON.parse(raw);
} catch {
// A blocked or corrupt localStorage must not stop the app from rendering; light is the
// default the rest of the stylesheet already assumes.
return null;
}
}
/**
* Whether the app is currently in dark mode.
*
* Reports the app's own state, not the OS preference. It used to read the media query and assign
* the result to darkModeOn on the way past — so merely asking the question overwrote the user's
* saved choice, and setInitialDarkMode() then applied whatever the OS happened to say.
*/
isDarkMode(): boolean {
return this.darkModeOn;
}
/** The OS preference, offered as a starting point when the user has never chosen for themselves. */
isSystemDarkMode(): boolean {
return !!(window.matchMedia && window.matchMedia('(prefers-color-scheme: dark)').matches);
}
setDarkMode(isDarkMode: boolean) {
this.darkModeOn = isDarkMode;
if (isDarkMode) {
document.body.classList.add('dark-theme');
document.body.classList.remove('light-theme');
this.theme.next('dark-theme');
} else {
document.body.classList.add('light-theme');
document.body.classList.remove('dark-theme');
this.theme.next('light-theme');
}
}
setInitialDarkMode(): void {
this.setDarkMode(this.darkModeOn);
}
}