All files / app/layout/services theme.service.ts

81.39% Statements 35/43
56.52% Branches 13/23
91.66% Functions 11/12
80% Lines 32/40

Press n or j to go to the next uncovered block, b, p or k for the previous block.

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 905x 5x       5x         5x 66x 66x   66x 129x     66x 60x 60x 60x 60x       66x 66x         3x       9x       66x       66x 66x 53x     13x       13x     13x       60x   60x         60x 60x 60x         60x       66x   66x                  
import { isPlatformBrowser } from '@angular/common';
import { Injectable, signal, effect, PLATFORM_ID, inject } from '@angular/core';
 
export type Theme = 'light' | 'dark';
 
const THEME_KEY = 'drevo-theme';
 
@Injectable({
    providedIn: 'root',
})
export class ThemeService {
    private readonly platformId = inject(PLATFORM_ID);
    private readonly isBrowser = isPlatformBrowser(this.platformId);
 
    readonly theme = signal<Theme>(this.getInitialTheme());
    readonly isDark = () => this.theme() === 'dark';
 
    constructor() {
        effect(() => {
            const currentTheme = this.theme();
            Eif (this.isBrowser) {
                this.applyTheme(currentTheme);
                this.saveTheme(currentTheme);
            }
        });
 
        Eif (this.isBrowser) {
            this.listenToSystemThemeChanges();
        }
    }
 
    toggleTheme(): void {
        this.theme.update(current => (current === 'light' ? 'dark' : 'light'));
    }
 
    setTheme(theme: Theme): void {
        this.theme.set(theme);
    }
 
    private getInitialTheme(): Theme {
        Iif (!this.isBrowser) {
            return 'light';
        }
 
        const savedTheme = localStorage.getItem(THEME_KEY) as Theme | null;
        if (savedTheme && (savedTheme === 'light' || savedTheme === 'dark')) {
            return savedTheme;
        }
 
        return this.getSystemTheme();
    }
 
    private getSystemTheme(): Theme {
        Iif (this.isBrowser && window.matchMedia('(prefers-color-scheme: dark)').matches) {
            return 'dark';
        }
        return 'light';
    }
 
    private applyTheme(theme: Theme): void {
        const root = document.documentElement;
 
        Iif (theme === 'dark') {
            root.classList.add('dark-theme');
            root.classList.remove('light-theme');
            root.style.colorScheme = 'dark';
        } else {
            root.classList.add('light-theme');
            root.classList.remove('dark-theme');
            root.style.colorScheme = 'light';
        }
    }
 
    private saveTheme(theme: Theme): void {
        localStorage.setItem(THEME_KEY, theme);
    }
 
    private listenToSystemThemeChanges(): void {
        const mediaQuery = window.matchMedia('(prefers-color-scheme: dark)');
 
        mediaQuery.addEventListener('change', e => {
            // Apply system theme only if user hasn't explicitly chosen one
            const savedTheme = localStorage.getItem(THEME_KEY);
            if (!savedTheme) {
                this.theme.set(e.matches ? 'dark' : 'light');
            }
        });
    }
}