779 lines
26 KiB
TypeScript
779 lines
26 KiB
TypeScript
import { Component, inject, OnInit, signal, effect } from '@angular/core';
|
|
import { ThemeService } from './services/theme.service';
|
|
import { CantiService } from './services/canti.service';
|
|
import { SettingsService } from './services/settings.service';
|
|
import { VERSION } from './version';
|
|
import { Router, ActivatedRoute, NavigationStart } from '@angular/router';
|
|
import { ToastController, Platform } from '@ionic/angular';
|
|
import { SwUpdate, VersionReadyEvent } from '@angular/service-worker';
|
|
import { filter, first } from 'rxjs/operators';
|
|
import { App } from '@capacitor/app';
|
|
import { addIcons } from 'ionicons';
|
|
import {
|
|
add,
|
|
addCircleOutline,
|
|
addOutline,
|
|
analyticsOutline,
|
|
arrowUpCircle,
|
|
bookOutline,
|
|
calendarOutline,
|
|
cameraOutline,
|
|
cameraReverseOutline,
|
|
carOutline,
|
|
chevronBack,
|
|
chevronBackOutline,
|
|
chevronDown,
|
|
chevronDownOutline,
|
|
chevronForward,
|
|
chevronForwardOutline,
|
|
chevronUp,
|
|
chevronUpOutline,
|
|
closeCircle,
|
|
closeCircleOutline,
|
|
closeOutline,
|
|
cloudDownloadOutline,
|
|
cloudOfflineOutline,
|
|
cloudUploadOutline,
|
|
contrastOutline,
|
|
copyOutline,
|
|
createOutline,
|
|
documentAttachOutline,
|
|
documentTextOutline,
|
|
downloadOutline,
|
|
eyeOutline,
|
|
informationCircleOutline,
|
|
keypadOutline,
|
|
listOutline,
|
|
logoApple,
|
|
logoYoutube,
|
|
mic,
|
|
micOutline,
|
|
musicalNote,
|
|
musicalNotesOutline,
|
|
pause,
|
|
pauseSharp,
|
|
peopleOutline,
|
|
personOutline,
|
|
phoneLandscapeOutline,
|
|
play,
|
|
playForwardOutline,
|
|
playSharp,
|
|
playSkipBackSharp,
|
|
playSkipForwardSharp,
|
|
pricetagsOutline,
|
|
qrCodeOutline,
|
|
refreshOutline,
|
|
remove,
|
|
removeCircleOutline,
|
|
removeOutline,
|
|
reorderTwoOutline,
|
|
saveOutline,
|
|
scanOutline,
|
|
searchOutline,
|
|
settingsOutline,
|
|
shareOutline,
|
|
shareSocialOutline,
|
|
sparklesOutline,
|
|
statsChartOutline,
|
|
swapVerticalOutline,
|
|
trashOutline,
|
|
arrowUndoOutline,
|
|
videocam,
|
|
videocamOffOutline
|
|
} from 'ionicons/icons';
|
|
|
|
@Component({
|
|
selector: 'app-root',
|
|
templateUrl: 'app.component.html',
|
|
styleUrls: ['app.component.scss'],
|
|
standalone: false,
|
|
})
|
|
export class AppComponent implements OnInit {
|
|
private themeService = inject(ThemeService); // Ensures theme is initialized at boot
|
|
public cantiService = inject(CantiService);
|
|
public settingsService = inject(SettingsService);
|
|
public version = VERSION;
|
|
private platform = inject(Platform);
|
|
private router = inject(Router);
|
|
private route = inject(ActivatedRoute);
|
|
private toastCtrl = inject(ToastController);
|
|
private swUpdate = inject(SwUpdate);
|
|
|
|
public showRedirectOverlay = signal<boolean>(false);
|
|
public showInstallOverlay = signal<boolean>(false);
|
|
public isInstalling = signal<boolean>(false);
|
|
public isRedirecting = signal<boolean>(false);
|
|
public isPwaInstalled = signal<boolean>(false);
|
|
public redirectFailed = signal<boolean>(false);
|
|
public protocolLink = '';
|
|
|
|
constructor() {
|
|
// Gli aggiornamenti automatici e periodici sono stati rimossi.
|
|
// L'aggiornamento viene gestito esclusivamente in modo manuale
|
|
// tramite il pulsante "Verifica Aggiornamenti App" in SettingsPage.
|
|
|
|
addIcons({
|
|
add,
|
|
'add-circle-outline': addCircleOutline,
|
|
'add-outline': addOutline,
|
|
'analytics-outline': analyticsOutline,
|
|
'arrow-up-circle': arrowUpCircle,
|
|
'book-outline': bookOutline,
|
|
'calendar-outline': calendarOutline,
|
|
'camera-outline': cameraOutline,
|
|
'camera-reverse-outline': cameraReverseOutline,
|
|
'car-outline': carOutline,
|
|
'chevron-back': chevronBack,
|
|
'chevron-back-outline': chevronBackOutline,
|
|
'chevron-down': chevronDown,
|
|
'chevron-down-outline': chevronDownOutline,
|
|
'chevron-forward': chevronForward,
|
|
'chevron-forward-outline': chevronForwardOutline,
|
|
'chevron-up': chevronUp,
|
|
'chevron-up-outline': chevronUpOutline,
|
|
'close-circle': closeCircle,
|
|
'close-circle-outline': closeCircleOutline,
|
|
'close-outline': closeOutline,
|
|
'cloud-download-outline': cloudDownloadOutline,
|
|
'cloud-offline-outline': cloudOfflineOutline,
|
|
'cloud-upload-outline': cloudUploadOutline,
|
|
'contrast-outline': contrastOutline,
|
|
'copy-outline': copyOutline,
|
|
'create-outline': createOutline,
|
|
'document-attach-outline': documentAttachOutline,
|
|
'document-text-outline': documentTextOutline,
|
|
'download-outline': downloadOutline,
|
|
'eye-outline': eyeOutline,
|
|
'information-circle-outline': informationCircleOutline,
|
|
'keypad-outline': keypadOutline,
|
|
'list-outline': listOutline,
|
|
'logo-apple': logoApple,
|
|
'logo-youtube': logoYoutube,
|
|
mic,
|
|
'mic-outline': micOutline,
|
|
'musical-note': musicalNote,
|
|
'musical-notes-outline': musicalNotesOutline,
|
|
pause,
|
|
'pause-sharp': pauseSharp,
|
|
'people-outline': peopleOutline,
|
|
'person-outline': personOutline,
|
|
'phone-landscape-outline': phoneLandscapeOutline,
|
|
play,
|
|
'play-forward-outline': playForwardOutline,
|
|
'play-sharp': playSharp,
|
|
'play-skip-back-sharp': playSkipBackSharp,
|
|
'play-skip-forward-sharp': playSkipForwardSharp,
|
|
'pricetags-outline': pricetagsOutline,
|
|
'qr-code-outline': qrCodeOutline,
|
|
'refresh-outline': refreshOutline,
|
|
remove,
|
|
'remove-circle-outline': removeCircleOutline,
|
|
'remove-outline': removeOutline,
|
|
'reorder-two-outline': reorderTwoOutline,
|
|
'save-outline': saveOutline,
|
|
'scan-outline': scanOutline,
|
|
'search-outline': searchOutline,
|
|
'settings-outline': settingsOutline,
|
|
'share-outline': shareOutline,
|
|
'share-social-outline': shareSocialOutline,
|
|
'sparkles-outline': sparklesOutline,
|
|
'stats-chart-outline': statsChartOutline,
|
|
'swap-vertical-outline': swapVerticalOutline,
|
|
'trash-outline': trashOutline,
|
|
'arrow-undo-outline': arrowUndoOutline,
|
|
videocam,
|
|
'videocam-off-outline': videocamOffOutline
|
|
});
|
|
|
|
effect(() => {
|
|
this.checkLoaderDismissal();
|
|
});
|
|
}
|
|
|
|
checkLoaderDismissal() {
|
|
const ready = this.settingsService.isVersionCheckComplete() && this.cantiService.firstLoadCompleted();
|
|
if (!ready) {
|
|
return;
|
|
}
|
|
|
|
// Se stiamo attivamente installando o reindirizzando, NON nascondiamo il loader
|
|
if (this.isInstalling() || this.isRedirecting()) {
|
|
return;
|
|
}
|
|
|
|
// Altrimenti, nascondiamo il loader per far entrare l'utente
|
|
if ((window as any).PwaLoader) {
|
|
(window as any).PwaLoader.hide();
|
|
}
|
|
}
|
|
|
|
async ngOnInit() {
|
|
// Gestione tasto back per PWA/Browser (intercettando popstate di Angular Router)
|
|
this.router.events.subscribe(event => {
|
|
if (event instanceof NavigationStart && event.navigationTrigger === 'popstate') {
|
|
const targetUrl = event.url.split('?')[0];
|
|
if (targetUrl !== '/home' && targetUrl !== '/') {
|
|
this.router.navigate(['/home'], { replaceUrl: true });
|
|
}
|
|
}
|
|
});
|
|
|
|
// Gestione tasto back hardware per nativo (Capacitor/Cordova)
|
|
this.platform.backButton.subscribeWithPriority(9999, () => {
|
|
const currentUrl = this.router.url;
|
|
const path = currentUrl.split('?')[0];
|
|
if (path !== '/home' && path !== '/') {
|
|
this.router.navigate(['/home']);
|
|
} else {
|
|
App.exitApp();
|
|
}
|
|
});
|
|
|
|
// Add global horizontal scroll support for wheel on horizontal containers
|
|
window.addEventListener('wheel', (event: WheelEvent) => {
|
|
if (Math.abs(event.deltaY) > 0 && Math.abs(event.deltaX) === 0) {
|
|
const path = event.composedPath();
|
|
for (const target of path) {
|
|
if (target instanceof HTMLElement) {
|
|
const style = window.getComputedStyle(target);
|
|
const isHorizontalScroll =
|
|
(style.overflowX === 'auto' || style.overflowX === 'scroll') &&
|
|
target.scrollWidth > target.clientWidth;
|
|
|
|
if (isHorizontalScroll) {
|
|
target.scrollLeft += event.deltaY;
|
|
event.preventDefault();
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}, { passive: false });
|
|
|
|
// 1. Allineamento istantaneo alla versione remota
|
|
if ((window as any).PwaLoader) {
|
|
(window as any).PwaLoader.update({
|
|
phase: 'Fase: Verifica Versione',
|
|
desc: 'Verifica della versione più recente in corso...'
|
|
});
|
|
}
|
|
|
|
const updated = await this.checkVersionSync();
|
|
if (updated) {
|
|
return; // Reloading, skip further setup
|
|
}
|
|
|
|
// Set signal indicating startup version check is complete
|
|
this.settingsService.isVersionCheckComplete.set(true);
|
|
|
|
// Handle PWA Launch Queue if supported (focus-existing launch behavior)
|
|
if ('launchQueue' in window) {
|
|
(window as any).launchQueue.setConsumer((launchParams: any) => {
|
|
if (launchParams.targetURL) {
|
|
this.handleLaunchUrl(launchParams.targetURL);
|
|
}
|
|
});
|
|
}
|
|
|
|
this.route.queryParams.subscribe(params => {
|
|
const protocolUrl = params['url'];
|
|
if (protocolUrl && protocolUrl.startsWith('web+canti:')) {
|
|
try {
|
|
const cleanUrl = protocolUrl.replace('web+canti://', 'http://localhost/');
|
|
const urlObj = new URL(cleanUrl);
|
|
|
|
let targetPath = urlObj.pathname;
|
|
if (targetPath === '/open' || targetPath === '//open') {
|
|
targetPath = '/';
|
|
} else if (targetPath.startsWith('/open/')) {
|
|
targetPath = targetPath.substring(5);
|
|
}
|
|
|
|
const queryParams: any = {};
|
|
urlObj.searchParams.forEach((value, key) => {
|
|
queryParams[key] = value;
|
|
});
|
|
|
|
this.router.navigate([targetPath], { queryParams, replaceUrl: true });
|
|
} catch (e) {
|
|
console.error('Failed to parse protocol url:', protocolUrl, e);
|
|
}
|
|
}
|
|
});
|
|
window.addEventListener('appinstalled', () => {
|
|
console.log('[AppComponent] PWA appinstalled event caught.');
|
|
localStorage.setItem('pwa-installed', 'true');
|
|
this.isPwaInstalled.set(true);
|
|
this.showInstallOverlay.set(false);
|
|
this.showRedirectOverlay.set(false);
|
|
|
|
// 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;
|
|
}
|
|
|
|
// 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();
|
|
}
|
|
|
|
handleLaunchUrl(urlStr: string) {
|
|
try {
|
|
const urlObj = new URL(urlStr);
|
|
|
|
// 1. Check if it's a protocol link inside query params (e.g. /?url=web+canti://...)
|
|
const protocolUrl = urlObj.searchParams.get('url');
|
|
if (protocolUrl && protocolUrl.startsWith('web+canti:')) {
|
|
const cleanUrl = protocolUrl.replace('web+canti://', 'http://localhost/');
|
|
const innerUrlObj = new URL(cleanUrl);
|
|
|
|
let targetPath = innerUrlObj.pathname;
|
|
if (targetPath === '/open' || targetPath === '//open') {
|
|
targetPath = '/';
|
|
} else if (targetPath.startsWith('/open/')) {
|
|
targetPath = targetPath.substring(5);
|
|
}
|
|
|
|
const queryParams: any = {};
|
|
innerUrlObj.searchParams.forEach((value, key) => {
|
|
queryParams[key] = value;
|
|
});
|
|
|
|
console.log('[AppComponent] Launch queue routing (protocol) to:', targetPath, queryParams);
|
|
this.router.navigate([targetPath], { queryParams, replaceUrl: true });
|
|
return;
|
|
}
|
|
|
|
// 2. Otherwise, route directly to the pathname and query params of the URL
|
|
let targetPath = urlObj.pathname;
|
|
const queryParams: any = {};
|
|
urlObj.searchParams.forEach((value, key) => {
|
|
queryParams[key] = value;
|
|
});
|
|
|
|
console.log('[AppComponent] Launch queue routing (direct) to:', targetPath, queryParams);
|
|
this.router.navigate([targetPath], { queryParams, replaceUrl: true });
|
|
} catch (e) {
|
|
console.error('Failed to parse launch url:', urlStr, e);
|
|
}
|
|
}
|
|
|
|
async checkVersionSync(): Promise<boolean> {
|
|
// 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([
|
|
fetch(`/version.json?cb=${Date.now()}`, { cache: 'no-store' }),
|
|
new Promise<Response | null>((resolve) => setTimeout(() => resolve(null), 5000))
|
|
]);
|
|
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}. Avvio aggiornamento automatico...`);
|
|
|
|
// Scarica e attiva il nuovo Service Worker se abilitato
|
|
if (this.swUpdate.isEnabled) {
|
|
try {
|
|
const hasSwUpdate = await Promise.race([
|
|
this.swUpdate.checkForUpdate(),
|
|
new Promise<boolean>((resolve) => setTimeout(() => resolve(false), 8000))
|
|
]);
|
|
if (hasSwUpdate) {
|
|
console.log('[PWA-Update] SW aggiornamento disponibile, attivazione automatica...');
|
|
await Promise.race([
|
|
this.swUpdate.activateUpdate(),
|
|
new Promise<void>((resolve) => setTimeout(resolve, 5000))
|
|
]);
|
|
}
|
|
} catch (e) {
|
|
console.warn('[PWA-Update] SwUpdate durante mismatch fallito:', e);
|
|
}
|
|
}
|
|
|
|
// Forziamo il controllo di aggiornamento della registrazione Service Worker
|
|
if ('serviceWorker' in navigator) {
|
|
try {
|
|
const registration = await navigator.serviceWorker.ready;
|
|
await registration.update();
|
|
} catch (e) {
|
|
console.warn('[PWA-Update] SW registration.update fallito:', e);
|
|
}
|
|
}
|
|
|
|
// 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) {
|
|
await registration.unregister();
|
|
}
|
|
}
|
|
|
|
// Pulisci le cache del browser
|
|
if ('caches' in window) {
|
|
const keys = await caches.keys();
|
|
for (const key of keys) {
|
|
await caches.delete(key);
|
|
}
|
|
}
|
|
|
|
// 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());
|
|
return true;
|
|
} else {
|
|
console.log('[PWA-Update] Versione allineata, nessun aggiornamento necessario.');
|
|
}
|
|
}
|
|
} catch (e) {
|
|
console.warn('[PWA-Update] version.json check fallito:', e);
|
|
}
|
|
|
|
// 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;
|
|
|
|
const activateAndReload = async () => {
|
|
if (activated) return;
|
|
activated = true;
|
|
try {
|
|
await this.swUpdate.activateUpdate();
|
|
} catch (e) {
|
|
console.warn('[PWA-Update] activateUpdate fallito all\'avvio:', e);
|
|
}
|
|
|
|
if ('serviceWorker' in navigator) {
|
|
const registrations = await navigator.serviceWorker.getRegistrations();
|
|
for (const reg of registrations) {
|
|
await reg.unregister();
|
|
}
|
|
}
|
|
if ('caches' in window) {
|
|
const keys = await caches.keys();
|
|
for (const key of keys) {
|
|
await caches.delete(key);
|
|
}
|
|
}
|
|
|
|
const url = new URL(window.location.href);
|
|
url.searchParams.set('update_cb', Date.now().toString());
|
|
window.location.replace(url.toString());
|
|
};
|
|
|
|
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, ricaricamento automatico...');
|
|
activateAndReload();
|
|
});
|
|
|
|
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. Applicazione automatica...');
|
|
|
|
setTimeout(() => {
|
|
console.log('[PWA-Update] Safety timeout raggiunto all\'avvio, procedo...');
|
|
sub.unsubscribe();
|
|
activateAndReload();
|
|
}, 15000);
|
|
|
|
return true;
|
|
} else {
|
|
sub.unsubscribe();
|
|
}
|
|
} catch (err) {
|
|
console.warn('[PWA-Update] Controllo SwUpdate fallito all\'avvio:', err);
|
|
}
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
async checkAndRedirectToPwa() {
|
|
if (window.location.hostname === 'localhost' || window.location.hostname === '127.0.0.1') {
|
|
console.log('[AppComponent] Running on localhost - skipping PWA redirect and install overlays.');
|
|
this.checkLoaderDismissal();
|
|
return;
|
|
}
|
|
|
|
const isStandalone = window.matchMedia('(display-mode: standalone)').matches || (navigator as any).standalone;
|
|
if (isStandalone) {
|
|
localStorage.setItem('pwa-installed', 'true');
|
|
return;
|
|
}
|
|
|
|
const search = window.location.search;
|
|
const path = window.location.pathname;
|
|
this.protocolLink = `web+canti://open${path}${search}`;
|
|
|
|
let isInstalled = false;
|
|
if ('getInstalledRelatedApps' in navigator) {
|
|
try {
|
|
const relatedApps = await (navigator as any).getInstalledRelatedApps();
|
|
isInstalled = relatedApps.length > 0;
|
|
} catch (e) {
|
|
console.warn('Failed to check installed apps:', e);
|
|
}
|
|
} else {
|
|
isInstalled = localStorage.getItem('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);
|
|
|
|
if (isInstalled) {
|
|
if (sessionStorage.getItem('skip-pwa-redirect') === 'true') {
|
|
this.showRedirectOverlay.set(false);
|
|
this.checkLoaderDismissal();
|
|
return;
|
|
}
|
|
this.showRedirectOverlay.set(true);
|
|
this.redirectFailed.set(false);
|
|
if ((window as any).PwaLoader) {
|
|
(window as any).PwaLoader.update({ isRedirect: true });
|
|
}
|
|
// Tentiamo il reindirizzamento automatico
|
|
setTimeout(() => {
|
|
window.location.href = this.protocolLink;
|
|
|
|
// Se dopo 2 secondi l'utente è ancora qui, mostriamo lo stato fallito per aprire manualmente o indicare la disinstallazione
|
|
setTimeout(() => {
|
|
if (this.showRedirectOverlay()) {
|
|
this.redirectFailed.set(true);
|
|
if ((window as any).PwaLoader) {
|
|
(window as any).PwaLoader.hide();
|
|
}
|
|
}
|
|
}, 2000);
|
|
}, 800);
|
|
} else {
|
|
// Se non è installata, proponiamo l'installazione immediata per evitare la cache del browser e avere un'esperienza ottimale
|
|
const skipInstall = sessionStorage.getItem('skip-pwa-install') === 'true';
|
|
const isMobile = this.settingsService.isAndroid() || this.settingsService.isIos();
|
|
const hasDesktopPrompt = this.settingsService.showInstallButton() || this.settingsService.deferredPrompt();
|
|
if (!skipInstall && (isMobile || hasDesktopPrompt)) {
|
|
this.showInstallOverlay.set(true);
|
|
} else {
|
|
this.checkLoaderDismissal();
|
|
}
|
|
}
|
|
}
|
|
|
|
openPwaManual() {
|
|
window.location.href = this.protocolLink;
|
|
if ((window as any).PwaLoader) {
|
|
(window as any).PwaLoader.show();
|
|
(window as any).PwaLoader.update({ isRedirect: true });
|
|
}
|
|
// Se dopo 2 secondi l'utente è ancora qui, probabilmente l'app non è installata
|
|
setTimeout(() => {
|
|
if (this.showRedirectOverlay()) {
|
|
this.redirectFailed.set(true);
|
|
localStorage.setItem('pwa-installed', 'false');
|
|
if ((window as any).PwaLoader) {
|
|
(window as any).PwaLoader.hide();
|
|
}
|
|
}
|
|
}, 2000);
|
|
}
|
|
|
|
stayInBrowser() {
|
|
sessionStorage.setItem('skip-pwa-redirect', 'true');
|
|
this.showRedirectOverlay.set(false);
|
|
this.checkLoaderDismissal();
|
|
}
|
|
|
|
stayInBrowserForceUninstallCheck() {
|
|
localStorage.setItem('pwa-installed', 'false');
|
|
this.isPwaInstalled.set(false);
|
|
this.stayInBrowser();
|
|
}
|
|
|
|
closeInstallOverlay() {
|
|
sessionStorage.setItem('skip-pwa-install', 'true');
|
|
this.showInstallOverlay.set(false);
|
|
this.checkLoaderDismissal();
|
|
}
|
|
|
|
async triggerInstall() {
|
|
this.isInstalling.set(true);
|
|
this.showInstallOverlay.set(false);
|
|
if ((window as any).PwaLoader) {
|
|
(window as any).PwaLoader.show();
|
|
(window as any).PwaLoader.update({
|
|
title: 'Installazione in corso...',
|
|
desc: 'Completa l\'installazione tramite la finestra del browser.'
|
|
});
|
|
}
|
|
|
|
const outcome = await this.settingsService.installPwa();
|
|
if (outcome === 'accepted') {
|
|
this.isInstalling.set(false);
|
|
this.isRedirecting.set(true);
|
|
if ((window as any).PwaLoader) {
|
|
(window as any).PwaLoader.update({
|
|
title: 'Apertura Applicazione...',
|
|
desc: 'Installazione completata! Chiudi il browser e continua sulla PWA.',
|
|
isRedirect: true
|
|
});
|
|
}
|
|
} else {
|
|
this.isInstalling.set(false);
|
|
this.isRedirecting.set(false);
|
|
this.showInstallOverlay.set(true);
|
|
this.checkLoaderDismissal();
|
|
}
|
|
}
|
|
|
|
async triggerInstallFromRedirect() {
|
|
this.isInstalling.set(true);
|
|
this.showRedirectOverlay.set(false);
|
|
if ((window as any).PwaLoader) {
|
|
(window as any).PwaLoader.show();
|
|
(window as any).PwaLoader.update({
|
|
title: 'Installazione in corso...',
|
|
desc: 'Completa l\'installazione tramite la finestra del browser.'
|
|
});
|
|
}
|
|
|
|
const outcome = await this.settingsService.installPwa();
|
|
if (outcome === 'accepted') {
|
|
sessionStorage.setItem('skip-pwa-redirect', 'true');
|
|
this.isInstalling.set(false);
|
|
this.isRedirecting.set(true);
|
|
if ((window as any).PwaLoader) {
|
|
(window as any).PwaLoader.update({
|
|
title: 'Apertura Applicazione...',
|
|
desc: 'Installazione completata! Chiudi il browser e continua sulla PWA.',
|
|
isRedirect: true
|
|
});
|
|
}
|
|
} else {
|
|
this.isInstalling.set(false);
|
|
this.isRedirecting.set(false);
|
|
this.showRedirectOverlay.set(true);
|
|
this.checkLoaderDismissal();
|
|
}
|
|
}
|
|
}
|
|
|
|
export function showFullscreenUpdateOverlay() {
|
|
if ((window as any).PwaLoader) {
|
|
(window as any).PwaLoader.show();
|
|
(window as any).PwaLoader.update({
|
|
title: 'Download aggiornamento',
|
|
phase: 'Fase: Download',
|
|
desc: 'Scaricamento della nuova versione...',
|
|
percent: 0
|
|
});
|
|
}
|
|
|
|
// Fetch remote version to display the version being downloaded
|
|
fetch(`/version.json?cb=${Date.now()}`)
|
|
.then(res => {
|
|
if (res.ok) return res.json();
|
|
throw new Error('Fallback');
|
|
})
|
|
.then(data => {
|
|
if (data && data.version && (window as any).PwaLoader) {
|
|
(window as any).PwaLoader.update({
|
|
version: 'Versione ' + data.version
|
|
});
|
|
}
|
|
})
|
|
.catch(() => {});
|
|
|
|
let percent = 0;
|
|
const interval = setInterval(() => {
|
|
if (percent < 95) {
|
|
percent += Math.floor(Math.random() * 5) + 2;
|
|
if (percent > 95) percent = 95;
|
|
if ((window as any).PwaLoader) {
|
|
(window as any).PwaLoader.update({ percent });
|
|
}
|
|
}
|
|
}, 150);
|
|
|
|
return {
|
|
finish: () => {
|
|
clearInterval(interval);
|
|
if ((window as any).PwaLoader) {
|
|
(window as any).PwaLoader.update({ percent: 100 });
|
|
}
|
|
}
|
|
};
|
|
}
|