feat: show instructions when playlist is empty & fix high contrast contrast
This commit is contained in:
@@ -114,6 +114,7 @@ export class PlayerPage implements OnInit, AfterViewInit, OnDestroy {
|
||||
|
||||
private initialPinchDistance: number | null = null;
|
||||
private initialFontSize: number = 1.0;
|
||||
public portraitFontSize: number = 1.0;
|
||||
|
||||
private lastMatchedTranscript: string = '';
|
||||
|
||||
@@ -267,8 +268,10 @@ export class PlayerPage implements OnInit, AfterViewInit, OnDestroy {
|
||||
this.autoscrollSpeed.set(playlistSongSetting.speed !== undefined ? playlistSongSetting.speed : 2);
|
||||
if (playlistSongSetting.zoom !== undefined) {
|
||||
this.fontSize.set(playlistSongSetting.zoom);
|
||||
this.portraitFontSize = playlistSongSetting.zoom;
|
||||
} else {
|
||||
this.fontSize.set(1.0);
|
||||
this.portraitFontSize = 1.0;
|
||||
}
|
||||
} else if (this.comunitaService.comunitaCode() && this.comunitaService.isFilterActive()) {
|
||||
const settings = this.comunitaService.comunitaCantiSettings();
|
||||
@@ -289,9 +292,13 @@ export class PlayerPage implements OnInit, AfterViewInit, OnDestroy {
|
||||
this.transposeAmount.set(0);
|
||||
this.autoscrollSpeed.set(2);
|
||||
}
|
||||
this.fontSize.set(1.0);
|
||||
this.portraitFontSize = 1.0;
|
||||
} else {
|
||||
this.transposeAmount.set(0);
|
||||
this.autoscrollSpeed.set(2);
|
||||
this.fontSize.set(1.0);
|
||||
this.portraitFontSize = 1.0;
|
||||
}
|
||||
}, { allowSignalWrites: true });
|
||||
|
||||
@@ -320,6 +327,9 @@ export class PlayerPage implements OnInit, AfterViewInit, OnDestroy {
|
||||
}
|
||||
|
||||
private checkAndLimitFontSize(targetFont: number): number {
|
||||
if (!this.isLandscape()) {
|
||||
return targetFont;
|
||||
}
|
||||
const titleMain = this.el.nativeElement.querySelector('.title-main');
|
||||
if (!titleMain) return targetFont;
|
||||
|
||||
@@ -388,9 +398,8 @@ export class PlayerPage implements OnInit, AfterViewInit, OnDestroy {
|
||||
}
|
||||
}, 150);
|
||||
} else {
|
||||
const limitedFont = this.checkAndLimitFontSize(1.0);
|
||||
this.fontSize.set(limitedFont);
|
||||
this.channel.postMessage({ type: 'SYNC_FONT', fontSize: limitedFont });
|
||||
this.fontSize.set(this.portraitFontSize);
|
||||
this.channel.postMessage({ type: 'SYNC_FONT', fontSize: this.portraitFontSize });
|
||||
}
|
||||
}
|
||||
|
||||
@@ -518,6 +527,9 @@ export class PlayerPage implements OnInit, AfterViewInit, OnDestroy {
|
||||
}
|
||||
if (Math.abs(limited - this.fontSize()) > 0.01) {
|
||||
this.fontSize.set(limited);
|
||||
if (!this.isLandscape()) {
|
||||
this.portraitFontSize = limited;
|
||||
}
|
||||
this.channel.postMessage({ type: 'SYNC_FONT', fontSize: this.fontSize() });
|
||||
}
|
||||
}
|
||||
@@ -582,6 +594,9 @@ export class PlayerPage implements OnInit, AfterViewInit, OnDestroy {
|
||||
}
|
||||
if (limited !== this.fontSize()) {
|
||||
this.fontSize.set(limited);
|
||||
if (!this.isLandscape()) {
|
||||
this.portraitFontSize = limited;
|
||||
}
|
||||
this.channel.postMessage({ type: 'SYNC_FONT', fontSize: this.fontSize() });
|
||||
this.updatePlaylistSettings();
|
||||
}
|
||||
@@ -593,6 +608,9 @@ export class PlayerPage implements OnInit, AfterViewInit, OnDestroy {
|
||||
if (this.fontSize() > minZ) {
|
||||
const target = Math.max(this.fontSize() - this.FONT_STEP, minZ);
|
||||
this.fontSize.set(target);
|
||||
if (!this.isLandscape()) {
|
||||
this.portraitFontSize = target;
|
||||
}
|
||||
this.channel.postMessage({ type: 'SYNC_FONT', fontSize: this.fontSize() });
|
||||
this.updatePlaylistSettings();
|
||||
}
|
||||
|
||||
@@ -79,14 +79,7 @@
|
||||
</ion-label>
|
||||
<ion-toggle slot="end" [checked]="settingsService.autoAdvance()" (ionChange)="settingsService.toggleAutoAdvance()" color="secondary"></ion-toggle>
|
||||
</ion-item>
|
||||
<ion-item class="transparent-item" lines="none">
|
||||
<ion-icon name="sunny-outline" slot="start" color="secondary"></ion-icon>
|
||||
<ion-label class="outfit-font">
|
||||
<h2 class="settings-item-title">Schermo Sempre Acceso</h2>
|
||||
<p class="settings-item-subtitle">Evita che lo schermo si spenga</p>
|
||||
</ion-label>
|
||||
<ion-toggle slot="end" [checked]="settingsService.keepScreenOn()" (ionChange)="settingsService.toggleKeepScreenOn()" color="secondary"></ion-toggle>
|
||||
</ion-item>
|
||||
|
||||
<ion-item class="transparent-item" lines="none">
|
||||
<ion-icon name="create-outline" slot="start" color="secondary"></ion-icon>
|
||||
<ion-label class="outfit-font">
|
||||
@@ -145,12 +138,50 @@
|
||||
<h2 class="settings-item-title">Comunità</h2>
|
||||
<p class="settings-item-subtitle">Mostra il filtro Comunità nella home</p>
|
||||
</ion-label>
|
||||
<ion-toggle slot="end" [checked]="settingsService.comunitaEnabled()" (ionChange)="settingsService.toggleComunitaEnabled()" color="secondary"></ion-toggle>
|
||||
<ion-toggle slot="end" [checked]="settingsService.comunitaEnabled()" (ionChange)="onComunitaToggleChange($event)" color="secondary"></ion-toggle>
|
||||
</ion-item>
|
||||
|
||||
<!-- Community Details/Form when enabled -->
|
||||
<div class="ion-padding-horizontal ion-padding-bottom" *ngIf="settingsService.comunitaEnabled()" style="border-top: 1px solid rgba(255,255,255,0.06); padding-top: 16px;">
|
||||
<div *ngIf="comunitaService.comunitaCode()" class="glass" style="background: rgba(255,255,255,0.03); border: 1px solid rgba(255,255,255,0.06); padding: 12px; border-radius: 12px; display: flex; align-items: center; justify-content: space-between;">
|
||||
<div style="overflow: hidden; text-overflow: ellipsis; white-space: nowrap; margin-right: 12px; flex-grow: 1;">
|
||||
<div style="font-size: 0.72rem; text-transform: uppercase; color: var(--ion-color-secondary); font-weight: bold; letter-spacing: 0.05em; margin-bottom: 2px;">Comunità Attiva</div>
|
||||
<div style="font-size: 0.9rem; font-weight: 500; color: var(--ion-text-color); overflow: hidden; text-overflow: ellipsis;">{{ comunitaService.comunitaNome() }}</div>
|
||||
<div style="font-size: 0.75rem; opacity: 0.7; color: var(--ion-text-color); margin-top: 2px;">Codice: {{ comunitaService.comunitaCode() }}</div>
|
||||
</div>
|
||||
<div style="display: flex; gap: 8px; flex-shrink: 0;">
|
||||
<ion-button fill="clear" size="small" color="secondary" (click)="editComunita()" style="margin: 0; --padding-start: 4px; --padding-end: 4px;">
|
||||
<ion-icon name="create-outline" slot="icon-only" style="font-size: 1.25rem;"></ion-icon>
|
||||
</ion-button>
|
||||
<ion-button fill="clear" size="small" color="danger" (click)="removeComunita()" style="margin: 0; --padding-start: 4px; --padding-end: 4px;">
|
||||
<ion-icon name="trash-outline" slot="icon-only" style="font-size: 1.25rem;"></ion-icon>
|
||||
</ion-button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div *ngIf="!comunitaService.comunitaCode()" style="display: flex; flex-direction: column; gap: 12px;">
|
||||
<p class="settings-item-subtitle outfit-font" style="margin: 0; font-size: 0.8rem; opacity: 0.85;">
|
||||
Inserisci il codice parrocchiale/comunità per scaricare i canti e le scalette dedicate:
|
||||
</p>
|
||||
<div style="display: flex; gap: 10px; align-items: center;">
|
||||
<div class="glass" style="background: rgba(255,255,255,0.03); border: 1px solid rgba(255,255,255,0.06); padding: 8px 12px; border-radius: 10px; flex-grow: 1; display: flex; align-items: center;">
|
||||
<input type="text"
|
||||
#comunitaCodeInput
|
||||
placeholder="Es: 123456"
|
||||
style="background: transparent; border: none; color: var(--ion-text-color); font-size: 0.85rem; width: 100%; outline: none;"
|
||||
class="outfit-font"
|
||||
(keyup.enter)="saveComunitaCode(comunitaCodeInput.value)">
|
||||
</div>
|
||||
<ion-button fill="solid" color="secondary" size="small" (click)="saveComunitaCode(comunitaCodeInput.value)" class="outfit-font" style="--border-radius: 10px; font-weight: bold; text-transform: none; margin: 0; height: 36px;">
|
||||
Attiva
|
||||
</ion-button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Identità Utente e Ripristino -->
|
||||
<div class="settings-group glass ion-margin-bottom" *ngIf="settingsService.comunitaEnabled() || settingsService.showEditor()">
|
||||
<div class="settings-group glass ion-margin-bottom">
|
||||
<div class="group-header ion-padding-start ion-padding-top">
|
||||
<h2 class="outfit-font settings-group-title" style="color: var(--ion-color-secondary); font-weight: bold; font-size: 1.05rem;">
|
||||
Identità Utente e Ripristino
|
||||
@@ -162,6 +193,17 @@
|
||||
Questo codice univoco identifica in modo anonimo il tuo dispositivo e ti consente di gestire comunità e canti. Salva il codice o il QR code per ripristinare il tuo profilo su un nuovo dispositivo.
|
||||
</p>
|
||||
|
||||
<!-- Nome Associato all'Identità -->
|
||||
<div class="glass" style="background: rgba(255,255,255,0.03); border: 1px solid rgba(255,255,255,0.06); padding: 12px; border-radius: 12px; margin-bottom: 12px;">
|
||||
<div style="font-size: 0.72rem; text-transform: uppercase; color: var(--ion-color-secondary); font-weight: bold; letter-spacing: 0.05em; margin-bottom: 4px;">Nome</div>
|
||||
<input type="text"
|
||||
[value]="settingsService.userName()"
|
||||
(input)="onNameChange($event)"
|
||||
placeholder="Inserisci il tuo nome..."
|
||||
style="background: transparent; border: none; color: var(--ion-text-color); font-size: 0.85rem; width: 100%; outline: none; padding: 4px 0;"
|
||||
class="outfit-font">
|
||||
</div>
|
||||
|
||||
<div class="glass" style="background: rgba(255,255,255,0.03); border: 1px solid rgba(255,255,255,0.06); padding: 12px; border-radius: 12px; display: flex; align-items: center; justify-content: space-between; margin-bottom: 16px;">
|
||||
<div style="overflow: hidden; text-overflow: ellipsis; white-space: nowrap; margin-right: 12px; flex-grow: 1;">
|
||||
<div style="font-size: 0.72rem; text-transform: uppercase; color: var(--ion-color-secondary); font-weight: bold; letter-spacing: 0.05em; margin-bottom: 2px;">Codice ID</div>
|
||||
@@ -183,10 +225,21 @@
|
||||
</ion-button>
|
||||
</div>
|
||||
|
||||
<ion-button *ngIf="settingsService.userUuid() !== settingsService.originalUserUuid()"
|
||||
expand="block" fill="outline" color="warning" (click)="restoreOriginalIdentity()" class="outfit-font ion-margin-top" style="--border-radius: 10px; font-size: 0.85rem; font-weight: bold; text-transform: none; margin-left: 0; margin-right: 0; margin-bottom: 0; height: 42px;">
|
||||
<ion-icon name="refresh-outline" slot="start"></ion-icon>
|
||||
Ripristina ID Originario
|
||||
</ion-button>
|
||||
|
||||
<ion-button expand="block" fill="solid" color="secondary" (click)="syncLocalDataToServer()" class="outfit-font ion-margin-top" style="--border-radius: 10px; font-size: 0.85rem; font-weight: bold; text-transform: none; margin-left: 0; margin-right: 0; margin-bottom: 0; height: 42px;">
|
||||
<ion-icon name="cloud-upload-outline" slot="start"></ion-icon>
|
||||
Salva Backup sul Server
|
||||
</ion-button>
|
||||
|
||||
<ion-button expand="block" fill="outline" color="secondary" (click)="restoreBackupFromServer()" class="outfit-font ion-margin-top" style="--border-radius: 10px; font-size: 0.85rem; font-weight: bold; text-transform: none; margin-left: 0; margin-right: 0; margin-bottom: 0; height: 42px;">
|
||||
<ion-icon name="cloud-download-outline" slot="start"></ion-icon>
|
||||
Ripristina Backup dal Server
|
||||
</ion-button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -254,44 +307,7 @@
|
||||
|
||||
|
||||
|
||||
<div class="settings-group glass ion-margin-top" *ngIf="settingsService.showEditor()">
|
||||
<ion-item class="transparent-item" lines="none" (click)="myCantiService.sendAllMyCanti()" detail="true" button *ngIf="myCantiService.myCanti().length > 0">
|
||||
<ion-icon name="send-outline" slot="start" color="secondary"></ion-icon>
|
||||
<ion-label class="outfit-font">
|
||||
<h2 class="settings-item-title">Proponi i miei canti ({{ myCantiService.myCanti().length }})</h2>
|
||||
<p class="settings-item-subtitle">Invia a {{ contactEmail }}</p>
|
||||
</ion-label>
|
||||
</ion-item>
|
||||
</div>
|
||||
|
||||
<div class="settings-group glass ion-margin-top">
|
||||
<ion-item class="transparent-item" lines="none" (click)="fullRefresh()" detail="true" button>
|
||||
<ion-icon name="cloud-download-outline" slot="start" color="secondary"></ion-icon>
|
||||
<ion-label class="outfit-font">
|
||||
<h2 class="settings-item-title">Allinea con Server</h2>
|
||||
<p class="settings-item-subtitle">Aggiorna canti e versione app</p>
|
||||
</ion-label>
|
||||
<ion-spinner slot="end" name="crescent" color="secondary" *ngIf="cantiService.loading()"></ion-spinner>
|
||||
</ion-item>
|
||||
|
||||
<ion-item class="transparent-item" lines="none" (click)="checkForAppUpdate()" detail="true" button style="border-top: 1px solid rgba(255,255,255,0.06);">
|
||||
<ion-icon name="sync-outline" slot="start" color="secondary"></ion-icon>
|
||||
<ion-label class="outfit-font">
|
||||
<h2 class="settings-item-title">Verifica Aggiornamenti App</h2>
|
||||
<p class="settings-item-subtitle">Forza la ricerca di una nuova versione dell'applicazione</p>
|
||||
</ion-label>
|
||||
</ion-item>
|
||||
|
||||
<div class="sync-info ion-padding-bottom">
|
||||
<p class="outfit-font settings-item-subtitle">
|
||||
Versione: <strong>v{{ version }}</strong> •
|
||||
Canti: <strong>{{ cantiService.canti().length }}</strong>
|
||||
</p>
|
||||
</div>
|
||||
<div class="sync-progress" *ngIf="cantiService.loading()">
|
||||
<div class="progress-bar" [style.width.%]="cantiService.progress()"></div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Legenda Pulsanti Canto -->
|
||||
<div class="settings-group glass ion-margin-top">
|
||||
|
||||
@@ -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<boolean> {
|
||||
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<void> | undefined = undefined;
|
||||
|
||||
if (this.swUpdate.isEnabled) {
|
||||
readyPromise = new Promise<void>((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<void>, 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<boolean> {
|
||||
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();
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user