Files
canti/src/app/services/playlist.service.ts
T
David Frassi e18c1c120b feat: integrazione scalette comunità come playlist
- Aggiunto il parsing di lista_nome e lista_esecuzione dall'API parrocchiale in ComunitaService.
- Le scalette vengono trasformate in ComunitaScaletta[] e persistite in localStorage.
- PlaylistService espone comunitaPlaylists (computed) e allPlaylists (computed che unisce playlist personali e scalette comunità).
- Le scalette della comunità appaiono nel dropdown Playlist SOLO quando il filtro comunità è attivo; scompaiono automaticamente alla disattivazione.
- Le scalette sono read-only: i pulsanti Modifica e Elimina vengono nascosti per le playlist della comunità.
- Icona people-outline e bordo sinistro secondario distinguono visivamente le scalette della comunità dalle playlist personali.
2026-05-20 16:17:34 +02:00

195 lines
5.6 KiB
TypeScript

import { Injectable, signal, inject, computed } from '@angular/core';
import { Storage } from '@ionic/storage-angular';
import { Canto, CantiService } from './canti.service';
import { ComunitaService } from './comunita.service';
import * as QRCode from 'qrcode';
@Injectable({
providedIn: 'root'
})
export class PlaylistService {
private storage = inject(Storage);
private cantiService = inject(CantiService);
private comunitaService = inject(ComunitaService);
public selectionMode = signal<boolean>(false);
public selectedIds = signal<Set<string>>(new Set());
public playlists = signal<any[]>([]);
public lastPlaylist = signal<any | null>(null);
public autoPlayPlaylist = signal<boolean>(false);
public activeListIds = signal<string[]>([]);
public activeListName = signal<string | null>(null);
public activePlaylistId = signal<string | null>(null);
private _storage: Storage | null = null;
// Community scalette exposed as playlists (only when community filter is active)
public comunitaPlaylists = computed(() => {
if (!this.comunitaService.comunitaCode() || !this.comunitaService.isFilterActive()) {
return [];
}
return this.comunitaService.comunitaScalette().map(s => ({
id: s.id,
name: s.name,
ids: s.ids,
createdAt: s.date,
isComunita: true
}));
});
// Merged list: personal playlists + community scalette
public allPlaylists = computed(() => {
const community = this.comunitaPlaylists();
const personal = this.playlists();
if (community.length > 0) {
return [...community, ...personal.map(p => ({ ...p, isComunita: false }))];
}
return personal.map(p => ({ ...p, isComunita: false }));
});
constructor() {
this.init();
}
async init() {
const storage = await this.storage.create();
this._storage = storage;
const saved = await this._storage.get('playlists');
if (saved) {
this.playlists.set(saved);
}
const last = await this._storage?.get('lastPlaylist');
if (last) {
this.lastPlaylist.set(last);
}
}
toggleSelectionMode() {
this.selectionMode.update(v => !v);
if (!this.selectionMode()) {
this.selectedIds.set(new Set());
}
}
toggleSongSelection(id: string) {
if (!this.selectionMode()) {
const active = this.activeListIds();
if (active.length > 0) {
this.selectedIds.set(new Set(active));
}
this.selectionMode.set(true);
}
this.selectedIds.update(set => {
const newSet = new Set(set);
if (newSet.has(id)) {
newSet.delete(id);
} else {
newSet.add(id);
}
return newSet;
});
}
async savePlaylist(name: string, ids: string[]) {
const editId = this.activePlaylistId();
let newPlaylist: any;
if (editId) {
this.playlists.update(p => p.map(pl => {
if (pl.id === editId) {
return { ...pl, name, ids };
}
return pl;
}));
newPlaylist = this.playlists().find(pl => pl.id === editId);
} else {
newPlaylist = {
id: Date.now().toString(),
name,
ids,
createdAt: new Date()
};
this.playlists.update(p => [newPlaylist, ...p]);
}
this.lastPlaylist.set(newPlaylist);
this.activeListIds.set(ids);
this.activeListName.set(name);
await this._storage?.set('playlists', this.playlists());
await this._storage?.set('lastPlaylist', newPlaylist);
// Reset selection mode, IDs and editing state after saving
this.selectedIds.set(new Set());
this.selectionMode.set(false);
this.activePlaylistId.set(newPlaylist.id);
}
async deletePlaylist(id: string) {
this.playlists.update(p => p.filter(pl => pl.id !== id));
await this._storage?.set('playlists', this.playlists());
}
async generateQR(ids: string[], name: string): Promise<string> {
const data = this.getShareLink(ids, name);
return await QRCode.toDataURL(data, {
width: 400,
margin: 2,
color: {
dark: '#2d3436',
light: '#ffffff'
}
});
}
getShareLink(ids: string[], name: string): string {
const data = JSON.stringify({ name, ids });
// Use btoa safely for UTF-8 strings
const base64 = btoa(unescape(encodeURIComponent(data)));
// Always use the production URL for sharing links as requested
const productionUrl = 'https://www.canticristiani.it/ionic';
return `${productionUrl}/?import=${base64}`;
}
processImportJson(json: any): boolean {
if (json && json.name && json.ids) {
this.activeListIds.set(json.ids);
this.activeListName.set(json.name);
return true;
}
return false;
}
async sharePlaylistQR(ids: string[], name: string) {
const qrImage = await this.generateQR(ids, name);
const shareLink = this.getShareLink(ids, name);
const fileName = `${name.toLowerCase().replace(/\s+/g, '_')}_qr.png`;
try {
const res = await fetch(qrImage);
const blob = await res.blob();
const file = new File([blob], fileName, { type: 'image/png' });
if (navigator.share && navigator.canShare && navigator.canShare({ files: [file] })) {
await navigator.share({
files: [file],
title: 'Playlist CantiCristiani',
text: `Ecco la playlist: ${name}\n\nClicca qui per aprirla subito: ${shareLink}`
});
} else {
// Fallback: download
const link = document.createElement('a');
link.href = qrImage;
link.download = fileName;
link.click();
}
} catch (err) {
console.error('Share failed', err);
}
}
async loadPlaylists() {
}
}