diff --git a/src/app/app.component.ts b/src/app/app.component.ts index b639a4d..4f22289 100644 --- a/src/app/app.component.ts +++ b/src/app/app.component.ts @@ -2,6 +2,7 @@ import { Component, inject, ApplicationRef } from '@angular/core'; import { SwUpdate, VersionReadyEvent } from '@angular/service-worker'; import { filter, first } from 'rxjs/operators'; import { concat, interval, fromEvent } from 'rxjs'; +import { ToastController } from '@ionic/angular'; @Component({ selector: 'app-root', @@ -12,6 +13,7 @@ import { concat, interval, fromEvent } from 'rxjs'; export class AppComponent { private swUpdate = inject(SwUpdate); private appRef = inject(ApplicationRef); + private toastCtrl = inject(ToastController); constructor() { this.setupUpdates(); @@ -55,14 +57,29 @@ export class AppComponent { } }); - // 4. Activate update and reload when a new version is ready + // 4. Activate update and reload when a new version is ready (Show interactive toast) this.swUpdate.versionUpdates .pipe(filter((evt): evt is VersionReadyEvent => evt.type === 'VERSION_READY')) - .subscribe(() => { - console.log('[PWA-Update] New version ready! Activating and reloading...'); - this.swUpdate.activateUpdate().then(() => { - window.location.reload(); + .subscribe(async () => { + console.log('[PWA-Update] New version ready! Showing toast prompt...'); + const toast = await this.toastCtrl.create({ + message: 'Nuova versione dell\'applicazione disponibile!', + position: 'bottom', + color: 'secondary', + buttons: [ + { + text: 'Aggiorna', + role: 'cancel', + handler: () => { + console.log('[PWA-Update] Activating update and reloading...'); + this.swUpdate.activateUpdate().then(() => { + window.location.replace(window.location.origin + window.location.pathname + '?update=' + Date.now()); + }); + } + } + ] }); + await toast.present(); }); } } diff --git a/src/app/components/qr-scanner/qr-scanner.component.html b/src/app/components/qr-scanner/qr-scanner.component.html index 97a871d..cc8d944 100644 --- a/src/app/components/qr-scanner/qr-scanner.component.html +++ b/src/app/components/qr-scanner/qr-scanner.component.html @@ -11,11 +11,30 @@
+ Su iPad, se non vedi lo switch fotocamera, tocca l'icona "aA" in alto nella barra di Safari e seleziona "Richiedi sito mobile". +
++
Apple non consente l'installazione automatica dei siti web. Segui questi semplici passi da Safari:
-Forza la ricerca di una nuova versione dell'applicazione
+
Versione: v{{ version }} •
diff --git a/src/app/pages/settings/settings.page.ts b/src/app/pages/settings/settings.page.ts
index 45a560e..a3a2cc5 100644
--- a/src/app/pages/settings/settings.page.ts
+++ b/src/app/pages/settings/settings.page.ts
@@ -3,11 +3,12 @@ import { ThemeService } from '../../services/theme.service';
import { SettingsService } from '../../services/settings.service';
import { CantiService } from '../../services/canti.service';
import { ConnectivityService } from '../../services/connectivity.service';
-import { SwUpdate } from '@angular/service-worker';
+import { SwUpdate, VersionReadyEvent } from '@angular/service-worker';
import { ToastController, ModalController, AlertController } from '@ionic/angular';
import { VERSION } from '../../version';
import { PlaylistService } from '../../services/playlist.service';
import { Router } from '@angular/router';
+import { filter, first } from 'rxjs/operators';
import { MyCantiService } from '../../services/my-canti.service';
import { CantiLettureService } from '../../services/canti-letture.service';
@@ -91,9 +92,23 @@ export class SettingsPage {
});
await toast.present();
- setTimeout(() => {
- window.location.reload();
- }, 2000);
+ this.swUpdate.versionUpdates
+ .pipe(
+ filter((evt): evt is VersionReadyEvent => evt.type === 'VERSION_READY'),
+ first()
+ )
+ .subscribe(async () => {
+ console.log('[PWA-Update] FullRefresh: version ready, activating...');
+ await this.swUpdate.activateUpdate();
+ window.location.replace(window.location.origin + window.location.pathname + '?update=' + Date.now());
+ });
+
+ setTimeout(async () => {
+ try {
+ await this.swUpdate.activateUpdate();
+ } catch(e) {}
+ window.location.replace(window.location.origin + window.location.pathname + '?update=' + Date.now());
+ }, 6000);
return;
}
} catch (err) {
@@ -108,4 +123,70 @@ export class SettingsPage {
});
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;
+ }
+
+ const toastLoading = await this.toastCtrl.create({
+ message: 'Ricerca aggiornamenti in corso...',
+ duration: 1500,
+ color: 'secondary'
+ });
+ await toastLoading.present();
+
+ try {
+ const updateFound = await this.swUpdate.checkForUpdate();
+ if (updateFound) {
+ const toast = await this.toastCtrl.create({
+ message: 'Nuova versione trovata! Installazione e attivazione in corso...',
+ duration: 3000,
+ color: 'success'
+ });
+ await toast.present();
+
+ // Sottoscrizione per attivare l'aggiornamento appena terminato il download
+ this.swUpdate.versionUpdates
+ .pipe(
+ filter((evt): evt is VersionReadyEvent => evt.type === 'VERSION_READY'),
+ first()
+ )
+ .subscribe(async () => {
+ console.log('[PWA-Update] Manual check: version ready, activating...');
+ await this.swUpdate.activateUpdate();
+ window.location.replace(window.location.origin + window.location.pathname + '?update=' + Date.now());
+ });
+
+ // Timeout di sicurezza per forzare l'attivazione e il ricaricamento se è già scaricato
+ setTimeout(async () => {
+ try {
+ await this.swUpdate.activateUpdate();
+ } catch(e) {}
+ window.location.replace(window.location.origin + window.location.pathname + '?update=' + Date.now());
+ }, 8000);
+ } else {
+ const toast = await this.toastCtrl.create({
+ message: 'L\'applicazione è già aggiornata all\'ultima versione.',
+ duration: 3000,
+ color: 'success'
+ });
+ await toast.present();
+ }
+ } catch (err) {
+ console.error('Check update failed', err);
+ const toast = await this.toastCtrl.create({
+ message: 'Errore durante la ricerca di aggiornamenti.',
+ duration: 3000,
+ color: 'danger'
+ });
+ await toast.present();
+ }
+ }
}
diff --git a/src/app/services/canti.service.ts b/src/app/services/canti.service.ts
index 66c8128..5663d83 100644
--- a/src/app/services/canti.service.ts
+++ b/src/app/services/canti.service.ts
@@ -13,6 +13,7 @@ export interface Canto {
id_momenti?: number[];
data_update?: string;
nonValidato?: boolean;
+ isPersonal?: boolean;
}
export interface Indice {
diff --git a/src/app/services/comunita.service.ts b/src/app/services/comunita.service.ts
index f35f2fa..23baeee 100644
--- a/src/app/services/comunita.service.ts
+++ b/src/app/services/comunita.service.ts
@@ -186,7 +186,8 @@ export class ComunitaService {
link_youtube: cp.link_youtube || '',
id_momenti: [],
data_update: cp.data_update || '',
- nonValidato: true
+ nonValidato: Number(cp.stato) === 10,
+ isPersonal: true
}));
this.comunitaCode.set(trimmedCode);
diff --git a/src/app/services/lyrics-parser.service.ts b/src/app/services/lyrics-parser.service.ts
index d3ebc6d..5a930ce 100644
--- a/src/app/services/lyrics-parser.service.ts
+++ b/src/app/services/lyrics-parser.service.ts
@@ -1,4 +1,5 @@
-import { Injectable } from '@angular/core';
+import { Injectable, inject } from '@angular/core';
+import { SettingsService } from './settings.service';
export interface ChordSegment {
text: string;
@@ -20,6 +21,7 @@ export interface ParsedSection {
providedIn: 'root'
})
export class LyricsParserService {
+ private settingsService = inject(SettingsService);
/**
* Parse plain text (campo 'testo') into structured sections.
@@ -180,7 +182,7 @@ export class LyricsParserService {
* Handles Italian notation.
*/
transposeChord(chord: string, semitones: number): string {
- if (!chord || semitones === 0) return chord;
+ if (!chord) return chord;
// Handle slash chords (e.g., DO/SOL)
if (chord.includes('/')) {
@@ -193,8 +195,9 @@ export class LyricsParserService {
let root = '';
let suffix = '';
+ const upperChord = chord.toUpperCase();
for (const r of possibleRoots) {
- if (chord.startsWith(r)) {
+ if (upperChord.startsWith(r)) {
root = r;
suffix = chord.substring(r.length);
break;
@@ -210,8 +213,24 @@ export class LyricsParserService {
let newIndex = (index + semitones) % 12;
if (newIndex < 0) newIndex += 12;
- // Preserve the original notation style (sharp or flat) if possible
- const useFlat = this.flatScale.includes(root);
+ // Decide flat vs sharp notation based on SettingsService preference:
+ const pref = this.settingsService.chordNotationPreference();
+ let useFlat = false;
+ if (pref === 'diesis') {
+ useFlat = false;
+ } else if (pref === 'bemolle') {
+ useFlat = true;
+ } else {
+ // Fallback/Default logic
+ if (root.includes('#')) {
+ useFlat = false;
+ } else if (root.toLowerCase().includes('b')) {
+ useFlat = true;
+ } else {
+ useFlat = semitones < 0;
+ }
+ }
+
const newRoot = useFlat ? this.flatScale[newIndex] : this.scale[newIndex];
return newRoot + suffix;
@@ -221,8 +240,6 @@ export class LyricsParserService {
* Transpose all chords in a parsed structure.
*/
transposeSections(sections: ParsedSection[], semitones: number): ParsedSection[] {
- if (semitones === 0) return sections;
-
return sections.map(section => ({
...section,
lines: section.lines.map(line => ({
diff --git a/src/app/services/playlist.service.ts b/src/app/services/playlist.service.ts
index 42729d4..bf93d69 100644
--- a/src/app/services/playlist.service.ts
+++ b/src/app/services/playlist.service.ts
@@ -3,6 +3,7 @@ import { Storage } from '@ionic/storage-angular';
import { Canto, CantiService } from './canti.service';
import { ComunitaService } from './comunita.service';
import * as QRCode from 'qrcode';
+import { ToastController } from '@ionic/angular';
@Injectable({
providedIn: 'root'
@@ -11,6 +12,7 @@ export class PlaylistService {
private storage = inject(Storage);
private cantiService = inject(CantiService);
private comunitaService = inject(ComunitaService);
+ private toastCtrl = inject(ToastController);
public selectionMode = signal