e18c1c120b
- 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.
195 lines
5.6 KiB
TypeScript
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() {
|
|
}
|
|
}
|