Ottimizzazione UI player, rotazione head tilt invertita, prefisso playlist in Home e implementazione Identity QR modal e interceptor API
This commit is contained in:
+43
-24
@@ -7,7 +7,9 @@
|
||||
<span class="app-name">{{ appName }}</span>
|
||||
<span class="version-badge" (click)="checkForAppUpdate($event)" style="cursor: pointer; display: inline-flex; align-items: center; gap: 4px;">
|
||||
v{{ version }}
|
||||
<ion-icon name="refresh-outline" style="font-size: 0.9em;"></ion-icon>
|
||||
<span *ngIf="hasUpdateAvailable()" style="display: inline-flex; align-items: center; justify-content: center; padding: 4px; margin-left: 2px; background: rgba(var(--ion-color-secondary-rgb), 0.25); border-radius: 50%; border: 1px solid var(--ion-color-secondary); width: 24px; height: 24px; box-shadow: 0 0 8px rgba(var(--ion-color-secondary-rgb), 0.3);">
|
||||
<ion-icon name="refresh-outline" style="font-size: 1.15rem; color: var(--ion-color-secondary); font-weight: bold;"></ion-icon>
|
||||
</span>
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
@@ -68,14 +70,13 @@
|
||||
</div>
|
||||
|
||||
<ion-button
|
||||
*ngIf="settingsService.showEditor()"
|
||||
[fill]="showOnlyMine() ? 'solid' : 'outline'"
|
||||
[fill]="activeFilterType() === 'lista_completa' || showValidati() || showNonValidati() ? 'solid' : 'outline'"
|
||||
size="small"
|
||||
(click)="toggleOnlyMine()"
|
||||
color="secondary"
|
||||
(click)="toggleFilterType('lista_completa')"
|
||||
class="filter-chip">
|
||||
Miei
|
||||
<span class="close-icon-wrapper" *ngIf="showOnlyMine()" (click)="clearOnlyMine($event)">
|
||||
{{ showValidati() ? 'Lista: Validati' : (showNonValidati() ? 'Lista: Non Validati' : 'Lista completa') }}
|
||||
<ion-icon slot="end" name="chevron-down-outline" *ngIf="!showValidati() && !showNonValidati()"></ion-icon>
|
||||
<span class="close-icon-wrapper" *ngIf="showValidati() || showNonValidati()" (click)="clearListaCompleta($event)">
|
||||
<ion-icon slot="end" name="close-circle"></ion-icon>
|
||||
</span>
|
||||
</ion-button>
|
||||
@@ -85,7 +86,7 @@
|
||||
size="small"
|
||||
(click)="toggleFilterType('liturgico')"
|
||||
class="filter-chip">
|
||||
{{ selectedLiturgico() !== null ? getSelectedLiturgicoLabel() : 'Liturgia' }}
|
||||
{{ selectedLiturgico() !== null ? 'Liturgia: ' + getSelectedLiturgicoLabel() : 'Liturgia' }}
|
||||
<ion-icon slot="end" name="chevron-down-outline" *ngIf="selectedLiturgico() === null"></ion-icon>
|
||||
<span class="close-icon-wrapper" *ngIf="selectedLiturgico() !== null" (click)="clearLiturgico($event)">
|
||||
<ion-icon slot="end" name="close-circle"></ion-icon>
|
||||
@@ -97,7 +98,7 @@
|
||||
size="small"
|
||||
(click)="toggleFilterType('tematico')"
|
||||
class="filter-chip">
|
||||
{{ selectedTematico() !== null ? getSelectedTematicoLabel() : 'Periodo' }}
|
||||
{{ selectedTematico() !== null ? 'Periodo: ' + getSelectedTematicoLabel() : 'Periodo' }}
|
||||
<ion-icon slot="end" name="chevron-down-outline" *ngIf="selectedTematico() === null"></ion-icon>
|
||||
<span class="close-icon-wrapper" *ngIf="selectedTematico() !== null" (click)="clearTematico($event)">
|
||||
<ion-icon slot="end" name="close-circle"></ion-icon>
|
||||
@@ -120,7 +121,7 @@
|
||||
size="small"
|
||||
(click)="playlistService.allPlaylists().length > 0 ? toggleFilterType('playlist') : playlistService.toggleSelectionMode()"
|
||||
class="filter-chip">
|
||||
{{ playlistService.activeListName() !== null ? playlistService.activeListName() : 'Playlist' }}
|
||||
{{ playlistService.activeListName() !== null ? 'Playlist: ' + playlistService.activeListName() : 'Playlist' }}
|
||||
<ion-icon slot="end" name="chevron-down-outline" *ngIf="playlistService.activeListName() === null && playlistService.allPlaylists().length > 0"></ion-icon>
|
||||
<ion-icon slot="end" name="add-circle-outline" *ngIf="playlistService.activeListName() === null && playlistService.allPlaylists().length === 0"></ion-icon>
|
||||
<span class="close-icon-wrapper" *ngIf="playlistService.activeListName() !== null" (click)="clearSpecialList($event)">
|
||||
@@ -145,7 +146,7 @@
|
||||
|
||||
|
||||
<!-- Category Scroll (Dropdown style) -->
|
||||
<ion-toolbar class="bg-gradient momentos-toolbar" *ngIf="activeFilterType() || playlistService.selectionMode() || (playlistService.activeListName() && !playlistService.selectionMode())">
|
||||
<ion-toolbar class="bg-gradient momentos-toolbar" *ngIf="activeFilterType() || playlistService.selectionMode()">
|
||||
<div class="momento-scroll">
|
||||
<!-- Selection Mode Actions -->
|
||||
<div class="selection-pill glass" *ngIf="playlistService.selectionMode()">
|
||||
@@ -162,7 +163,7 @@
|
||||
</div>
|
||||
|
||||
<!-- Active Playlist Actions -->
|
||||
<div class="selection-pill glass" *ngIf="playlistService.activeListName() && !playlistService.selectionMode()">
|
||||
<div class="selection-pill glass" *ngIf="activeFilterType() === 'playlist' && playlistService.activePlaylistId() && !playlistService.selectionMode()">
|
||||
<ion-button fill="clear" color="secondary" (click)="editPlaylist()" class="mini-action-btn" *ngIf="!isComunitaPlaylist() && isActivePlaylistSaved()">
|
||||
<ion-icon slot="icon-only" name="create-outline"></ion-icon>
|
||||
</ion-button>
|
||||
@@ -170,20 +171,37 @@
|
||||
<ion-icon slot="icon-only" name="share-social-outline"></ion-icon>
|
||||
</ion-button>
|
||||
<ion-button fill="clear" color="danger" (click)="deleteActivePlaylist()" class="mini-action-btn" *ngIf="playlistService.activePlaylistId() && !isComunitaPlaylist()">
|
||||
<ion-icon slot="icon-only" name="trash-outline"></ion-icon>
|
||||
<ion-icon name="trash-outline" slot="icon-only"></ion-icon>
|
||||
</ion-button>
|
||||
</div>
|
||||
|
||||
<!-- Filter chips -->
|
||||
<div
|
||||
*ngFor="let item of (activeFilterType() === 'playlist' ? playlistService.allPlaylists() : (activeFilterType() === 'liturgico' ? cantiService.indiceLiturgico() : (activeFilterType() === 'tematico' ? cantiService.indiceTematico() : [])))"
|
||||
class="momento-chip glass"
|
||||
[class.active]="activeFilterType() === 'playlist' ? playlistService.activePlaylistId() === item.id : isIndexSelected(item.id)"
|
||||
[class.comunita-chip]="activeFilterType() === 'playlist' && item.isComunita"
|
||||
(click)="activeFilterType() === 'playlist' ? selectPlaylist(item) : toggleIndex(item.id, activeFilterType()!)">
|
||||
<ion-icon *ngIf="activeFilterType() === 'playlist' && item.isComunita" name="people-outline" style="font-size: 0.85rem; margin-right: 4px; vertical-align: middle;"></ion-icon>
|
||||
{{ activeFilterType() === 'playlist' ? item.name : item.tag_name }}
|
||||
</div>
|
||||
<ng-container *ngIf="activeFilterType() === 'lista_completa'">
|
||||
<div
|
||||
class="momento-chip glass"
|
||||
[class.active]="showValidati()"
|
||||
(click)="toggleValidati()">
|
||||
Validati
|
||||
</div>
|
||||
<div
|
||||
class="momento-chip glass"
|
||||
[class.active]="showNonValidati()"
|
||||
(click)="toggleNonValidati()">
|
||||
Non Validati
|
||||
</div>
|
||||
</ng-container>
|
||||
|
||||
<ng-container *ngIf="activeFilterType() !== 'lista_completa'">
|
||||
<div
|
||||
*ngFor="let item of (activeFilterType() === 'playlist' ? playlistService.allPlaylists() : (activeFilterType() === 'liturgico' ? cantiService.indiceLiturgico() : (activeFilterType() === 'tematico' ? cantiService.indiceTematico() : [])))"
|
||||
class="momento-chip glass"
|
||||
[class.active]="activeFilterType() === 'playlist' ? playlistService.activePlaylistId() === item.id : isIndexSelected(item.id)"
|
||||
[class.comunita-chip]="activeFilterType() === 'playlist' && item.isComunita"
|
||||
(click)="activeFilterType() === 'playlist' ? selectPlaylist(item) : toggleIndex(item.id, activeFilterType()!)">
|
||||
<ion-icon *ngIf="activeFilterType() === 'playlist' && item.isComunita" name="people-outline" style="font-size: 0.85rem; margin-right: 4px; vertical-align: middle;"></ion-icon>
|
||||
{{ activeFilterType() === 'playlist' ? item.name : item.tag_name }}
|
||||
</div>
|
||||
</ng-container>
|
||||
</div>
|
||||
</ion-toolbar>
|
||||
</ion-header>
|
||||
@@ -284,7 +302,7 @@
|
||||
|
||||
<div class="item-wrapper">
|
||||
<!-- Selection Area -->
|
||||
<div class="selection-column" (click)="playlistService.toggleSongSelection(canto.id); $event.stopPropagation()">
|
||||
<div class="selection-column" (click)="toggleSongSelection(canto.id); $event.stopPropagation()">
|
||||
<span class="canto-number" [class.selected-number]="playlistService.selectedIds().has(canto.id)">
|
||||
{{ canto.id.startsWith('my_') ? getMySongNumber(canto) : canto.id_canti }}
|
||||
</span>
|
||||
@@ -297,7 +315,8 @@
|
||||
<h2 class="outfit-font" style="font-weight: 500; color: var(--ion-color-secondary); display: flex; align-items: center; flex-wrap: wrap; gap: 4px;">
|
||||
<span *ngIf="getCommunitySongNumber(canto)" style="color: var(--ion-color-secondary); font-weight: 600; margin-right: 8px; opacity: 0.9;">{{ getCommunitySongNumber(canto) }}</span>
|
||||
<span>{{ canto.titolo }}</span>
|
||||
<span *ngIf="canto.nonValidato" class="non-validato-badge">Non Validato</span>
|
||||
<span *ngIf="canto.nonValidato && !canto.id.startsWith('my_')" class="non-validato-badge remote-badge">Remoto</span>
|
||||
<span *ngIf="canto.id.startsWith('my_')" class="non-validato-badge mio-badge">Mio</span>
|
||||
</h2>
|
||||
<p style="color: rgba(255,255,255,0.6); margin-bottom: 2px; display: flex; align-items: center; flex-wrap: wrap; gap: 8px;">
|
||||
<span>{{ canto.autore || 'Autore sconosciuto' }}</span>
|
||||
|
||||
@@ -946,12 +946,24 @@ ion-title {
|
||||
margin-left: 6px;
|
||||
display: inline-block;
|
||||
vertical-align: middle;
|
||||
|
||||
&.mio-badge {
|
||||
background: rgba(var(--ion-color-secondary-rgb), 0.15);
|
||||
color: var(--ion-color-secondary);
|
||||
border-color: rgba(var(--ion-color-secondary-rgb), 0.35);
|
||||
}
|
||||
}
|
||||
|
||||
:host-context(body.high-contrast) .non-validato-badge {
|
||||
background: rgba(231, 76, 60, 0.1) !important;
|
||||
color: #c0392b !important;
|
||||
border-color: #c0392b !important;
|
||||
|
||||
&.mio-badge {
|
||||
background: rgba(var(--ion-color-secondary-rgb), 0.1) !important;
|
||||
color: var(--ion-color-secondary-shade, #007bb6) !important;
|
||||
border-color: var(--ion-color-secondary-shade, #007bb6) !important;
|
||||
}
|
||||
}
|
||||
|
||||
/* ==========================================================================
|
||||
|
||||
+421
-17
@@ -33,8 +33,10 @@ export class HomePage implements OnDestroy {
|
||||
public showOnlyMine = signal<boolean>(false);
|
||||
public showTopTen = signal<boolean>(false);
|
||||
public showSuggeriti = signal<boolean>(false);
|
||||
public showValidati = signal<boolean>(false);
|
||||
public showNonValidati = signal<boolean>(false);
|
||||
public isMassCardExpanded = signal<boolean>(false);
|
||||
public activeFilterType = signal<'liturgico' | 'tematico' | 'playlist' | null>(null);
|
||||
public activeFilterType = signal<'liturgico' | 'tematico' | 'playlist' | 'lista_completa' | null>(null);
|
||||
public loadedThumbs = new Set<string>();
|
||||
public version = VERSION;
|
||||
public appName = environment.appName;
|
||||
@@ -44,6 +46,7 @@ export class HomePage implements OnDestroy {
|
||||
|
||||
public showAndroidBanner = signal<boolean>(false);
|
||||
public showIosTooltip = signal<boolean>(false);
|
||||
public hasUpdateAvailable = signal<boolean>(false);
|
||||
|
||||
public fontSize = signal<number>(1.0);
|
||||
public youtubePlayerService = inject(YoutubePlayerService);
|
||||
@@ -73,6 +76,7 @@ export class HomePage implements OnDestroy {
|
||||
private swUpdate = inject(SwUpdate);
|
||||
|
||||
private firstInteraction = true;
|
||||
private updatePollInterval: any = null;
|
||||
|
||||
async checkForAppUpdate(event?: Event) {
|
||||
if (event) event.stopPropagation();
|
||||
@@ -187,6 +191,24 @@ export class HomePage implements OnDestroy {
|
||||
}
|
||||
}
|
||||
|
||||
private async checkUpdateStatus() {
|
||||
try {
|
||||
if (this.swUpdate.isEnabled) {
|
||||
const swFoundUpdate = await this.swUpdate.checkForUpdate().catch(() => false);
|
||||
if (swFoundUpdate) {
|
||||
this.hasUpdateAvailable.set(true);
|
||||
return;
|
||||
}
|
||||
}
|
||||
const mismatch = await this.checkVersionJson();
|
||||
if (mismatch) {
|
||||
this.hasUpdateAvailable.set(true);
|
||||
}
|
||||
} catch (err) {
|
||||
console.warn('Failed to check for updates on startup:', err);
|
||||
}
|
||||
}
|
||||
|
||||
onInteraction() {
|
||||
if (this.firstInteraction) {
|
||||
this.firstInteraction = false;
|
||||
@@ -241,10 +263,11 @@ export class HomePage implements OnDestroy {
|
||||
const comunitaIds = this.comunitaService.comunitaCantiIds();
|
||||
|
||||
if (comunitaCode && this.comunitaService.isFilterActive()) {
|
||||
// Base is standard canti + my canti + community custom canti
|
||||
// Base is standard canti + my canti + community custom canti + remote custom canti
|
||||
list = [
|
||||
...this.cantiService.canti(),
|
||||
...this.myCantiService.myCanti(),
|
||||
...this.playlistService.remoteCustomSongs(),
|
||||
...this.comunitaService.comunitaCantiPersonali()
|
||||
];
|
||||
|
||||
@@ -278,8 +301,12 @@ export class HomePage implements OnDestroy {
|
||||
return numA - numB;
|
||||
});
|
||||
} else {
|
||||
// General context: only standard canti + my canti
|
||||
list = [...this.cantiService.canti(), ...this.myCantiService.myCanti()];
|
||||
// General context: only standard canti + my canti + remote custom canti
|
||||
list = [
|
||||
...this.cantiService.canti(),
|
||||
...this.myCantiService.myCanti(),
|
||||
...this.playlistService.remoteCustomSongs()
|
||||
];
|
||||
}
|
||||
|
||||
// 2. Cumulative filtering
|
||||
@@ -290,7 +317,7 @@ export class HomePage implements OnDestroy {
|
||||
}
|
||||
|
||||
// A. Filter by Playlist (if activeIds is present)
|
||||
if (activeIds.length > 0 && !selectionMode) {
|
||||
if (activeIds.length > 0 && (!selectionMode || this.playlistService.activePlaylistId() === null)) {
|
||||
list = activeIds
|
||||
.map(id => list.find(c => c.id === id))
|
||||
.filter((c): c is any => !!c);
|
||||
@@ -302,6 +329,16 @@ export class HomePage implements OnDestroy {
|
||||
list = list.filter(c => myIds.has(c.id));
|
||||
}
|
||||
|
||||
// Filter by Validati (non personali e non contrassegnati come non validati)
|
||||
if (this.showValidati()) {
|
||||
list = list.filter(c => !c.nonValidato && !c.id.startsWith('my_'));
|
||||
}
|
||||
|
||||
// Filter by Non-Validati (personali o esplicitamente non validati)
|
||||
if (this.showNonValidati()) {
|
||||
list = list.filter(c => c.nonValidato || c.id.startsWith('my_'));
|
||||
}
|
||||
|
||||
// C. Filter by Liturgical Moment
|
||||
if (litId !== null) {
|
||||
list = list.filter(c => c.id_momenti?.includes(litId));
|
||||
@@ -377,6 +414,21 @@ export class HomePage implements OnDestroy {
|
||||
});
|
||||
|
||||
constructor() {
|
||||
// Check if there is an update available
|
||||
this.checkUpdateStatus();
|
||||
if (this.swUpdate.isEnabled) {
|
||||
this.swUpdate.versionUpdates.subscribe(evt => {
|
||||
if (evt.type === 'VERSION_READY') {
|
||||
this.hasUpdateAvailable.set(true);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// Polling setup: check for updates every 30 seconds
|
||||
this.updatePollInterval = setInterval(() => {
|
||||
this.checkUpdateStatus();
|
||||
}, 30000);
|
||||
|
||||
// Check if install prompts should be visible
|
||||
const androidDismissed = localStorage.getItem('pwa-android-dismissed') === 'true';
|
||||
const iosDismissed = localStorage.getItem('pwa-ios-dismissed') === 'true';
|
||||
@@ -411,6 +463,8 @@ export class HomePage implements OnDestroy {
|
||||
this.showOnlyMine.set(false);
|
||||
this.showTopTen.set(false);
|
||||
this.showSuggeriti.set(false);
|
||||
this.showValidati.set(false);
|
||||
this.showNonValidati.set(false);
|
||||
this.playlistService.activeListIds.set([]);
|
||||
this.playlistService.activeListName.set(null);
|
||||
this.playlistService.activePlaylistId.set(null);
|
||||
@@ -432,6 +486,12 @@ export class HomePage implements OnDestroy {
|
||||
if (params['import']) {
|
||||
this.handleImport(params['import']);
|
||||
}
|
||||
if (params['playlist-uid']) {
|
||||
this.handleRemotePlaylistImport(params['playlist-uid'], params['playlist-id']);
|
||||
}
|
||||
if (params['restore-uid']) {
|
||||
this.handleRemoteRestore(params['restore-uid']);
|
||||
}
|
||||
});
|
||||
|
||||
let prevSelectionMode = false;
|
||||
@@ -443,7 +503,7 @@ export class HomePage implements OnDestroy {
|
||||
|
||||
if (selectionMode) {
|
||||
if (!prevSelectionMode) {
|
||||
if (activeIds.length === 0) {
|
||||
if (activeIds.length === 0 || this.playlistService.activePlaylistId() === null) {
|
||||
this.isAddingSongs.set(true);
|
||||
} else {
|
||||
this.isAddingSongs.set(false);
|
||||
@@ -476,7 +536,12 @@ export class HomePage implements OnDestroy {
|
||||
updatedList = [...updatedList, ...newSongs];
|
||||
}
|
||||
|
||||
this.reorderList.set(updatedList);
|
||||
// Previeni cicli infiniti se il contenuto della lista non è effettivamente cambiato
|
||||
const currentIdsStr = currentIds.join(',');
|
||||
const updatedIdsStr = updatedList.map(c => c.id).join(',');
|
||||
if (currentIdsStr !== updatedIdsStr) {
|
||||
this.reorderList.set(updatedList);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
prevSelectionMode = false;
|
||||
@@ -580,11 +645,200 @@ export class HomePage implements OnDestroy {
|
||||
}
|
||||
}
|
||||
|
||||
async handleRemotePlaylistImport(uid: string, pid?: string) {
|
||||
const loading = await this.loadingCtrl.create({
|
||||
message: 'Scaricamento playlist da remoto...'
|
||||
});
|
||||
await loading.present();
|
||||
|
||||
try {
|
||||
const response = await fetch(`https://api.canticristiani.it/${uid}.json?cb=${Date.now()}`, { cache: 'no-store' });
|
||||
if (!response.ok) {
|
||||
throw new Error(`Risposta del server non valida: ${response.status}`);
|
||||
}
|
||||
const remoteJson = await response.json();
|
||||
if (Array.isArray(remoteJson)) {
|
||||
// 1. Reconstruct custom songs
|
||||
const customSongs = remoteJson
|
||||
.filter((item: any) => item.momenti && !item.momenti.includes('Playlist'))
|
||||
.map((item: any) => ({
|
||||
id: `my_${item.id_canti}`,
|
||||
id_canti: Number(item.id_canti),
|
||||
titolo: item.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)) || []
|
||||
}));
|
||||
|
||||
// 2. 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: `remote_${uid}_${item.id_canti}`,
|
||||
name: `[Remote] ${item.titolo}`,
|
||||
ids: item.testo ? item.testo.split(',') : [],
|
||||
songSettings: songSettings,
|
||||
createdAt: new Date(),
|
||||
isRemote: true
|
||||
};
|
||||
});
|
||||
|
||||
if (playlists.length === 0) {
|
||||
throw new Error('Nessuna playlist trovata in questa identità.');
|
||||
}
|
||||
|
||||
// Find the selected one
|
||||
let selectedPl = playlists[0];
|
||||
if (pid) {
|
||||
const targetId = `remote_${uid}_${pid}`;
|
||||
const found = playlists.find(p => p.id === targetId || p.id.endsWith(`_${pid}`));
|
||||
if (found) {
|
||||
selectedPl = found;
|
||||
}
|
||||
}
|
||||
|
||||
await this.playlistService.saveRemotePlaylist(selectedPl, customSongs);
|
||||
|
||||
// Clear other filters to avoid confusion
|
||||
this.selectedLiturgico.set(null);
|
||||
this.selectedTematico.set(null);
|
||||
|
||||
// Select it immediately
|
||||
this.selectPlaylist(selectedPl);
|
||||
|
||||
await loading.dismiss();
|
||||
|
||||
const toast = await this.toastCtrl.create({
|
||||
message: `Playlist "${selectedPl.name}" caricata per consultazione!`,
|
||||
duration: 3000,
|
||||
color: 'success'
|
||||
});
|
||||
await toast.present();
|
||||
} else {
|
||||
throw new Error('Formato dati non valido.');
|
||||
}
|
||||
} catch (err: any) {
|
||||
await loading.dismiss();
|
||||
console.error('Failed to import remote playlist:', err);
|
||||
const alert = await this.alertCtrl.create({
|
||||
header: 'Errore Importazione',
|
||||
message: 'Impossibile scaricare la playlist da remoto. Controlla la connessione o il codice.',
|
||||
buttons: ['OK']
|
||||
});
|
||||
await alert.present();
|
||||
} finally {
|
||||
this.router.navigate([], { queryParams: { 'playlist-uid': null, 'playlist-id': null }, queryParamsHandling: 'merge' });
|
||||
}
|
||||
}
|
||||
|
||||
async handleRemoteRestore(uid: string) {
|
||||
const alert = await this.alertCtrl.create({
|
||||
header: 'Ripristina Identità',
|
||||
message: 'Sei sicuro di voler scaricare e ripristinare i dati di questa identità? L\'ID attuale del dispositivo verrà sovrascritto.',
|
||||
buttons: [
|
||||
{ text: 'Annulla', role: 'cancel' },
|
||||
{
|
||||
text: 'Ripristina',
|
||||
role: 'destructive',
|
||||
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/${uid}.json?cb=${Date.now()}`, { cache: 'no-store' });
|
||||
if (response.ok) {
|
||||
const remoteJson = await response.json();
|
||||
if (Array.isArray(remoteJson)) {
|
||||
// Reconstruct custom songs
|
||||
const customSongs = remoteJson
|
||||
.filter((item: any) => !item.momenti || !item.momenti.includes('Playlist'))
|
||||
.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)) || []
|
||||
}));
|
||||
|
||||
if (customSongs.length > 0) {
|
||||
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()
|
||||
};
|
||||
});
|
||||
|
||||
if (playlists.length > 0) {
|
||||
this.playlistService.playlists.set(playlists);
|
||||
if (this.playlistService['_storage']) {
|
||||
const key = this.playlistService.getPlaylistsStorageKey();
|
||||
await this.playlistService['_storage'].set(key, playlists);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
} catch (err) {
|
||||
console.error('Failed to restore remote backup:', err);
|
||||
} finally {
|
||||
await loading.dismiss();
|
||||
}
|
||||
|
||||
this.settingsService.setUserUuid(uid);
|
||||
|
||||
const toast = await this.toastCtrl.create({
|
||||
message: 'Dati ripristinati con successo! Ricaricamento...',
|
||||
duration: 2000,
|
||||
color: 'success'
|
||||
});
|
||||
await toast.present();
|
||||
|
||||
setTimeout(() => {
|
||||
window.location.replace(window.location.origin + window.location.pathname);
|
||||
}, 1500);
|
||||
}
|
||||
}
|
||||
]
|
||||
});
|
||||
await alert.present();
|
||||
}
|
||||
|
||||
ionViewWillLeave() {
|
||||
}
|
||||
|
||||
ngOnDestroy() {
|
||||
this.audioEngine.stopSearchRecognition();
|
||||
if (this.updatePollInterval) {
|
||||
clearInterval(this.updatePollInterval);
|
||||
}
|
||||
}
|
||||
|
||||
onSearch(event: any) {
|
||||
@@ -619,8 +873,9 @@ export class HomePage implements OnDestroy {
|
||||
return [
|
||||
...this.cantiService.canti(),
|
||||
...this.myCantiService.myCanti(),
|
||||
...this.playlistService.remoteCustomSongs(),
|
||||
...this.comunitaService.comunitaCantiPersonali()
|
||||
].find(c => c.id === id);
|
||||
].find(c => c.id === id || String(c.id_canti) === id);
|
||||
}
|
||||
|
||||
getPlayingCanto() {
|
||||
@@ -643,6 +898,7 @@ export class HomePage implements OnDestroy {
|
||||
|
||||
toggleOnlyMine() {
|
||||
this.showOnlyMine.update(v => !v);
|
||||
this.activeFilterType.set(null);
|
||||
this.limit.set(10);
|
||||
}
|
||||
|
||||
@@ -652,13 +908,24 @@ export class HomePage implements OnDestroy {
|
||||
this.limit.set(10);
|
||||
}
|
||||
|
||||
toggleIndex(id: number, type: 'liturgico' | 'tematico' | 'playlist') {
|
||||
toggleIndex(id: number, type: 'liturgico' | 'tematico' | 'playlist' | 'lista_completa') {
|
||||
if (type === 'liturgico') {
|
||||
this.selectedLiturgico.update(cur => cur === id ? null : id);
|
||||
} else if (type === 'tematico') {
|
||||
this.selectedTematico.update(cur => cur === id ? null : id);
|
||||
}
|
||||
this.activeFilterType.set(null);
|
||||
|
||||
// Mantieni aperto il menu di secondo livello se c'è una selezione attiva per quel tipo
|
||||
let hasSelection = false;
|
||||
if (type === 'liturgico' && this.selectedLiturgico() !== null) {
|
||||
hasSelection = true;
|
||||
} else if (type === 'tematico' && this.selectedTematico() !== null) {
|
||||
hasSelection = true;
|
||||
}
|
||||
|
||||
if (!hasSelection) {
|
||||
this.activeFilterType.set(null);
|
||||
}
|
||||
this.limit.set(10);
|
||||
}
|
||||
|
||||
@@ -734,6 +1001,8 @@ export class HomePage implements OnDestroy {
|
||||
this.showOnlyMine() ||
|
||||
this.showTopTen() ||
|
||||
this.showSuggeriti() ||
|
||||
this.showValidati() ||
|
||||
this.showNonValidati() ||
|
||||
this.playlistService.activeListName() !== null ||
|
||||
this.searchQuery() !== '';
|
||||
}
|
||||
@@ -744,6 +1013,8 @@ export class HomePage implements OnDestroy {
|
||||
this.showOnlyMine.set(false);
|
||||
this.showTopTen.set(false);
|
||||
this.showSuggeriti.set(false);
|
||||
this.showValidati.set(false);
|
||||
this.showNonValidati.set(false);
|
||||
this.playlistService.activeListIds.set([]);
|
||||
this.playlistService.activeListName.set(null);
|
||||
this.playlistService.activePlaylistId.set(null);
|
||||
@@ -752,9 +1023,23 @@ export class HomePage implements OnDestroy {
|
||||
this.limit.set(10);
|
||||
}
|
||||
|
||||
toggleFilterType(type: 'liturgico' | 'tematico' | 'playlist') {
|
||||
toggleFilterType(type: 'liturgico' | 'tematico' | 'playlist' | 'lista_completa') {
|
||||
if (this.activeFilterType() === type) {
|
||||
this.activeFilterType.set(null);
|
||||
// Chiudi solo se non ci sono selezioni di secondo livello attive per questo tipo
|
||||
let hasSelection = false;
|
||||
if (type === 'liturgico' && this.selectedLiturgico() !== null) {
|
||||
hasSelection = true;
|
||||
} else if (type === 'tematico' && this.selectedTematico() !== null) {
|
||||
hasSelection = true;
|
||||
} else if (type === 'playlist' && this.playlistService.activePlaylistId() !== null) {
|
||||
hasSelection = true;
|
||||
} else if (type === 'lista_completa' && (this.showValidati() || this.showNonValidati())) {
|
||||
hasSelection = true;
|
||||
}
|
||||
|
||||
if (!hasSelection) {
|
||||
this.activeFilterType.set(null);
|
||||
}
|
||||
} else {
|
||||
this.activeFilterType.set(type);
|
||||
}
|
||||
@@ -764,12 +1049,13 @@ export class HomePage implements OnDestroy {
|
||||
this.playlistService.activeListIds.set(pl.ids);
|
||||
this.playlistService.activeListName.set(pl.name);
|
||||
this.playlistService.activePlaylistId.set(pl.id);
|
||||
this.activeFilterType.set(null);
|
||||
// Mantieni il menu aperto poiché la playlist è ora selezionata ed attiva
|
||||
this.limit.set(50);
|
||||
}
|
||||
|
||||
toggleTopTen() {
|
||||
this.showTopTen.update(v => !v);
|
||||
this.activeFilterType.set(null);
|
||||
this.limit.set(30);
|
||||
}
|
||||
|
||||
@@ -779,8 +1065,53 @@ export class HomePage implements OnDestroy {
|
||||
this.limit.set(30);
|
||||
}
|
||||
|
||||
toggleValidati() {
|
||||
this.showValidati.update(v => !v);
|
||||
this.showNonValidati.set(false);
|
||||
if (!this.showValidati()) {
|
||||
this.activeFilterType.set(null);
|
||||
} else {
|
||||
this.activeFilterType.set('lista_completa');
|
||||
}
|
||||
this.limit.set(30);
|
||||
}
|
||||
|
||||
clearValidati(event?: Event) {
|
||||
if (event) event.stopPropagation();
|
||||
this.showValidati.set(false);
|
||||
this.activeFilterType.set(null);
|
||||
this.limit.set(30);
|
||||
}
|
||||
|
||||
toggleNonValidati() {
|
||||
this.showNonValidati.update(v => !v);
|
||||
this.showValidati.set(false);
|
||||
if (!this.showNonValidati()) {
|
||||
this.activeFilterType.set(null);
|
||||
} else {
|
||||
this.activeFilterType.set('lista_completa');
|
||||
}
|
||||
this.limit.set(30);
|
||||
}
|
||||
|
||||
clearNonValidati(event?: Event) {
|
||||
if (event) event.stopPropagation();
|
||||
this.showNonValidati.set(false);
|
||||
this.activeFilterType.set(null);
|
||||
this.limit.set(30);
|
||||
}
|
||||
|
||||
clearListaCompleta(event?: Event) {
|
||||
if (event) event.stopPropagation();
|
||||
this.showValidati.set(false);
|
||||
this.showNonValidati.set(false);
|
||||
this.activeFilterType.set(null);
|
||||
this.limit.set(10);
|
||||
}
|
||||
|
||||
toggleSuggeriti() {
|
||||
this.showSuggeriti.update(v => !v);
|
||||
this.activeFilterType.set(null);
|
||||
this.limit.set(30);
|
||||
}
|
||||
|
||||
@@ -996,6 +1327,42 @@ export class HomePage implements OnDestroy {
|
||||
handleScannedData(data: string) {
|
||||
if (!data) return;
|
||||
|
||||
if (data.includes('playlist-uid=')) {
|
||||
try {
|
||||
const urlObj = new URL(data);
|
||||
const uid = urlObj.searchParams.get('playlist-uid');
|
||||
const pid = urlObj.searchParams.get('playlist-id');
|
||||
if (uid) {
|
||||
this.handleRemotePlaylistImport(uid, pid || undefined);
|
||||
return;
|
||||
}
|
||||
} catch (e) {
|
||||
const uidMatch = data.match(/[?&]playlist-uid=([^&]+)/);
|
||||
const pidMatch = data.match(/[?&]playlist-id=([^&]+)/);
|
||||
if (uidMatch && uidMatch[1]) {
|
||||
this.handleRemotePlaylistImport(uidMatch[1], pidMatch ? pidMatch[1] : undefined);
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (data.includes('restore-uid=')) {
|
||||
try {
|
||||
const urlObj = new URL(data);
|
||||
const uid = urlObj.searchParams.get('restore-uid');
|
||||
if (uid) {
|
||||
this.handleRemoteRestore(uid);
|
||||
return;
|
||||
}
|
||||
} catch (e) {
|
||||
const uidMatch = data.match(/[?&]restore-uid=([^&]+)/);
|
||||
if (uidMatch && uidMatch[1]) {
|
||||
this.handleRemoteRestore(uidMatch[1]);
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (data.includes('import=')) {
|
||||
try {
|
||||
let base64 = '';
|
||||
@@ -1115,6 +1482,11 @@ export class HomePage implements OnDestroy {
|
||||
return !!id && id.startsWith('comunita_');
|
||||
}
|
||||
|
||||
isRemotePlaylist(): boolean {
|
||||
const id = this.playlistService.activePlaylistId();
|
||||
return !!id && id.startsWith('remote_');
|
||||
}
|
||||
|
||||
isActivePlaylistSaved(): boolean {
|
||||
const id = this.playlistService.activePlaylistId();
|
||||
if (!id) return false;
|
||||
@@ -1164,16 +1536,22 @@ export class HomePage implements OnDestroy {
|
||||
const name = this.playlistService.activeListName();
|
||||
if (!id || !name) return;
|
||||
|
||||
const isRemote = this.isRemotePlaylist();
|
||||
|
||||
const alert = await this.alertCtrl.create({
|
||||
header: 'Elimina Playlist',
|
||||
message: `Vuoi davvero eliminare "${name}"?`,
|
||||
header: isRemote ? 'Rimuovi Playlist' : 'Elimina Playlist',
|
||||
message: isRemote ? `Vuoi davvero rimuovere la playlist "${name}"?` : `Vuoi davvero eliminare "${name}"?`,
|
||||
buttons: [
|
||||
{ text: 'Annulla', role: 'cancel' },
|
||||
{
|
||||
text: 'Elimina',
|
||||
text: isRemote ? 'Rimuovi' : 'Elimina',
|
||||
role: 'destructive',
|
||||
handler: () => {
|
||||
this.playlistService.deletePlaylist(id);
|
||||
if (isRemote) {
|
||||
this.playlistService.clearRemotePlaylist();
|
||||
} else {
|
||||
this.playlistService.deletePlaylist(id);
|
||||
}
|
||||
this.clearSpecialList();
|
||||
}
|
||||
}
|
||||
@@ -1203,6 +1581,31 @@ export class HomePage implements OnDestroy {
|
||||
this.playlistService.activeListName.set(name);
|
||||
}
|
||||
|
||||
toggleSongSelection(id: string) {
|
||||
if (!this.playlistService.selectionMode()) {
|
||||
const activeIds = this.playlistService.activeListIds();
|
||||
const activeName = this.playlistService.activeListName();
|
||||
|
||||
if (activeIds.length > 0) {
|
||||
// Se selezioniamo i numerini di una playlist attiva, non andiamo in modifica di quella playlist
|
||||
// ma avviamo la selezione partendo da questa lista per creare una nuova playlist.
|
||||
this.playlistService.activePlaylistId.set(null);
|
||||
|
||||
const clickedCanto = this.findCanto(id);
|
||||
if (clickedCanto) {
|
||||
this.playlistService.selectedIds.set(new Set([clickedCanto.id]));
|
||||
this.reorderList.set([clickedCanto]);
|
||||
}
|
||||
|
||||
this.playlistService.selectionMode.set(true);
|
||||
this.isAddingSongs.set(true); // Rimaniamo sulla lista filtrata per selezionare altri canti
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
this.playlistService.toggleSongSelection(id);
|
||||
}
|
||||
|
||||
drop(event: CdkDragDrop<any[]>) {
|
||||
const arr = [...this.reorderList()];
|
||||
moveItemInArray(arr, event.previousIndex, event.currentIndex);
|
||||
@@ -1233,6 +1636,7 @@ export class HomePage implements OnDestroy {
|
||||
} else {
|
||||
this.comunitaService.isFilterActive.update(v => !v);
|
||||
}
|
||||
this.activeFilterType.set(null);
|
||||
}
|
||||
|
||||
editComunitaCode(event: Event) {
|
||||
|
||||
Reference in New Issue
Block a user