Ottimizzazione UI player, rotazione head tilt invertita, prefisso playlist in Home e implementazione Identity QR modal e interceptor API

This commit is contained in:
David Frassi
2026-06-12 17:13:46 +02:00
parent 4d1883a45d
commit d023bb7d3f
31 changed files with 1706 additions and 160 deletions
+12 -12
View File
@@ -156,9 +156,16 @@
</div>
<!-- Camera Head Gestures Toggle in Portrait -->
<ion-button fill="clear" size="small" (click)="toggleCameraNavigation()" [color]="enableCameraNavigation() ? 'success' : 'secondary'" *ngIf="settingsService.enableVisualAutoscroll()">
<ion-icon slot="icon-only" [name]="enableCameraNavigation() ? 'videocam' : 'videocam-off-outline'"></ion-icon>
</ion-button>
<div class="group" *ngIf="settingsService.enableVisualAutoscroll()">
<ion-button fill="clear" size="small" (click)="toggleCameraNavigation()" [color]="enableCameraNavigation() ? 'success' : 'secondary'" style="margin: 0;">
<ion-icon slot="icon-only" [name]="enableCameraNavigation() ? 'videocam' : 'videocam-off-outline'"></ion-icon>
</ion-button>
<div class="camera-indicator" *ngIf="enableCameraNavigation()" [class.tilted]="faceDetector.isTilted()">
<ion-icon name="person-outline"
[style.transform]="'rotate(' + (-faceDetector.currentTiltAngle()) + 'deg)'"></ion-icon>
<span class="val">{{ faceDetector.currentTiltAngle() }}°</span>
</div>
</div>
<!-- Transposition (Only in chords mode) -->
@@ -208,12 +215,5 @@
</ion-toolbar>
</ion-footer>
<!-- Floating Camera Preview for Head Navigation -->
<div class="camera-preview-floating glass" *ngIf="enableCameraNavigation()">
<video id="face-preview-video" muted playsinline autoplay></video>
<div class="camera-preview-overlay">
<div class="angle-indicator" [class.tilted]="faceDetector.isTilted()">
{{ faceDetector.currentTiltAngle() }}°
</div>
</div>
</div>
<!-- Hidden Camera Video for Head Navigation (needed for face detection API to run) -->
<video *ngIf="enableCameraNavigation()" id="face-preview-video" muted playsinline autoplay style="position: absolute; width: 1px; height: 1px; opacity: 0; pointer-events: none;"></video>
+37 -17
View File
@@ -257,6 +257,28 @@
ion-icon { font-size: 0.9rem; }
}
.camera-indicator {
display: flex;
align-items: center;
gap: 4px;
font-size: 0.8rem;
font-weight: 700;
color: var(--ion-color-secondary);
padding: 0 4px;
transition: color 0.15s ease-out;
ion-icon {
font-size: 1.1rem;
transition: transform 0.15s ease-out;
display: inline-block;
}
&.tilted {
color: var(--ion-color-success, #2ed573);
text-shadow: 0 0 5px rgba(46, 213, 115, 0.6);
}
}
}
// Transcript area
@@ -741,16 +763,17 @@ ion-content.full-screen-content {
position: fixed;
bottom: 60px;
right: 15px;
width: 90px;
height: 120px;
border-radius: 12px;
overflow: hidden;
padding: 8px 14px;
border-radius: 20px;
border: 1px solid rgba(var(--ion-color-secondary-rgb), 0.4);
box-shadow: 0 4px 20px rgba(0, 0, 0, 0.5);
z-index: 1000;
display: flex;
flex-direction: column;
background: #000;
align-items: center;
justify-content: center;
background: rgba(18, 18, 18, 0.85);
backdrop-filter: blur(10px);
-webkit-backdrop-filter: blur(10px);
@media (orientation: landscape) {
bottom: 15px;
@@ -758,27 +781,24 @@ ion-content.full-screen-content {
}
video {
width: 100%;
height: 100%;
object-fit: cover;
transform: scaleX(-1); // Mirror camera preview
position: absolute;
width: 1px;
height: 1px;
opacity: 0;
pointer-events: none;
}
.camera-preview-overlay {
position: absolute;
bottom: 0;
left: 0;
right: 0;
background: rgba(0, 0, 0, 0.6);
padding: 2px 0;
display: flex;
justify-content: center;
align-items: center;
.angle-indicator {
font-size: 0.75rem;
font-size: 0.85rem;
font-weight: 700;
color: #fff;
display: flex;
align-items: center;
&.tilted {
color: #2ed573;
+62 -42
View File
@@ -1,4 +1,4 @@
import { Component, OnInit, OnDestroy, signal, computed, effect, inject, ElementRef, AfterViewInit } from '@angular/core';
import { Component, OnInit, OnDestroy, signal, computed, effect, inject, ElementRef, AfterViewInit, untracked } from '@angular/core';
import { DomSanitizer, SafeResourceUrl } from '@angular/platform-browser';
import { ActivatedRoute, Router } from '@angular/router';
import { AlertController, ToastController, GestureController } from '@ionic/angular';
@@ -177,19 +177,40 @@ export class PlayerPage implements OnInit, AfterViewInit, OnDestroy {
if (!found) {
found = myCantiList.find(c => c.id === id);
}
if (!found) {
found = this.playlistService.remoteCustomSongs().find(c => c.id === id || String(c.id_canti) === id);
}
if (!found) {
found = comunitaCantiPers.find(c => c.id === id);
}
if (found) {
const current = this.canto();
// Avoid duplicate triggers for the same song
if (!current || current.id !== found.id) {
const current = untracked(() => this.canto());
// Avoid duplicate triggers for the same song, but update if song content changed
const contentChanged = !current ||
current.id !== found.id ||
current.titolo !== found.titolo ||
current.autore !== found.autore ||
current.link_youtube !== found.link_youtube ||
current.testo !== found.testo ||
current.accordi !== found.accordi ||
JSON.stringify(current.id_momenti) !== JSON.stringify(found.id_momenti);
if (contentChanged) {
this.logPreviousSongTime();
this.canto.set(found);
this.currentLineIndex.set(0);
this.lastScrollBlock.set('start');
this.songStartTime = Date.now();
this.cantiService.getStorage()?.set('last_song_id', id);
this.channel.postMessage({ type: 'SYNC_CANTO', id: found.id });
this.channel.postMessage({ type: 'SYNC_INDEX', index: 0 });
setTimeout(() => {
const scrollEl = this.el.nativeElement.querySelector('.lyrics-container');
if (scrollEl) {
scrollEl.scrollTop = 0;
}
}, 100);
}
}
}
@@ -203,15 +224,27 @@ export class PlayerPage implements OnInit, AfterViewInit, OnDestroy {
const activePlaylistId = this.playlistService.activePlaylistId();
let playlistSongSetting: any = null;
if (activePlaylistId) {
const pl = this.playlistService.playlists().find(p => p.id === activePlaylistId);
if (pl && pl.songSettings && pl.songSettings[c.id]) {
playlistSongSetting = pl.songSettings[c.id];
if (activePlaylistId.startsWith('remote_')) {
const pl = this.playlistService.remotePlaylist();
if (pl && pl.songSettings && pl.songSettings[c.id]) {
playlistSongSetting = pl.songSettings[c.id];
}
} else {
const pl = this.playlistService.playlists().find(p => p.id === activePlaylistId);
if (pl && pl.songSettings && pl.songSettings[c.id]) {
playlistSongSetting = pl.songSettings[c.id];
}
}
}
if (playlistSongSetting) {
this.transposeAmount.set(playlistSongSetting.tonalita !== undefined ? playlistSongSetting.tonalita : 0);
this.autoscrollSpeed.set(playlistSongSetting.speed !== undefined ? playlistSongSetting.speed : 2);
if (playlistSongSetting.zoom !== undefined) {
this.fontSize.set(playlistSongSetting.zoom);
} else {
this.fontSize.set(1.0);
}
} else if (this.comunitaService.comunitaCode() && this.comunitaService.isFilterActive()) {
const settings = this.comunitaService.comunitaCantiSettings();
const songSetting = settings.find(s => s.id_canti === c.id_canti || s.id_canti === Number(c.id));
@@ -322,6 +355,7 @@ export class PlayerPage implements OnInit, AfterViewInit, OnDestroy {
onTouchEnd() {
this.initialPinchDistance = null;
this.updatePlaylistSettings();
}
private getDistance(t1: Touch, t2: Touch): number {
@@ -334,9 +368,7 @@ export class PlayerPage implements OnInit, AfterViewInit, OnDestroy {
this.transposeAmount.set(0);
}
transposeUp() {
this.transposeAmount.update(v => v + 1);
private updatePlaylistSettings() {
const c = this.canto();
if (c) {
const dbSpeed = this.autoscrollSpeed() * 100;
@@ -344,30 +376,35 @@ export class PlayerPage implements OnInit, AfterViewInit, OnDestroy {
const activePlaylistId = this.playlistService.activePlaylistId();
if (activePlaylistId) {
this.playlistService.updatePlaylistSongSettings(activePlaylistId, c.id, this.transposeAmount(), this.autoscrollSpeed());
if (activePlaylistId.startsWith('remote_')) {
return;
}
this.playlistService.updatePlaylistSongSettings(
activePlaylistId,
c.id,
this.transposeAmount(),
this.autoscrollSpeed(),
this.fontSize()
);
}
}
}
transposeUp() {
this.transposeAmount.update(v => v + 1);
this.updatePlaylistSettings();
}
transposeDown() {
this.transposeAmount.update(v => v - 1);
const c = this.canto();
if (c) {
const dbSpeed = this.autoscrollSpeed() * 100;
this.statsService.updateSongSettings(c.id_canti, this.transposeAmount(), dbSpeed);
const activePlaylistId = this.playlistService.activePlaylistId();
if (activePlaylistId) {
this.playlistService.updatePlaylistSongSettings(activePlaylistId, c.id, this.transposeAmount(), this.autoscrollSpeed());
}
}
this.updatePlaylistSettings();
}
zoomIn() {
if (this.fontSize() < this.MAX_FONT) {
this.fontSize.update(v => Math.min(v + this.FONT_STEP, this.MAX_FONT));
this.channel.postMessage({ type: 'SYNC_FONT', fontSize: this.fontSize() });
this.updatePlaylistSettings();
}
}
@@ -375,6 +412,7 @@ export class PlayerPage implements OnInit, AfterViewInit, OnDestroy {
if (this.fontSize() > this.MIN_FONT) {
this.fontSize.update(v => Math.max(v - this.FONT_STEP, this.MIN_FONT));
this.channel.postMessage({ type: 'SYNC_FONT', fontSize: this.fontSize() });
this.updatePlaylistSettings();
}
}
@@ -857,30 +895,12 @@ export class PlayerPage implements OnInit, AfterViewInit, OnDestroy {
increaseAutoscrollSpeed() {
this.autoscrollSpeed.update(s => Math.min(10, s + 1));
const c = this.canto();
if (c) {
const dbSpeed = this.autoscrollSpeed() * 100;
this.statsService.updateSongSettings(c.id_canti, this.transposeAmount(), dbSpeed);
const activePlaylistId = this.playlistService.activePlaylistId();
if (activePlaylistId) {
this.playlistService.updatePlaylistSongSettings(activePlaylistId, c.id, this.transposeAmount(), this.autoscrollSpeed());
}
}
this.updatePlaylistSettings();
}
decreaseAutoscrollSpeed() {
this.autoscrollSpeed.update(s => Math.max(1, s - 1));
const c = this.canto();
if (c) {
const dbSpeed = this.autoscrollSpeed() * 100;
this.statsService.updateSongSettings(c.id_canti, this.transposeAmount(), dbSpeed);
const activePlaylistId = this.playlistService.activePlaylistId();
if (activePlaylistId) {
this.playlistService.updatePlaylistSongSettings(activePlaylistId, c.id, this.transposeAmount(), this.autoscrollSpeed());
}
}
this.updatePlaylistSettings();
}
private logPreviousSongTime() {
@@ -5,8 +5,9 @@ import { IonicModule, ToastController, IonTextarea, PopoverController, NavContro
import { createWorker } from 'tesseract.js';
import { CantiService } from '../../services/canti.service';
import { MyCantiService } from '../../services/my-canti.service';
import { PlaylistService } from '../../services/playlist.service';
import { ThemeService } from '../../services/theme.service';
import { ActivatedRoute, RouterModule } from '@angular/router';
import { ActivatedRoute, RouterModule, Router } from '@angular/router';
@Component({
selector: 'app-propose-canto',
@@ -21,9 +22,11 @@ export class ProposeCantoPage implements OnInit {
public cantiService = inject(CantiService);
private myCantiService = inject(MyCantiService);
private playlistService = inject(PlaylistService);
private navCtrl = inject(NavController);
public themeService = inject(ThemeService);
private route = inject(ActivatedRoute);
private router = inject(Router);
title: string = '';
author: string = '';
@@ -112,10 +115,11 @@ export class ProposeCantoPage implements OnInit {
const editId = params['editId'];
if (editId) {
this.editId = editId;
// Find the song in standard canti or personal canti list
// Find the song in standard canti, personal canti, or remote custom canti list
const song = [
...this.cantiService.canti(),
...this.myCantiService.myCanti()
...this.myCantiService.myCanti(),
...this.playlistService.remoteCustomSongs()
].find(c => c.id === editId);
if (song) {
@@ -128,8 +132,8 @@ export class ProposeCantoPage implements OnInit {
const litIds = this.cantiService.indiceLiturgico().map(m => m.id);
const temIds = this.cantiService.indiceTematico().map(m => m.id);
this.selectedLiturgico = song.id_momenti?.filter(id => litIds.includes(id)) || [];
this.selectedTematico = song.id_momenti?.filter(id => temIds.includes(id)) || [];
this.selectedLiturgico = song.id_momenti?.filter((id: number) => litIds.includes(id)) || [];
this.selectedTematico = song.id_momenti?.filter((id: number) => temIds.includes(id)) || [];
}
}
});
@@ -749,16 +753,62 @@ export class ProposeCantoPage implements OnInit {
// Combine lit and tematico for id_momenti
const id_momenti = [...this.selectedLiturgico, ...this.selectedTematico];
await this.myCantiService.saveCanto({
id: this.editId || undefined,
titolo: this.title,
autore: this.author,
link_youtube: this.youtubeLink,
testo: this.content,
accordi: this.content, // Save to both fields for compatibility
id_momenti: id_momenti
});
const activePlaylistId = this.playlistService.activePlaylistId();
const isRemotePlaylist = activePlaylistId && activePlaylistId.startsWith('remote_');
this.navCtrl.back();
if (isRemotePlaylist) {
// 1. Clone the song (generate a brand new my_... ID)
const savedCanto = await this.myCantiService.saveCanto({
titolo: this.title,
autore: this.author,
link_youtube: this.youtubeLink,
testo: this.content,
accordi: this.content,
id_momenti: id_momenti
});
// 2. Clone/convert remote playlist to local personal playlist
const remotePl = this.playlistService.remotePlaylist();
if (remotePl) {
const originalIds = remotePl.ids || [];
const updatedIds = originalIds.map((id: string) => id === this.editId ? savedCanto.id : id);
const songSettings = { ...(remotePl.songSettings || {}) };
if (this.editId && songSettings[this.editId]) {
songSettings[savedCanto.id] = { ...songSettings[this.editId] };
delete songSettings[this.editId];
}
const localName = remotePl.name.replace('[Remote] ', '');
// Force save as a new local playlist
this.playlistService.activePlaylistId.set(null);
await this.playlistService.savePlaylist(localName, updatedIds, songSettings);
}
// Navigate to the player with the new cloned song ID immediately
this.router.navigate(['/player'], { queryParams: { id: savedCanto.id }, replaceUrl: true });
} else {
// Standard path
const savedCanto = await this.myCantiService.saveCanto({
id: this.editId || undefined,
titolo: this.title,
autore: this.author,
link_youtube: this.youtubeLink,
testo: this.content,
accordi: this.content, // Save to both fields for compatibility
id_momenti: id_momenti
});
if (this.editId && !this.editId.startsWith('my_') && savedCanto && savedCanto.id) {
await this.playlistService.replaceSongIdInPlaylists(this.editId, savedCanto.id);
}
if (this.editId && savedCanto && savedCanto.id && this.editId !== savedCanto.id) {
this.router.navigate(['/player'], { queryParams: { id: savedCanto.id }, replaceUrl: true });
} else {
this.navCtrl.back();
}
}
}
}
+41
View File
@@ -156,6 +156,47 @@
</ion-item>
</div>
<!-- Identità Utente e Ripristino -->
<div class="settings-group glass ion-margin-bottom" *ngIf="settingsService.comunitaEnabled() || settingsService.showEditor()">
<div class="group-header ion-padding-start ion-padding-top">
<h2 class="outfit-font settings-group-title" style="color: var(--ion-color-secondary); font-weight: bold; font-size: 1.05rem;">
Identità Utente e Ripristino
</h2>
</div>
<div class="ion-padding-horizontal ion-padding-bottom">
<p class="settings-item-subtitle outfit-font" style="margin: 0 0 16px 0; font-size: 0.85rem; line-height: 1.45; opacity: 0.85; color: var(--ion-text-color);">
Questo codice univoco identifica in modo anonimo il tuo dispositivo e ti consente di gestire comunità e canti. Salva il codice o il QR code per ripristinare il tuo profilo su un nuovo dispositivo.
</p>
<div class="glass" style="background: rgba(255,255,255,0.03); border: 1px solid rgba(255,255,255,0.06); padding: 12px; border-radius: 12px; display: flex; align-items: center; justify-content: space-between; margin-bottom: 16px;">
<div style="overflow: hidden; text-overflow: ellipsis; white-space: nowrap; margin-right: 12px; flex-grow: 1;">
<div style="font-size: 0.72rem; text-transform: uppercase; color: var(--ion-color-secondary); font-weight: bold; letter-spacing: 0.05em; margin-bottom: 2px;">Codice ID</div>
<div style="font-family: monospace; font-size: 0.78rem; opacity: 0.9; color: var(--ion-text-color);" class="select-all">{{ settingsService.userUuid() }}</div>
</div>
<ion-button fill="clear" size="small" (click)="showBackupQrCode()" color="secondary" style="margin: 0; --padding-start: 4px; --padding-end: 4px;">
<ion-icon name="qr-code-outline" slot="icon-only" style="font-size: 1.35rem;"></ion-icon>
</ion-button>
</div>
<div style="display: grid; grid-template-columns: 1fr 1fr; gap: 10px;">
<ion-button expand="block" fill="outline" color="secondary" (click)="scanRestoreQrCode()" class="outfit-font" style="--border-radius: 10px; font-size: 0.8rem; font-weight: bold; text-transform: none; margin: 0; height: 38px;">
<ion-icon name="scan-outline" slot="start"></ion-icon>
Scansiona QR
</ion-button>
<ion-button expand="block" fill="outline" color="secondary" (click)="manualRestore()" class="outfit-font" style="--border-radius: 10px; font-size: 0.8rem; font-weight: bold; text-transform: none; margin: 0; height: 38px;">
<ion-icon name="keypad-outline" slot="start"></ion-icon>
Inserisci Codice
</ion-button>
</div>
<ion-button expand="block" fill="solid" color="secondary" (click)="syncLocalDataToServer()" class="outfit-font ion-margin-top" style="--border-radius: 10px; font-size: 0.85rem; font-weight: bold; text-transform: none; margin-left: 0; margin-right: 0; margin-bottom: 0; height: 42px;">
<ion-icon name="cloud-upload-outline" slot="start"></ion-icon>
Salva Backup sul Server
</ion-button>
</div>
</div>
<!-- Invio Dati Statistici -->
<div class="settings-group glass ion-margin-bottom">
<ion-item class="transparent-item" lines="none">
+207 -1
View File
@@ -4,7 +4,7 @@ import { SettingsService } from '../../services/settings.service';
import { CantiService } from '../../services/canti.service';
import { ConnectivityService } from '../../services/connectivity.service';
import { SwUpdate, VersionReadyEvent } from '@angular/service-worker';
import { ToastController, ModalController, AlertController } from '@ionic/angular';
import { ToastController, ModalController, AlertController, LoadingController } from '@ionic/angular';
import { VERSION } from '../../version';
import { PlaylistService } from '../../services/playlist.service';
import { Router } from '@angular/router';
@@ -15,6 +15,8 @@ import { CantiLettureService } from '../../services/canti-letture.service';
import { ComunitaService } from '../../services/comunita.service';
import { environment } from '../../../environments/environment';
import { showFullscreenUpdateOverlay } from '../../app.component';
import { QrScannerComponent } from '../../components/qr-scanner/qr-scanner.component';
import { IdentityQrModalComponent } from '../../components/identity-qr-modal/identity-qr-modal.component';
@Component({
selector: 'app-settings',
@@ -35,6 +37,7 @@ export class SettingsPage {
private toastCtrl = inject(ToastController);
private modalCtrl = inject(ModalController);
private alertCtrl = inject(AlertController);
private loadingCtrl = inject(LoadingController);
private router = inject(Router);
public version = VERSION;
@@ -52,6 +55,209 @@ export class SettingsPage {
this.showIosInstructions = !this.showIosInstructions;
}
async showBackupQrCode() {
const modal = await this.modalCtrl.create({
component: IdentityQrModalComponent,
componentProps: {
userUuid: this.settingsService.userUuid()
}
});
return await modal.present();
}
async scanRestoreQrCode() {
const modal = await this.modalCtrl.create({
component: QrScannerComponent
});
await modal.present();
const { data } = await modal.onWillDismiss();
if (data) {
this.confirmRestore(data);
}
}
async manualRestore() {
const alert = await this.alertCtrl.create({
header: 'Ripristina con Codice',
message: 'Digita o incolla il tuo codice identificativo univoco.',
inputs: [
{
name: 'code',
type: 'text',
placeholder: 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx',
value: ''
}
],
buttons: [
{
text: 'Annulla',
role: 'cancel'
},
{
text: 'Conferma',
handler: (data) => {
if (data.code && data.code.trim()) {
this.confirmRestore(data.code.trim());
}
}
}
]
});
await alert.present();
}
private async confirmRestore(code: string) {
// Basic validation for UUID
const uuidRegex = /^[0-9a-f]{8}-[0-9a-f]{4}-[1-5][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/i;
if (!uuidRegex.test(code)) {
const errorAlert = await this.alertCtrl.create({
header: 'Codice Non Valido',
message: 'Il codice inserito non sembra essere un identificativo univoco valido. Riprova.',
buttons: ['OK']
});
await errorAlert.present();
return;
}
const alert = await this.alertCtrl.create({
header: 'Ripristina Identità',
message: 'Sei sicuro di voler ripristinare questa identità? L\'ID attuale del dispositivo verrà sovrascritto permanentemente ed eventuali canti e playlist remoti associati al nuovo ID verranno scaricati.',
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/${code}.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 backup data:', err);
} finally {
await loading.dismiss();
}
this.settingsService.setUserUuid(code);
const toast = await this.toastCtrl.create({
message: 'Identità e dati ripristinati con successo! Ricaricamento...',
duration: 2000,
color: 'success',
position: 'bottom'
});
await toast.present();
setTimeout(() => {
window.location.replace(window.location.origin + window.location.pathname);
}, 1500);
}
}
]
});
await alert.present();
}
async syncLocalDataToServer() {
const uid = this.settingsService.userUuid();
if (!uid) {
const alert = await this.alertCtrl.create({
header: 'Errore',
message: 'Nessun identificativo utente (UID) trovato.',
buttons: ['OK']
});
await alert.present();
return;
}
const toastLoading = await this.toastCtrl.create({
message: 'Invio backup in corso...',
duration: 1500,
color: 'secondary'
});
await toastLoading.present();
try {
await this.playlistService.syncLocalDataToServer();
await toastLoading.dismiss();
const toastSuccess = await this.toastCtrl.create({
message: 'Backup sincronizzato con successo!',
duration: 3000,
color: 'success'
});
await toastSuccess.present();
} catch (err: any) {
try {
await toastLoading.dismiss();
} catch (e) {}
console.error('Failed to backup to server:', err);
const alertError = await this.alertCtrl.create({
header: 'Errore di sincronizzazione',
message: 'Impossibile inviare il backup al server. Controlla la connessione e riprova.',
buttons: ['OK']
});
await alertError.present();
}
}
onModeChange(event: any) {
this.settingsService.setShowChordsDefault(event.detail.value === 'chords');
}