539 lines
18 KiB
TypeScript
539 lines
18 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);
|
|
|
|
/** Visualizza durata, bpm e tonalità sotto autore / titolo: true = attivo */
|
|
public showDurationBpmTonality = signal<boolean>(true);
|
|
|
|
/** Attiva autoscroll standard nel dettaglio canto: true = attivo */
|
|
public enableStandardAutoscroll = signal<boolean>(false);
|
|
|
|
/** Attiva autoscroll visuale nel dettaglio canto: true = attivo */
|
|
public enableVisualAutoscroll = signal<boolean>(true);
|
|
|
|
/** Preferenza notazione accordi: diesis o bemolle */
|
|
public chordNotationPreference = signal<'diesis' | 'bemolle'>('diesis');
|
|
|
|
/** Avanzamento a pagine del karaoke manuale: true = i tasti next/prev voltano la pagina */
|
|
public karaokePageScrollMode = signal<boolean>(false);
|
|
|
|
/** Vista orizzontale per proiezione: true = attiva layout landscape per proiezione */
|
|
public landscapeProjectionEnabled = signal<boolean>(false);
|
|
|
|
/** Schermo nero per l'ascolto delle playlist in macchina: true = attivo */
|
|
public carModeBlackScreen = signal<boolean>(false);
|
|
|
|
/** Global zoom/font size factor for song presentation */
|
|
public globalZoom = signal<number>(1.0);
|
|
|
|
/** Identificativo utente univoco per la gestione delle comunità */
|
|
public userUuid = signal<string>('');
|
|
|
|
/** Identificativo originario assegnato alla prima installazione */
|
|
public originalUserUuid = signal<string>('');
|
|
|
|
/** Nome associato all'identità utente */
|
|
public userName = signal<string>('');
|
|
|
|
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);
|
|
public isVersionCheckComplete = signal<boolean>(false);
|
|
|
|
constructor() {
|
|
// Gestione/Generazione ID utente univoco
|
|
let savedUuid = localStorage.getItem('user-uuid');
|
|
if (!savedUuid) {
|
|
if (typeof crypto !== 'undefined' && crypto.randomUUID) {
|
|
savedUuid = crypto.randomUUID();
|
|
} else {
|
|
savedUuid = 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, (c) => {
|
|
const r = Math.random() * 16 | 0;
|
|
const v = c === 'x' ? r : (r & 0x3 | 0x8);
|
|
return v.toString(16);
|
|
});
|
|
}
|
|
localStorage.setItem('user-uuid', savedUuid);
|
|
}
|
|
this.userUuid.set(savedUuid);
|
|
|
|
// Salvataggio dell'ID originario (alla prima installazione) se non è già presente
|
|
let originalUuid = localStorage.getItem('original-user-uuid');
|
|
if (!originalUuid) {
|
|
originalUuid = savedUuid;
|
|
localStorage.setItem('original-user-uuid', originalUuid);
|
|
}
|
|
this.originalUserUuid.set(originalUuid);
|
|
|
|
const savedName = localStorage.getItem('user-name');
|
|
if (savedName) {
|
|
this.userName.set(savedName);
|
|
}
|
|
|
|
// 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-20260605';
|
|
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('show-duration-bpm-tonality', 'true');
|
|
localStorage.setItem('enable-standard-autoscroll', 'false');
|
|
localStorage.setItem('enable-visual-autoscroll', 'true');
|
|
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);
|
|
}
|
|
|
|
// Keep screen always on by default and always active
|
|
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 savedShowDurBpmTon = localStorage.getItem('show-duration-bpm-tonality');
|
|
if (savedShowDurBpmTon !== null) {
|
|
this.showDurationBpmTonality.set(savedShowDurBpmTon === 'true');
|
|
} else {
|
|
this.showDurationBpmTonality.set(true);
|
|
}
|
|
|
|
const savedStandardAutoscroll = localStorage.getItem('enable-standard-autoscroll');
|
|
if (savedStandardAutoscroll !== null) {
|
|
this.enableStandardAutoscroll.set(savedStandardAutoscroll === 'true');
|
|
} else {
|
|
this.enableStandardAutoscroll.set(false);
|
|
}
|
|
|
|
const savedVisualAutoscroll = localStorage.getItem('enable-visual-autoscroll');
|
|
if (savedVisualAutoscroll !== null) {
|
|
this.enableVisualAutoscroll.set(savedVisualAutoscroll === 'true');
|
|
} else {
|
|
this.enableVisualAutoscroll.set(true);
|
|
}
|
|
|
|
const savedNotation = localStorage.getItem('chord-notation-preference');
|
|
if (savedNotation !== null) {
|
|
this.chordNotationPreference.set(savedNotation === 'bemolle' ? 'bemolle' : 'diesis');
|
|
} else {
|
|
this.chordNotationPreference.set('diesis');
|
|
}
|
|
|
|
const savedKaraokePageScrollMode = localStorage.getItem('karaoke-page-scroll-mode');
|
|
if (savedKaraokePageScrollMode !== null) {
|
|
this.karaokePageScrollMode.set(savedKaraokePageScrollMode === 'true');
|
|
} else {
|
|
this.karaokePageScrollMode.set(false);
|
|
}
|
|
|
|
const savedLandscapeProjectionEnabled = localStorage.getItem('landscape-projection-enabled');
|
|
if (savedLandscapeProjectionEnabled !== null) {
|
|
this.landscapeProjectionEnabled.set(savedLandscapeProjectionEnabled === 'true');
|
|
} else {
|
|
this.landscapeProjectionEnabled.set(false);
|
|
}
|
|
|
|
const savedCarModeBlackScreen = localStorage.getItem('car-mode-black-screen');
|
|
if (savedCarModeBlackScreen !== null) {
|
|
this.carModeBlackScreen.set(savedCarModeBlackScreen === 'true');
|
|
} else {
|
|
this.carModeBlackScreen.set(false);
|
|
}
|
|
|
|
const savedGlobalZoom = localStorage.getItem('global-zoom');
|
|
if (savedGlobalZoom !== null) {
|
|
const parsed = parseFloat(savedGlobalZoom);
|
|
this.globalZoom.set(isNaN(parsed) ? 1.0 : parsed);
|
|
} else {
|
|
this.globalZoom.set(1.0);
|
|
}
|
|
|
|
// 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());
|
|
});
|
|
|
|
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('show-duration-bpm-tonality', this.showDurationBpmTonality().toString());
|
|
});
|
|
|
|
effect(() => {
|
|
localStorage.setItem('enable-standard-autoscroll', this.enableStandardAutoscroll().toString());
|
|
});
|
|
|
|
effect(() => {
|
|
localStorage.setItem('enable-visual-autoscroll', this.enableVisualAutoscroll().toString());
|
|
});
|
|
|
|
effect(() => {
|
|
localStorage.setItem('chord-notation-preference', this.chordNotationPreference());
|
|
});
|
|
|
|
effect(() => {
|
|
localStorage.setItem('karaoke-page-scroll-mode', this.karaokePageScrollMode().toString());
|
|
});
|
|
|
|
effect(() => {
|
|
localStorage.setItem('landscape-projection-enabled', this.landscapeProjectionEnabled().toString());
|
|
});
|
|
|
|
effect(() => {
|
|
localStorage.setItem('car-mode-black-screen', this.carModeBlackScreen().toString());
|
|
});
|
|
|
|
effect(() => {
|
|
localStorage.setItem('global-zoom', this.globalZoom().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());
|
|
}
|
|
|
|
|
|
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());
|
|
}
|
|
|
|
toggleShowDurationBpmTonality() {
|
|
const newValue = !this.showDurationBpmTonality();
|
|
this.showDurationBpmTonality.set(newValue);
|
|
localStorage.setItem('show-duration-bpm-tonality', newValue.toString());
|
|
}
|
|
|
|
toggleStandardAutoscroll() {
|
|
const newValue = !this.enableStandardAutoscroll();
|
|
this.enableStandardAutoscroll.set(newValue);
|
|
localStorage.setItem('enable-standard-autoscroll', newValue.toString());
|
|
}
|
|
|
|
toggleVisualAutoscroll() {
|
|
const newValue = !this.enableVisualAutoscroll();
|
|
this.enableVisualAutoscroll.set(newValue);
|
|
localStorage.setItem('enable-visual-autoscroll', newValue.toString());
|
|
}
|
|
|
|
toggleKaraokePageScrollMode() {
|
|
const newValue = !this.karaokePageScrollMode();
|
|
this.karaokePageScrollMode.set(newValue);
|
|
localStorage.setItem('karaoke-page-scroll-mode', newValue.toString());
|
|
}
|
|
|
|
toggleLandscapeProjectionEnabled() {
|
|
const newValue = !this.landscapeProjectionEnabled();
|
|
this.landscapeProjectionEnabled.set(newValue);
|
|
localStorage.setItem('landscape-projection-enabled', newValue.toString());
|
|
}
|
|
|
|
toggleCarModeBlackScreen() {
|
|
const newValue = !this.carModeBlackScreen();
|
|
this.carModeBlackScreen.set(newValue);
|
|
localStorage.setItem('car-mode-black-screen', newValue.toString());
|
|
}
|
|
|
|
setChordNotationPreference(val: 'diesis' | 'bemolle') {
|
|
this.chordNotationPreference.set(val);
|
|
}
|
|
|
|
setUserUuid(uuid: string) {
|
|
const trimmed = uuid.trim();
|
|
if (trimmed) {
|
|
this.userUuid.set(trimmed);
|
|
localStorage.setItem('user-uuid', trimmed);
|
|
}
|
|
}
|
|
|
|
setUserName(name: string) {
|
|
const trimmed = name.trim();
|
|
this.userName.set(trimmed);
|
|
localStorage.setItem('user-name', trimmed);
|
|
}
|
|
|
|
async installPwa(): Promise<string | undefined> {
|
|
const promptEvent = this.deferredPrompt();
|
|
if (!promptEvent) {
|
|
return undefined;
|
|
}
|
|
// 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);
|
|
return outcome;
|
|
}
|
|
}
|