131 lines
3.5 KiB
TypeScript

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<Theme>('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);
}
}