fix(pwa): progress bar setup loading, PWA install detection, Miei filter chip & UI cleanup

This commit is contained in:
David Frassi
2026-08-08 16:17:20 +02:00
parent 0069c1a55f
commit a15a6c2139
21 changed files with 2378 additions and 559 deletions
+82 -66
View File
@@ -304,30 +304,70 @@ export class AppComponent implements OnInit {
console.log('[AppComponent] PWA appinstalled event caught.');
localStorage.setItem('pwa-installed', 'true');
this.isPwaInstalled.set(true);
this.isInstalling.set(false);
this.isRedirecting.set(true);
this.showInstallOverlay.set(false);
this.showRedirectOverlay.set(false);
if ((window as any).PwaLoader) {
(window as any).PwaLoader.show();
(window as any).PwaLoader.update({
title: 'Chiudi il browser',
desc: 'Applicazione installata con successo! Chiudi il browser e continua sulla PWA.',
isRedirect: true
});
}
if (window.location.hostname === 'localhost' || window.location.hostname === '127.0.0.1') {
console.log('[AppComponent] Running on localhost - skipping protocol link redirect on appinstalled.');
// Se la finestra è GIÀ stata trasformata in PWA standalone (es. Desktop Mac/Windows)
const isStandalone = window.matchMedia('(display-mode: standalone)').matches || (navigator as any).standalone;
if (isStandalone) {
this.isInstalling.set(false);
this.isRedirecting.set(false);
this.checkLoaderDismissal();
return;
}
setTimeout(() => {
window.location.href = this.protocolLink;
}, 1000);
// Su Android, l'evento 'appinstalled' scatta in 2-3 secondi, MA l'OS impiega 10-12s
// per pacchettizzare e registrare il WebAPK nella schermata Home.
// Calibriamo la progress bar spalmata su ~11 secondi reali prima del reindirizzamento.
this.isInstalling.set(true);
this.isRedirecting.set(false);
if ((window as any).PwaLoader) {
(window as any).PwaLoader.show();
(window as any).PwaLoader.update({
title: 'Installazione applicazione',
phase: 'Fase: Registrazione',
desc: 'Generazione e registrazione dell\'applicazione sul dispositivo in corso...',
percent: 15
});
}
const startTime = Date.now();
const TARGET_DURATION_MS = 11000; // 11 secondi reali per completare l'installazione WebAPK
const timer = setInterval(() => {
const elapsed = Date.now() - startTime;
let pct = Math.min(100, Math.round(15 + (elapsed / TARGET_DURATION_MS) * 85));
if ((window as any).PwaLoader) {
(window as any).PwaLoader.update({ percent: pct });
}
if (pct >= 100) {
clearInterval(timer);
this.isInstalling.set(false);
this.isRedirecting.set(true);
if ((window as any).PwaLoader) {
(window as any).PwaLoader.update({
title: 'Chiudi il browser',
desc: 'Applicazione installata con successo! Chiudi il browser e continua sulla PWA.',
isRedirect: true
});
}
if (window.location.hostname === 'localhost' || window.location.hostname === '127.0.0.1') {
console.log('[AppComponent] Running on localhost - skipping protocol link redirect on appinstalled.');
this.isRedirecting.set(false);
this.checkLoaderDismissal();
return;
}
setTimeout(() => {
window.location.href = this.protocolLink;
}, 1000);
}
}, 250);
});
this.checkAndRedirectToPwa();
@@ -375,9 +415,8 @@ export class AppComponent implements OnInit {
}
async checkVersionSync(): Promise<boolean> {
// Controlla SEMPRE version.json per primo — è il modo più affidabile per
// rilevare un disallineamento di versione, indipendentemente dallo stato del SW.
// Su mobile, checkForUpdate() può essere lento o inaffidabile.
// Verifica all'avvio se è disponibile una nuova versione remota (sia tramite version.json che SwUpdate)
// Se disponibile, viene aggiornata e attivata automaticamente senza richiedere l'intervento dell'utente.
try {
console.log(`[PWA-Update] Verifica version.json all'avvio (locale=${VERSION})...`);
const response = await Promise.race([
@@ -387,10 +426,9 @@ export class AppComponent implements OnInit {
if (response && response.ok) {
const data = await response.json();
if (data && data.version && data.version !== VERSION) {
console.log(`[PWA-Update] Mismatch rilevato: locale=${VERSION}, remota=${data.version}. Forza aggiornamento...`);
const overlay = showFullscreenUpdateOverlay();
console.log(`[PWA-Update] Mismatch rilevato: locale=${VERSION}, remota=${data.version}. Avvio aggiornamento automatico...`);
// Prova ad attivare tramite SwUpdate se abilitato (scarica il nuovo bundle SW)
// Scarica e attiva il nuovo Service Worker se abilitato
if (this.swUpdate.isEnabled) {
try {
const hasSwUpdate = await Promise.race([
@@ -398,7 +436,7 @@ export class AppComponent implements OnInit {
new Promise<boolean>((resolve) => setTimeout(() => resolve(false), 8000))
]);
if (hasSwUpdate) {
console.log('[PWA-Update] SW aggiornamento disponibile, attivazione...');
console.log('[PWA-Update] SW aggiornamento disponibile, attivazione automatica...');
await Promise.race([
this.swUpdate.activateUpdate(),
new Promise<void>((resolve) => setTimeout(resolve, 5000))
@@ -409,7 +447,7 @@ export class AppComponent implements OnInit {
}
}
// Aggiorna anche la registrazione SW direttamente (doppia sicurezza)
// Forziamo il controllo di aggiornamento della registrazione Service Worker
if ('serviceWorker' in navigator) {
try {
const registration = await navigator.serviceWorker.ready;
@@ -419,7 +457,7 @@ export class AppComponent implements OnInit {
}
}
// Disattiva service worker attivi per forzare il refresh completo
// Deregistra i vecchi Service Worker per applicare la nuova versione pulita
if ('serviceWorker' in navigator) {
const registrations = await navigator.serviceWorker.getRegistrations();
for (const registration of registrations) {
@@ -427,7 +465,7 @@ export class AppComponent implements OnInit {
}
}
// Cancella le cache del browser
// Pulisci le cache del browser
if ('caches' in window) {
const keys = await caches.keys();
for (const key of keys) {
@@ -435,9 +473,7 @@ export class AppComponent implements OnInit {
}
}
overlay.finish();
// Ricarica con parametro cache-busting per forzare l'allineamento remoto
// Ricarica la pagina in modo trasparente
const url = new URL(window.location.href);
url.searchParams.set('update_cb', Date.now().toString());
window.location.replace(url.toString());
@@ -450,13 +486,12 @@ export class AppComponent implements OnInit {
console.warn('[PWA-Update] version.json check fallito:', e);
}
// 2. Fallback: Prova SwUpdate nel caso in cui il controllo version.json sia fallito o sia stato servito dalla cache
// Fallback: Controlla direttamente tramite SwUpdate se non intercettato da version.json
if (this.swUpdate.isEnabled) {
try {
console.log('[PWA-Update] Verifica aggiornamenti via SwUpdate all\'avvio...');
let activated = false;
let overlay: any = null;
const activateAndReload = async () => {
if (activated) return;
@@ -467,7 +502,6 @@ export class AppComponent implements OnInit {
console.warn('[PWA-Update] activateUpdate fallito all\'avvio:', e);
}
// Deregistra i vecchi SW e cancella le cache per un ricaricamento pulito
if ('serviceWorker' in navigator) {
const registrations = await navigator.serviceWorker.getRegistrations();
for (const reg of registrations) {
@@ -481,43 +515,36 @@ export class AppComponent implements OnInit {
}
}
if (overlay) overlay.finish();
setTimeout(() => {
const url = new URL(window.location.href);
url.searchParams.set('update_cb', Date.now().toString());
window.location.replace(url.toString());
}, 600);
const url = new URL(window.location.href);
url.searchParams.set('update_cb', Date.now().toString());
window.location.replace(url.toString());
};
// Sottoscrivi PRIMA di verificare l'aggiornamento per evitare race condition
const sub = this.swUpdate.versionUpdates
.pipe(
filter((evt): evt is VersionReadyEvent => evt.type === 'VERSION_READY'),
first()
)
.subscribe(() => {
console.log('[PWA-Update] VERSION_READY ricevuto all\'avvio');
console.log('[PWA-Update] VERSION_READY ricevuto all\'avvio, ricaricamento automatico...');
activateAndReload();
});
// Concedi fino a 8 secondi al controllo SW — le connessioni mobili possono essere lente
const hasUpdate = await Promise.race([
this.swUpdate.checkForUpdate(),
new Promise<boolean>((resolve) => setTimeout(() => resolve(false), 8000))
]);
if (hasUpdate) {
console.log('[PWA-Update] Aggiornamento rilevato via SwUpdate. Avvio download...');
overlay = showFullscreenUpdateOverlay();
console.log('[PWA-Update] Aggiornamento rilevato via SwUpdate. Applicazione automatica...');
// Timeout di sicurezza di 25 secondi: se VERSION_READY non arriva, attiva comunque
setTimeout(() => {
console.log('[PWA-Update] Safety timeout raggiunto all\'avvio, procedo...');
sub.unsubscribe();
activateAndReload();
}, 25000);
}, 15000);
return true; // Attendi il ricaricamento
return true;
} else {
sub.unsubscribe();
}
@@ -546,34 +573,23 @@ export class AppComponent implements OnInit {
const path = window.location.pathname;
this.protocolLink = `web+canti://open${path}${search}`;
// Controlliamo se abbiamo già salvato che l'app è installata o se possiamo verificarlo
let isInstalled = localStorage.getItem('pwa-installed') === 'true';
if (!isInstalled && 'getInstalledRelatedApps' in navigator) {
let isInstalled = false;
if ('getInstalledRelatedApps' in navigator) {
try {
const relatedApps = await (navigator as any).getInstalledRelatedApps();
isInstalled = relatedApps.length > 0;
if (isInstalled) {
localStorage.setItem('pwa-installed', 'true');
}
} catch (e) {
console.warn('Failed to check installed apps:', e);
}
} else {
isInstalled = localStorage.getItem('pwa-installed') === 'true';
}
// Se non è rilevata in localStorage/relatedApps ed è Android o Desktop con supporto ai prompt:
// attendiamo 1.5s per dare tempo all'evento 'beforeinstallprompt' di scattare.
// Se non scatta, significa che l'app è già installata.
if (!isInstalled && !this.settingsService.isIos() && ('onbeforeinstallprompt' in window)) {
await new Promise(resolve => setTimeout(resolve, 1500));
isInstalled = localStorage.getItem('pwa-installed') === 'true';
if (!isInstalled) {
const hasPrompt = this.settingsService.showInstallButton() || this.settingsService.deferredPrompt();
if (!hasPrompt) {
console.log('[AppComponent] PWA detected as already installed (onbeforeinstallprompt supported but no prompt fired).');
isInstalled = true;
localStorage.setItem('pwa-installed', 'true');
}
}
// Se prima era salvata come installata ma l'utente riceve il prima possibile un beforeinstallprompt,
// significa che l'app è stata disinstallata!
if (this.settingsService.deferredPrompt() || this.settingsService.showInstallButton()) {
isInstalled = false;
localStorage.setItem('pwa-installed', 'false');
}
this.isPwaInstalled.set(isInstalled);