import { DOCUMENT } from '@angular/common'; import { computed, Inject, Injectable, signal } from '@angular/core'; import { OpenViduThemeMode, OpenViduThemeService } from 'openvidu-components-angular'; export type Theme = 'light' | 'dark'; @Injectable({ providedIn: 'root' }) export class ThemeService { private readonly THEME_KEY = 'ovMeet-theme'; private readonly _currentTheme = signal('light'); // Computed signals for reactivity public readonly currentTheme = computed(() => this._currentTheme()); public readonly isDark = computed(() => this._currentTheme() === 'dark'); public readonly isLight = computed(() => this._currentTheme() === 'light'); constructor(@Inject(DOCUMENT) private document: Document, protected ovComponentsThemeService: OpenViduThemeService) {} /** * Initializes the theme based on: * 1. Saved preference in localStorage * 2. System preference (prefers-color-scheme) * 3. Light theme as default */ initializeTheme(): void { const savedTheme = this.getSavedTheme(); const systemPreference = this.getSystemPreference(); const initialTheme = savedTheme || systemPreference || 'light'; this.setTheme(initialTheme); this.ovComponentsThemeService.setTheme(initialTheme as OpenViduThemeMode); this.listenToSystemChanges(); } /** * Toggles between light and dark theme */ public toggleTheme(): void { const newTheme: Theme = this._currentTheme() === 'light' ? 'dark' : 'light'; this.setTheme(newTheme); } /** * Changes the current theme */ private setTheme(theme: Theme): void { this._currentTheme.set(theme); this.applyThemeToDocument(theme); this.saveThemePreference(theme); this.ovComponentsThemeService.setTheme(theme as OpenViduThemeMode); } /** * Applies the theme to the document */ private applyThemeToDocument(theme: Theme): void { const htmlElement = this.document.documentElement; if (theme === 'dark') { htmlElement.setAttribute('data-theme', 'dark'); } else { htmlElement.removeAttribute('data-theme'); } } /** * Saves the theme preference in localStorage */ private saveThemePreference(theme: Theme): void { try { localStorage.setItem(this.THEME_KEY, theme); } catch (error) { console.warn('Could not save theme preference:', error); } } /** * Gets the saved preference from localStorage */ private getSavedTheme(): Theme | null { try { const saved = localStorage.getItem(this.THEME_KEY); return saved === 'dark' || saved === 'light' ? saved : null; } catch (error) { console.warn('Could not read theme preference:', error); return null; } } /** * Gets the system preference */ private getSystemPreference(): Theme { if (typeof window !== 'undefined' && window.matchMedia) { return window.matchMedia('(prefers-color-scheme: dark)').matches ? 'dark' : 'light'; } return 'light'; } /** * Listens to system preference changes */ private listenToSystemChanges(): void { if (typeof window !== 'undefined' && window.matchMedia) { const mediaQuery = window.matchMedia('(prefers-color-scheme: dark)'); // Only update if there's no saved preference mediaQuery.addEventListener('change', (e) => { if (!this.getSavedTheme()) { this.setTheme(e.matches ? 'dark' : 'light'); } }); } } /** * Resets to system preference */ public resetToSystemPreference(): void { try { localStorage.removeItem(this.THEME_KEY); } catch (error) { console.warn('Could not remove theme preference:', error); } const systemTheme = this.getSystemPreference(); this.setTheme(systemTheme); } }