gestione comunità

This commit is contained in:
David Frassi
2026-05-18 16:07:37 +02:00
parent 961b7ba65c
commit af12a1f2da
15 changed files with 1021 additions and 61 deletions
+229 -9
View File
@@ -9,11 +9,12 @@ import { PlaylistService } from '../services/playlist.service';
import { SettingsService } from '../services/settings.service';
import { VERSION } from '../version';
import { YoutubePlayerService } from '../services/youtube-player.service';
import { ModalController, AlertController, ToastController } from '@ionic/angular';
import { ModalController, AlertController, ToastController, LoadingController } from '@ionic/angular';
import { CdkDragDrop, moveItemInArray } from '@angular/cdk/drag-drop';
import { MyCantiService } from '../services/my-canti.service';
import { QrScannerComponent } from '../components/qr-scanner/qr-scanner.component';
import { CantiLettureService } from '../services/canti-letture.service';
import { ComunitaService } from '../services/comunita.service';
@Component({
selector: 'app-home',
@@ -38,6 +39,7 @@ export class HomePage implements OnDestroy {
public fontSize = signal<number>(1.0);
public youtubePlayerService = inject(YoutubePlayerService);
public comunitaService = inject(ComunitaService);
public isSeeking = false;
private readonly MIN_FONT = 0.6;
@@ -59,6 +61,7 @@ export class HomePage implements OnDestroy {
private modalCtrl = inject(ModalController);
private alertCtrl = inject(AlertController);
private toastCtrl = inject(ToastController);
private loadingCtrl = inject(LoadingController);
private firstInteraction = true;
@@ -70,7 +73,14 @@ export class HomePage implements OnDestroy {
private scrollEffect = effect(() => {
const activeId = this.youtubePlayerService.currentCantoId();
const list = this.filteredCanti();
if (activeId) {
const index = list.findIndex(c => c.id === activeId);
if (index !== -1 && index >= this.limit()) {
this.limit.set(index + 15);
}
// Delay slightly to ensure DOM is updated and classes are applied
setTimeout(() => {
const element = document.getElementById('canto-' + activeId);
@@ -79,7 +89,7 @@ export class HomePage implements OnDestroy {
}
}, 500);
}
});
}, { allowSignalWrites: true });
private normalize(str: string | undefined): string {
if (!str) return '';
@@ -103,6 +113,25 @@ export class HomePage implements OnDestroy {
let list = [...this.cantiService.canti(), ...this.myCantiService.myCanti()];
// Filter by Community if code is set and filter is active
const comunitaCode = this.comunitaService.comunitaCode();
const comunitaIds = this.comunitaService.comunitaCantiIds();
if (comunitaCode && this.comunitaService.isFilterActive()) {
list = list.filter(c => {
return comunitaIds.includes(c.id_canti) || comunitaIds.includes(c.id);
});
// Sort list by community song number (num_canto) ASC!
const cantiInfo = this.comunitaService.comunitaCantiInfo();
list.sort((a, b) => {
const infoA = cantiInfo.find(x => x.id_canti === a.id_canti || x.id_canti === Number(a.id));
const infoB = cantiInfo.find(x => x.id_canti === b.id_canti || x.id_canti === Number(b.id));
const numA = infoA ? Number(infoA.num_canto) : 999999;
const numB = infoB ? Number(infoB.num_canto) : 999999;
return numA - numB;
});
}
if (onlyMine) {
list = this.myCantiService.myCanti();
}
@@ -160,17 +189,21 @@ export class HomePage implements OnDestroy {
// Converte "uno" in "1" per facilitare la ricerca vocale (es. "numero uno")
const processedQuery = query.replace(/\buno\b/g, '1');
// Se la ricerca inizia con "numero" o "nr", filtra esattamente per id_canti
// Se la ricerca inizia con "numero" o "nr", filtra esattamente per id_canti/comunita number
const numberMatchPattern = processedQuery.match(/^(?:numero|nr\.?)\s*(\d+)$/);
if (numberMatchPattern) {
const targetId = numberMatchPattern[1];
return list.filter(c => c.id_canti.toString() === targetId);
return list.filter(c => {
const commNum = this.getCommunitySongNumber(c);
return commNum ? commNum === targetId : c.id_canti.toString() === targetId;
});
}
return list.filter(c => {
const titleMatch = this.normalize(c.titolo).includes(query);
const authorMatch = this.normalize(c.autore).includes(query);
const numberMatch = c.id_canti.toString().includes(query);
const commNum = this.getCommunitySongNumber(c);
const numberMatch = commNum ? commNum.includes(query) : c.id_canti.toString().includes(query);
// Strip tags like {Chorus} and newlines from lyrics before searching
const lyricsPlain = (c.testo || '')
@@ -642,10 +675,6 @@ export class HomePage implements OnDestroy {
setTimeout(() => {
this.limit.update(l => l + 10);
event.target.complete();
if (this.limit() >= this.filteredCanti().length) {
event.target.disabled = true;
}
}, 500);
}
@@ -883,4 +912,195 @@ export class HomePage implements OnDestroy {
this.limit.set(10);
}
getCommunitySongNumber(canto: any): string | null {
if (!this.comunitaService.comunitaCode() || !this.comunitaService.isFilterActive()) return null;
const cantiInfo = this.comunitaService.comunitaCantiInfo();
const info = cantiInfo.find(x => x.id_canti === canto.id_canti || x.id_canti === Number(canto.id));
return info && info.num_canto ? info.num_canto.toString() : null;
}
toggleComunitaFilter() {
if (!this.comunitaService.comunitaCode()) {
this.promptComunitaCode();
} else {
this.comunitaService.isFilterActive.update(v => !v);
}
}
editComunitaCode(event: Event) {
event.stopPropagation();
this.promptComunitaCode();
}
async promptComunitaCode() {
const alert = await this.alertCtrl.create({
header: 'Imposta Comunità',
subHeader: 'Inserisci il codice parrocchiale/comunità per attivare il libretto dedicato:',
cssClass: 'premium-alert',
inputs: [
{
name: 'code',
type: 'text',
placeholder: 'Es: 123456',
value: this.comunitaService.comunitaCode()
}
],
buttons: [
{
text: 'Annulla',
role: 'cancel'
},
{
text: 'Rimuovi',
role: 'destructive',
cssClass: 'alert-button-delete',
handler: async () => {
await this.comunitaService.setComunitaCode('');
const toast = await this.toastCtrl.create({
message: 'Comunità disattivata.',
duration: 2000,
color: 'secondary'
});
await toast.present();
}
},
{
text: 'Salva',
handler: async (data) => {
const trimmed = (data.code || '').trim();
if (!trimmed) {
await this.comunitaService.setComunitaCode('');
return;
}
// Show loading overlay with progress
const loading = await this.loadingCtrl.create({
message: 'Caricamento 0%',
cssClass: 'premium-loading',
spinner: 'crescent'
});
await loading.present();
// Subscribe to progress updates
let progressInterval: any = null;
progressInterval = setInterval(() => {
const pct = this.comunitaService.loadingProgress();
loading.message = `Caricamento ${pct}%`;
if (pct >= 100) {
clearInterval(progressInterval);
}
}, 100);
const success = await this.comunitaService.setComunitaCode(trimmed);
clearInterval(progressInterval);
await loading.dismiss();
if (success) {
const toast = await this.toastCtrl.create({
message: `Comunità attivata: ${this.comunitaService.comunitaNome()}`,
duration: 2000,
color: 'success'
});
await toast.present();
} else {
const toast = await this.toastCtrl.create({
message: 'Codice non trovato o errore di connessione.',
duration: 2000,
color: 'danger'
});
await toast.present();
setTimeout(() => this.promptComunitaCode(), 500);
}
}
}
]
});
await alert.present();
}
private massCardStartX: number = 0;
private massCardStartY: number = 0;
onMassCardTouchStart(event: TouchEvent) {
if (event.touches.length === 1) {
this.massCardStartX = event.touches[0].clientX;
this.massCardStartY = event.touches[0].clientY;
}
}
onMassCardTouchEnd(event: TouchEvent) {
if (event.changedTouches.length === 1) {
const endX = event.changedTouches[0].clientX;
const endY = event.changedTouches[0].clientY;
const diffX = endX - this.massCardStartX;
const diffY = endY - this.massCardStartY;
// Ensure it is a horizontal swipe (diffX is much larger than diffY)
if (Math.abs(diffX) > 60 && Math.abs(diffY) < 40) {
// Prevent triggering click expansion
event.stopPropagation();
event.preventDefault();
if (diffX > 0) {
// Swiped right -> go to PREVIOUS date
this.navigateMassDate(-1);
} else {
// Swiped left -> go to NEXT date
this.navigateMassDate(1);
}
}
}
}
async navigateMassDate(direction: number) {
const list = this.cantiLettureService.availableMasses();
if (list.length === 0) return;
const currentDate = this.cantiLettureService.selectedMassDate();
if (!currentDate) {
this.cantiLettureService.selectedMassDate.set(list[0].date);
return;
}
const currentIndex = list.findIndex(m => m.date === currentDate);
if (currentIndex === -1) {
this.cantiLettureService.selectedMassDate.set(list[0].date);
return;
}
const nextIndex = currentIndex + direction;
if (nextIndex >= 0 && nextIndex < list.length) {
// Set the next/prev date
this.cantiLettureService.selectedMassDate.set(list[nextIndex].date);
const toast = await this.toastCtrl.create({
message: `Messa: ${list[nextIndex].day} ${this.formatCompactDate(list[nextIndex].date)}`,
duration: 1500,
color: 'secondary',
position: 'bottom'
});
await toast.present();
} else {
// "quando le messe sono finite rimani sull'ultima valorizzata, non dare errore"
const targetIndex = direction > 0 ? list.length - 1 : 0;
this.cantiLettureService.selectedMassDate.set(list[targetIndex].date);
const toast = await this.toastCtrl.create({
message: direction > 0 ? 'Nessun\'altra messa futura disponibile.' : 'Nessun\'altra messa passata disponibile.',
duration: 1500,
color: 'medium',
position: 'bottom'
});
await toast.present();
}
}
private formatCompactDate(dateStr: string): string {
const parts = dateStr.split('-');
if (parts.length === 3) {
return `${parts[2]}/${parts[1]}`;
}
return dateStr;
}
}