Files
canti/src/app/services/settings.service.ts
T

418 lines
14 KiB
TypeScript

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<boolean>(true);
/** UI Fullscreen mode (lyrics only): true = active */
public fullscreenMode = signal<boolean>(false);
/** Browser Fullscreen state: true = fullscreen active */
public browserFullscreen = signal<boolean>(
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<boolean>(true);
/** Editor e Canti Personali: true = mostra funzioni aggiunta e lista "Miei" */
public showEditor = signal<boolean>(false);
/** Schermo sempre acceso: true = attiva Screen Wake Lock */
public keepScreenOn = signal<boolean>(true);
/** Funzionalità Comunità: true = il chip comunità è visibile nella home */
public comunitaEnabled = signal<boolean>(false);
/** Invio dati statistici: true = invia pacchetto dati statistici */
public invioDatiStatistici = signal<boolean>(false);
/** Visualizza tag sotto autore nella lista canti: true = attivo */
public showTagsInList = signal<boolean>(true);
/** Visualizza data update sotto autore nella lista canti: true = attivo */
public showUpdateDate = signal<boolean>(true);
/** Attiva autoscroll standard nel dettaglio canto: true = attivo */
public enableStandardAutoscroll = signal<boolean>(true);
/** Attiva autoscroll acustico nel dettaglio canto: true = attivo */
public enableAcousticAutoscroll = signal<boolean>(false);
/** Preferenza notazione accordi: diesis o bemolle */
public chordNotationPreference = signal<'diesis' | 'bemolle'>('diesis');
private wakeLock: any = null;
// PWA installation signals
public deferredPrompt = signal<any>(null);
public showInstallButton = signal<boolean>(false);
public isStandalone = signal<boolean>(false);
public isIos = signal<boolean>(false);
public isAndroid = signal<boolean>(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');
localStorage.setItem('chord-notation-preference', 'diesis');
// 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);
}
const savedNotation = localStorage.getItem('chord-notation-preference');
if (savedNotation !== null) {
this.chordNotationPreference.set(savedNotation === 'bemolle' ? 'bemolle' : 'diesis');
} else {
this.chordNotationPreference.set('diesis');
}
// 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(() => {
localStorage.setItem('chord-notation-preference', this.chordNotationPreference());
});
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());
}
setChordNotationPreference(val: 'diesis' | 'bemolle') {
this.chordNotationPreference.set(val);
}
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);
}
}