import { Injectable, signal, effect } from '@angular/core'; @Injectable({ providedIn: 'root' }) export class SettingsService { /** Default mode for viewing songs: true = chords, false = text only */ public showChordsDefault = signal(true); /** UI Fullscreen mode (lyrics only): true = active */ public fullscreenMode = signal(false); /** Browser Fullscreen state: true = fullscreen active */ public browserFullscreen = signal( typeof document !== 'undefined' && !!( document.fullscreenElement || (document as any).webkitFullscreenElement || (document as any).mozFullScreenElement || (document as any).msFullscreenElement ) ); /** Avanzamento automatico: true = passa al brano successivo automaticamente */ public autoAdvance = signal(true); /** Editor e Canti Personali: true = mostra funzioni aggiunta e lista "Miei" */ public showEditor = signal(false); /** Schermo sempre acceso: true = attiva Screen Wake Lock */ public keepScreenOn = signal(true); /** Funzionalità Comunità: true = il chip comunità è visibile nella home */ public comunitaEnabled = signal(false); /** Invio dati statistici: true = invia pacchetto dati statistici */ public invioDatiStatistici = signal(false); /** Visualizza tag sotto autore nella lista canti: true = attivo */ public showTagsInList = signal(true); /** Visualizza data update sotto autore nella lista canti: true = attivo */ public showUpdateDate = signal(true); /** Attiva autoscroll standard nel dettaglio canto: true = attivo */ public enableStandardAutoscroll = signal(true); /** Attiva autoscroll acustico nel dettaglio canto: true = attivo */ public enableAcousticAutoscroll = signal(false); private wakeLock: any = null; // PWA installation signals public deferredPrompt = signal(null); public showInstallButton = signal(false); public isStandalone = signal(false); public isIos = signal(false); public isAndroid = signal(false); constructor() { // Detect PWA status try { this.isStandalone.set( window.matchMedia('(display-mode: standalone)').matches || (window.navigator as any).standalone === true ); this.isIos.set( /iPad|iPhone|iPod/.test(navigator.userAgent) && !(window as any).MSStream ); this.isAndroid.set( /Android/i.test(navigator.userAgent) ); } catch (e) { console.warn('Browser environment does not support display-mode media query'); } window.addEventListener('beforeinstallprompt', (e: any) => { // Prevent the mini-infobar from appearing on mobile e.preventDefault(); // Stash the event so it can be triggered later. this.deferredPrompt.set(e); // Update UI notify the user they can install the PWA this.showInstallButton.set(true); }); window.addEventListener('appinstalled', () => { this.deferredPrompt.set(null); this.showInstallButton.set(false); this.isStandalone.set(true); console.log('PWA was installed'); }); // Migration: force settings defaults once for existing users to match the new rules const migrationKey = 'defaults-migrated-20260521'; if (localStorage.getItem(migrationKey) !== 'true') { localStorage.setItem('show-chords-default', 'true'); localStorage.setItem('fullscreen-mode', this.isIos().toString()); localStorage.setItem('show-editor', 'false'); localStorage.setItem('auto-advance', 'true'); localStorage.setItem('keep-screen-on', 'true'); localStorage.setItem('comunita-enabled', 'false'); localStorage.setItem('invio-dati-statistici', 'false'); localStorage.setItem('show-tags-in-list', 'true'); localStorage.setItem('show-update-date', 'true'); localStorage.setItem('enable-standard-autoscroll', 'true'); localStorage.setItem('enable-acoustic-autoscroll', 'false'); // ThemeService high contrast default localStorage.setItem('high-contrast', 'true'); localStorage.setItem(migrationKey, 'true'); } const savedChords = localStorage.getItem('show-chords-default'); if (savedChords !== null) { this.showChordsDefault.set(savedChords === 'true'); } else { this.showChordsDefault.set(true); } const savedFullscreen = localStorage.getItem('fullscreen-mode'); if (savedFullscreen !== null) { this.fullscreenMode.set(savedFullscreen === 'true'); } else { this.fullscreenMode.set(this.isIos()); } const savedEditor = localStorage.getItem('show-editor'); if (savedEditor !== null) { this.showEditor.set(savedEditor === 'true'); } else { this.showEditor.set(false); } const savedAutoAdvance = localStorage.getItem('auto-advance'); if (savedAutoAdvance !== null) { this.autoAdvance.set(savedAutoAdvance === 'true'); } else { this.autoAdvance.set(true); } const savedKeepScreenOn = localStorage.getItem('keep-screen-on'); if (savedKeepScreenOn !== null) { this.keepScreenOn.set(savedKeepScreenOn === 'true'); } else { this.keepScreenOn.set(true); } const savedComunitaEnabled = localStorage.getItem('comunita-enabled'); if (savedComunitaEnabled !== null) { this.comunitaEnabled.set(savedComunitaEnabled === 'true'); } else { this.comunitaEnabled.set(false); } const savedStats = localStorage.getItem('invio-dati-statistici'); if (savedStats !== null) { this.invioDatiStatistici.set(savedStats === 'true'); } else { this.invioDatiStatistici.set(false); } const savedShowTags = localStorage.getItem('show-tags-in-list'); if (savedShowTags !== null) { this.showTagsInList.set(savedShowTags === 'true'); } else { this.showTagsInList.set(true); } const savedShowUpdateDate = localStorage.getItem('show-update-date'); if (savedShowUpdateDate !== null) { this.showUpdateDate.set(savedShowUpdateDate === 'true'); } else { this.showUpdateDate.set(true); } const savedStandardAutoscroll = localStorage.getItem('enable-standard-autoscroll'); if (savedStandardAutoscroll !== null) { this.enableStandardAutoscroll.set(savedStandardAutoscroll === 'true'); } else { this.enableStandardAutoscroll.set(true); } const savedAcousticAutoscroll = localStorage.getItem('enable-acoustic-autoscroll'); if (savedAcousticAutoscroll !== null) { this.enableAcousticAutoscroll.set(savedAcousticAutoscroll === 'true'); } else { this.enableAcousticAutoscroll.set(false); } // Sync browser fullscreen state with listeners (supporting vendor prefixes) const updateFullscreenState = () => { const isFs = !!( document.fullscreenElement || (document as any).webkitFullscreenElement || (document as any).mozFullScreenElement || (document as any).msFullscreenElement ); this.browserFullscreen.set(isFs); }; document.addEventListener('fullscreenchange', updateFullscreenState); document.addEventListener('webkitfullscreenchange', updateFullscreenState); document.addEventListener('mozfullscreenchange', updateFullscreenState); document.addEventListener('MSFullscreenChange', updateFullscreenState); effect(() => { localStorage.setItem('show-chords-default', this.showChordsDefault().toString()); }); effect(() => { const mode = this.fullscreenMode(); localStorage.setItem('fullscreen-mode', mode.toString()); const isFs = !!( document.fullscreenElement || (document as any).webkitFullscreenElement || (document as any).mozFullScreenElement || (document as any).msFullscreenElement ); if (mode && !isFs) { const docEl = document.documentElement as any; if (docEl.requestFullscreen) { docEl.requestFullscreen().catch((err: any) => console.log('Request fs ignored', err)); } else if (docEl.webkitRequestFullscreen) { docEl.webkitRequestFullscreen(); } } else if (!mode && isFs) { const doc = document as any; if (doc.exitFullscreen) { doc.exitFullscreen().catch((err: any) => console.log('Exit fs ignored', err)); } else if (doc.webkitExitFullscreen) { doc.webkitExitFullscreen(); } } }); effect(() => { localStorage.setItem('auto-advance', this.autoAdvance().toString()); }); effect(() => { localStorage.setItem('show-tags-in-list', this.showTagsInList().toString()); }); effect(() => { localStorage.setItem('show-update-date', this.showUpdateDate().toString()); }); effect(() => { localStorage.setItem('enable-standard-autoscroll', this.enableStandardAutoscroll().toString()); }); effect(() => { localStorage.setItem('enable-acoustic-autoscroll', this.enableAcousticAutoscroll().toString()); }); effect(() => { const active = this.keepScreenOn(); localStorage.setItem('keep-screen-on', active.toString()); if (active) { this.requestWakeLock(); } else { this.releaseWakeLock(); } }); // Re-request on visibility change document.addEventListener('visibilitychange', () => { if (this.keepScreenOn() && document.visibilityState === 'visible') { this.requestWakeLock(); } }); } setShowChordsDefault(val: boolean) { this.showChordsDefault.set(val); } toggleFullscreenMode() { this.fullscreenMode.update(v => !v); } toggleAutoAdvance() { this.autoAdvance.update(v => !v); } toggleEditor() { const newValue = !this.showEditor(); this.showEditor.set(newValue); localStorage.setItem('show-editor', newValue.toString()); } toggleKeepScreenOn() { this.keepScreenOn.update(v => !v); } toggleComunitaEnabled() { const newValue = !this.comunitaEnabled(); this.comunitaEnabled.set(newValue); localStorage.setItem('comunita-enabled', newValue.toString()); } private async requestWakeLock() { if ('wakeLock' in navigator) { try { this.wakeLock = await (navigator as any).wakeLock.request('screen'); } catch (err: any) { console.error(`Wake Lock error: ${err.name}, ${err.message}`); } } } private releaseWakeLock() { if (this.wakeLock) { this.wakeLock.release(); this.wakeLock = null; } } toggleBrowserFullscreen() { const isFs = !!( document.fullscreenElement || (document as any).webkitFullscreenElement || (document as any).mozFullScreenElement || (document as any).msFullscreenElement ); if (!isFs) { const docEl = document.documentElement as any; if (docEl.requestFullscreen) { docEl.requestFullscreen().catch((err: any) => console.error(err)); } else if (docEl.webkitRequestFullscreen) { docEl.webkitRequestFullscreen(); } else if (docEl.mozRequestFullScreen) { docEl.mozRequestFullScreen(); } else if (docEl.msRequestFullscreen) { docEl.msRequestFullscreen(); } } else { const doc = document as any; if (doc.exitFullscreen) { doc.exitFullscreen().catch((err: any) => console.error(err)); } else if (doc.webkitExitFullscreen) { doc.webkitExitFullscreen(); } else if (doc.mozCancelFullScreen) { doc.mozCancelFullScreen(); } else if (doc.msExitFullscreen) { doc.msExitFullscreen(); } } } toggleInvioDatiStatistici() { const newValue = !this.invioDatiStatistici(); this.invioDatiStatistici.set(newValue); localStorage.setItem('invio-dati-statistici', newValue.toString()); } toggleShowTagsInList() { const newValue = !this.showTagsInList(); this.showTagsInList.set(newValue); localStorage.setItem('show-tags-in-list', newValue.toString()); } toggleShowUpdateDate() { const newValue = !this.showUpdateDate(); this.showUpdateDate.set(newValue); localStorage.setItem('show-update-date', newValue.toString()); } toggleStandardAutoscroll() { const newValue = !this.enableStandardAutoscroll(); this.enableStandardAutoscroll.set(newValue); localStorage.setItem('enable-standard-autoscroll', newValue.toString()); } toggleAcousticAutoscroll() { const newValue = !this.enableAcousticAutoscroll(); this.enableAcousticAutoscroll.set(newValue); localStorage.setItem('enable-acoustic-autoscroll', newValue.toString()); } async installPwa() { const promptEvent = this.deferredPrompt(); if (!promptEvent) { return; } // Show the install prompt promptEvent.prompt(); // Wait for the user to respond to the prompt const { outcome } = await promptEvent.userChoice; console.log(`User response to the install prompt: ${outcome}`); // We've used the prompt, and can't use it again, discard it this.deferredPrompt.set(null); this.showInstallButton.set(false); } }