@@ -254,44 +307,7 @@
-
-
0">
-
-
- Proponi i miei canti ({{ myCantiService.myCanti().length }})
- Invia a {{ contactEmail }}
-
-
-
-
-
-
-
- Allinea con Server
- Aggiorna canti e versione app
-
-
-
-
-
-
-
- Verifica Aggiornamenti App
- Forza la ricerca di una nuova versione dell'applicazione
-
-
-
-
-
- Versione: v{{ version }} •
- Canti: {{ cantiService.canti().length }}
-
-
-
-
diff --git a/src/app/pages/settings/settings.page.ts b/src/app/pages/settings/settings.page.ts
index 4a32a7a..ebebbe7 100644
--- a/src/app/pages/settings/settings.page.ts
+++ b/src/app/pages/settings/settings.page.ts
@@ -142,9 +142,15 @@ export class SettingsPage {
if (response.ok) {
const remoteJson = await response.json();
if (Array.isArray(remoteJson)) {
+ // Reconstruct user name/metadata
+ const userMetadata = remoteJson.find((item: any) => item.momenti && item.momenti.includes('UserMetadata'));
+ if (userMetadata && userMetadata.titolo) {
+ this.settingsService.setUserName(userMetadata.titolo);
+ }
+
// Reconstruct custom songs
const customSongs = remoteJson
- .filter((item: any) => !item.momenti || !item.momenti.includes('Playlist'))
+ .filter((item: any) => (!item.momenti || !item.momenti.includes('Playlist')) && (!item.momenti || !item.momenti.includes('UserMetadata')))
.map((item: any) => ({
id: `my_${item.id_canti}`,
id_canti: Number(item.id_canti),
@@ -215,6 +221,37 @@ export class SettingsPage {
await alert.present();
}
+ async restoreOriginalIdentity() {
+ const original = this.settingsService.originalUserUuid();
+ if (!original) {
+ const alert = await this.alertCtrl.create({
+ header: 'Errore',
+ message: 'Nessun identificativo originario trovato.',
+ buttons: ['OK']
+ });
+ await alert.present();
+ return;
+ }
+
+ const alert = await this.alertCtrl.create({
+ header: 'Ripristina ID Originario',
+ message: `Sei sicuro di voler ripristinare il codice ID originario assegnato alla prima installazione? L'ID attuale del dispositivo verrà sovrascritto e verranno scaricati eventuali canti e playlist associati all'ID originario.`,
+ buttons: [
+ {
+ text: 'Annulla',
+ role: 'cancel'
+ },
+ {
+ text: 'Ripristina',
+ handler: () => {
+ this.confirmRestore(original);
+ }
+ }
+ ]
+ });
+ await alert.present();
+ }
+
async syncLocalDataToServer() {
const uid = this.settingsService.userUuid();
if (!uid) {
@@ -258,188 +295,201 @@ export class SettingsPage {
}
}
+ async restoreBackupFromServer() {
+ const code = this.settingsService.userUuid();
+ if (!code) {
+ const alert = await this.alertCtrl.create({
+ header: 'Errore',
+ message: 'Nessun identificativo utente (UID) trovato.',
+ buttons: ['OK']
+ });
+ await alert.present();
+ return;
+ }
+
+ const alert = await this.alertCtrl.create({
+ header: 'Ripristina Backup',
+ message: 'Sei sicuro di voler ripristinare i dati dal server? I tuoi canti personali e le tue scalette locali verranno allineati con l\'ultimo backup presente sul server.',
+ buttons: [
+ {
+ text: 'Annulla',
+ role: 'cancel'
+ },
+ {
+ text: 'Ripristina',
+ handler: async () => {
+ const loading = await this.loadingCtrl.create({
+ message: 'Scaricamento dati da remoto...'
+ });
+ await loading.present();
+
+ try {
+ const response = await fetch(`https://api.canticristiani.it/${code}.json?cb=${Date.now()}`, { cache: 'no-store' });
+ if (response.ok) {
+ const remoteJson = await response.json();
+ if (Array.isArray(remoteJson)) {
+ // Reconstruct user name/metadata
+ const userMetadata = remoteJson.find((item: any) => item.momenti && item.momenti.includes('UserMetadata'));
+ if (userMetadata && userMetadata.titolo) {
+ this.settingsService.setUserName(userMetadata.titolo);
+ }
+
+ // Reconstruct custom songs
+ const customSongs = remoteJson
+ .filter((item: any) => (!item.momenti || !item.momenti.includes('Playlist')) && (!item.momenti || !item.momenti.includes('UserMetadata')))
+ .map((item: any) => ({
+ id: `my_${item.id_canti}`,
+ id_canti: Number(item.id_canti),
+ titolo: item.titolo || 'Senza Titolo',
+ testo: item.testo || '',
+ accordi: item.testo?.includes('[') ? item.testo : undefined,
+ id_momenti: item.momenti?.map((m: any) => Number(m)).filter((m: any) => !isNaN(m)) || []
+ }));
+
+ this.myCantiService.myCanti.set(customSongs);
+ const storage = this.cantiService.getStorage();
+ if (storage) {
+ await storage.set('my-canti', customSongs);
+ }
+
+ // Reconstruct playlists
+ const playlists = remoteJson
+ .filter((item: any) => item.momenti && item.momenti.includes('Playlist'))
+ .map((item: any) => {
+ let songSettings = {};
+ if (item.periodi && item.periodi.length > 0) {
+ try {
+ songSettings = JSON.parse(item.periodi[0]);
+ } catch (e) {}
+ }
+ return {
+ id: String(item.id_canti),
+ name: item.titolo,
+ ids: item.testo ? item.testo.split(',') : [],
+ songSettings: songSettings,
+ createdAt: new Date()
+ };
+ });
+
+ this.playlistService.playlists.set(playlists);
+ if (this.playlistService['_storage']) {
+ const key = this.playlistService.getPlaylistsStorageKey();
+ await this.playlistService['_storage'].set(key, playlists);
+ }
+
+ const toast = await this.toastCtrl.create({
+ message: 'Dati ripristinati con successo! Ricaricamento...',
+ duration: 2000,
+ color: 'success',
+ position: 'bottom'
+ });
+ await toast.present();
+
+ setTimeout(() => {
+ window.location.replace(window.location.origin + window.location.pathname);
+ }, 1500);
+ } else {
+ throw new Error('Formato dati del backup non valido.');
+ }
+ } else {
+ throw new Error('Nessun backup trovato sul server per questo ID.');
+ }
+ } catch (err: any) {
+ console.error('Failed to restore backup data:', err);
+ const errorAlert = await this.alertCtrl.create({
+ header: 'Errore Ripristino',
+ message: err.message || 'Impossibile scaricare il backup dal server. Verifica la connessione.',
+ buttons: ['OK']
+ });
+ await errorAlert.present();
+ } finally {
+ await loading.dismiss();
+ }
+ }
+ }
+ ]
+ });
+ await alert.present();
+ }
+
onModeChange(event: any) {
this.settingsService.setShowChordsDefault(event.detail.value === 'chords');
}
+ onNameChange(event: any) {
+ this.settingsService.setUserName(event.target.value);
+ }
+
onMassChange(event: any) {
this.cantiLettureService.setSelectedMass(event.detail.value);
}
- /**
- * Performs a full data refresh + checks for app updates.
- * Uses both the Angular SW and the version.json fallback.
- */
- async fullRefresh() {
- // 1. Refresh JSON data
- this.cantiService.refresh();
-
- // 2. Refresh liturgical readings JSON
- try {
- await this.cantiLettureService.fetchData();
- } catch (err) {
- console.error('Failed to refresh liturgical readings:', err);
- }
- // 3. Refresh community data if a code is active
- const comunitaCode = this.comunitaService.comunitaCode();
- if (comunitaCode) {
- try {
- await this.comunitaService.setComunitaCode(comunitaCode);
- } catch (err) {
- console.error('Failed to refresh community data:', err);
- }
- }
- // 4. Check for app updates (SW + version.json fallback)
- const updateAvailable = await this.performUpdateCheck();
- if (updateAvailable) {
- return; // updateAvailable already triggered the update/reload flow
+ async onComunitaToggleChange(event: any) {
+ const checked = event.detail.checked;
+ if (checked) {
+ this.settingsService.comunitaEnabled.set(true);
+ localStorage.setItem('comunita-enabled', 'true');
+ } else {
+ this.settingsService.comunitaEnabled.set(false);
+ localStorage.setItem('comunita-enabled', 'false');
}
-
- const toast = await this.toastCtrl.create({
- message: 'Dati aggiornati correttamente!',
- duration: 2000,
- color: 'success'
- });
- await toast.present();
}
- async checkForAppUpdate() {
- if (!this.swUpdate.isEnabled) {
- const toast = await this.toastCtrl.create({
- message: 'Aggiornamenti non supportati su questo browser.',
- duration: 3000,
- color: 'medium'
- });
- await toast.present();
- return;
- }
+ async editComunita() {
+ await this.comunitaService.setComunitaCode('');
+ }
- const toastLoading = await this.toastCtrl.create({
- message: 'Ricerca aggiornamenti in corso...',
- duration: 1500,
- color: 'secondary'
+ async saveComunitaCode(code: string) {
+ const trimmed = (code || '').trim();
+ if (!trimmed) return;
+
+ const loading = await this.loadingCtrl.create({
+ message: 'Caricamento 0%',
+ cssClass: 'premium-loading',
+ spinner: 'crescent'
});
- await toastLoading.present();
+ await loading.present();
- const updateAvailable = await this.performUpdateCheck();
+ let progressInterval = setInterval(() => {
+ const pct = this.comunitaService.loadingProgress();
+ loading.message = `Caricamento ${pct}%`;
+ if (pct >= 100) {
+ clearInterval(progressInterval);
+ }
+ }, 100);
- if (!updateAvailable) {
+ const success = await this.comunitaService.setComunitaCode(trimmed);
+ clearInterval(progressInterval);
+ await loading.dismiss();
+
+ if (success) {
const toast = await this.toastCtrl.create({
- message: 'L\'applicazione è già aggiornata all\'ultima versione.',
- duration: 3000,
+ message: `Comunità attivata: ${this.comunitaService.comunitaNome()}`,
+ duration: 2000,
color: 'success'
});
await toast.present();
- }
- }
-
- /**
- * Shared update check logic: tries Angular SW first, falls back to version.json.
- * Returns true if an update was found and the reload flow was initiated.
- */
- private async performUpdateCheck(): Promise {
- try {
- // Layer 1: Force the browser to re-fetch the SW script
- if ('serviceWorker' in navigator) {
- const registration = await navigator.serviceWorker.ready;
- await registration.update();
- }
-
- let sub: any = null;
- let readyPromise: Promise | undefined = undefined;
-
- if (this.swUpdate.isEnabled) {
- readyPromise = new Promise((resolve) => {
- sub = this.swUpdate.versionUpdates
- .pipe(
- filter((evt): evt is VersionReadyEvent => evt.type === 'VERSION_READY'),
- first()
- )
- .subscribe(() => {
- resolve();
- });
- });
- }
-
- // Layer 2: Ask Angular SW to check
- if (this.swUpdate.isEnabled) {
- const swFoundUpdate = await this.swUpdate.checkForUpdate();
- if (swFoundUpdate) {
- await this.applyUpdateAndReload(readyPromise, sub);
- return true;
- }
- }
-
- if (sub) sub.unsubscribe();
-
- // Layer 3: Fallback — check version.json
- const versionMismatch = await this.checkVersionJson();
- if (versionMismatch) {
- console.log('[PWA-Update] version.json mismatch detected from settings');
- await this.applyUpdateAndReload();
- return true;
- }
-
- return false;
- } catch (err) {
- console.error('[PWA-Update] Update check failed from settings:', err);
+ } else {
const toast = await this.toastCtrl.create({
- message: 'Errore durante la ricerca di aggiornamenti.',
- duration: 3000,
+ message: 'Codice non trovato o errore di connessione.',
+ duration: 2000,
color: 'danger'
});
await toast.present();
- return false;
}
}
- private async applyUpdateAndReload(readyPromise?: Promise, subscription?: any) {
- const overlay = showFullscreenUpdateOverlay();
-
- let activated = false;
- const activateAndReload = async () => {
- if (activated) return;
- activated = true;
- try {
- if (this.swUpdate.isEnabled) {
- await this.swUpdate.activateUpdate();
- }
- } catch (e) {
- console.warn('[PWA-Update] activateUpdate failed:', e);
- }
- overlay.finish();
- setTimeout(() => {
- window.location.replace(window.location.origin + window.location.pathname + '?update=' + Date.now());
- }, 600);
- };
-
- if (readyPromise) {
- Promise.race([
- readyPromise,
- new Promise((resolve) => setTimeout(resolve, 25000))
- ]).then(() => {
- if (subscription) subscription.unsubscribe();
- activateAndReload();
- });
- } else {
- setTimeout(() => {
- activateAndReload();
- }, 1000);
- }
- }
-
- private async checkVersionJson(): Promise {
- try {
- const response = await fetch(`/version.json?cb=${Date.now()}`, { cache: 'no-store' });
- if (!response.ok) return false;
- const data = await response.json();
- console.log(`[PWA-Update] Settings version check: local=${VERSION}, remote=${data.version}`);
- return data.version !== VERSION;
- } catch (err) {
- console.warn('[PWA-Update] version.json check failed:', err);
- return false;
- }
+ async removeComunita() {
+ await this.comunitaService.setComunitaCode('');
+ this.settingsService.comunitaEnabled.set(false);
+ localStorage.setItem('comunita-enabled', 'false');
+ const toast = await this.toastCtrl.create({
+ message: 'Comunità disattivata.',
+ duration: 2000,
+ color: 'secondary'
+ });
+ await toast.present();
}
}
diff --git a/src/app/services/canti.service.ts b/src/app/services/canti.service.ts
index afb7ead..56da78c 100644
--- a/src/app/services/canti.service.ts
+++ b/src/app/services/canti.service.ts
@@ -60,9 +60,6 @@ export class CantiService {
await this.loadFromStorage();
if (this.canti() && this.canti().length > 0) {
this.firstLoadCompleted.set(true);
- if (this.settingsService.isVersionCheckComplete() && (window as any).PwaLoader) {
- (window as any).PwaLoader.hide();
- }
} else {
// First boot or data cleared: show setup loader immediately
if ((window as any).PwaLoader) {
@@ -159,19 +156,12 @@ export class CantiService {
this.progress.set(100);
this.loading.set(false);
this.firstLoadCompleted.set(true);
-
- if (this.settingsService.isVersionCheckComplete() && (window as any).PwaLoader) {
- (window as any).PwaLoader.hide();
- }
}
},
error: (error) => {
console.error('Failed to fetch canti', error);
this.loading.set(false);
this.firstLoadCompleted.set(true);
- if ((window as any).PwaLoader) {
- (window as any).PwaLoader.hide();
- }
}
});
}
diff --git a/src/app/services/playlist.service.ts b/src/app/services/playlist.service.ts
index 92f0eac..ea41c54 100644
--- a/src/app/services/playlist.service.ts
+++ b/src/app/services/playlist.service.ts
@@ -363,6 +363,16 @@ export class PlaylistService {
const payload = [...customSongs, ...playlistSongs];
+ if (this.settingsService.userName()) {
+ payload.push({
+ id_canti: 999999,
+ titolo: this.settingsService.userName(),
+ momenti: ['UserMetadata'],
+ periodi: [],
+ testo: ''
+ });
+ }
+
const response = await fetch('https://api.canticristiani.it/miei', {
method: 'POST',
headers: {
diff --git a/src/app/services/settings.service.ts b/src/app/services/settings.service.ts
index def04a3..331bb59 100644
--- a/src/app/services/settings.service.ts
+++ b/src/app/services/settings.service.ts
@@ -56,6 +56,12 @@ export class SettingsService {
/** Identificativo utente univoco per la gestione delle comunità */
public userUuid = signal('');
+ /** Identificativo originario assegnato alla prima installazione */
+ public originalUserUuid = signal('');
+
+ /** Nome associato all'identità utente */
+ public userName = signal('');
+
private wakeLock: any = null;
// PWA installation signals
@@ -83,6 +89,19 @@ export class SettingsService {
}
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(
@@ -166,12 +185,8 @@ export class SettingsService {
this.autoAdvance.set(true);
}
- const savedKeepScreenOn = localStorage.getItem('keep-screen-on');
- if (savedKeepScreenOn !== null) {
- this.keepScreenOn.set(savedKeepScreenOn === 'true');
- } else {
- this.keepScreenOn.set(true);
- }
+ // Keep screen always on by default and always active
+ this.keepScreenOn.set(true);
const savedComunitaEnabled = localStorage.getItem('comunita-enabled');
if (savedComunitaEnabled !== null) {
@@ -318,9 +333,6 @@ export class SettingsService {
localStorage.setItem('show-editor', newValue.toString());
}
- toggleKeepScreenOn() {
- this.keepScreenOn.update(v => !v);
- }
toggleComunitaEnabled() {
const newValue = !this.comunitaEnabled();
@@ -426,6 +438,12 @@ export class SettingsService {
}
}
+ setUserName(name: string) {
+ const trimmed = name.trim();
+ this.userName.set(trimmed);
+ localStorage.setItem('user-name', trimmed);
+ }
+
async installPwa() {
const promptEvent = this.deferredPrompt();
if (!promptEvent) {
diff --git a/src/app/version.ts b/src/app/version.ts
index ef20d01..b09cb27 100644
--- a/src/app/version.ts
+++ b/src/app/version.ts
@@ -1 +1 @@
-export const VERSION = '2026.06.14.1936';
+export const VERSION = '2026.06.15.0021';