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 40x 40x   40x 71x     40x 34x 34x 34x 34x       40x 40x         3x       9x       40x       40x 40x 27x     13x       13x     13x       34x   34x         34x 34x 34x         34x       40x   40x                  
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');
            }
        });
    }
}