Miglioramenti generali layout player landscape/portrait, traduzione pulsanti navigazione, gestione aggiornamenti PWA e modifiche al posizionamento del pulsante aggiungi canto
This commit is contained in:
+24
-57
@@ -19,9 +19,6 @@
|
||||
<ion-button (click)="importPlaylist()" class="add-btn">
|
||||
<ion-icon slot="icon-only" name="qr-code-outline"></ion-icon>
|
||||
</ion-button>
|
||||
<ion-button routerLink="/propose-canto" class="add-btn" *ngIf="!playlistService.selectionMode() && settingsService.showEditor()">
|
||||
<ion-icon slot="icon-only" name="add-outline"></ion-icon>
|
||||
</ion-button>
|
||||
<ion-button routerLink="/settings" class="settings-btn">
|
||||
<ion-icon slot="icon-only" name="settings-outline"></ion-icon>
|
||||
</ion-button>
|
||||
@@ -48,6 +45,19 @@
|
||||
{{ filteredCanti().length }}
|
||||
</div>
|
||||
<div class="filter-buttons">
|
||||
<ion-button
|
||||
[fill]="activeFilterType() === 'playlist' || playlistService.activeListName() !== null ? 'solid' : 'outline'"
|
||||
size="small"
|
||||
(click)="playlistService.allPlaylists().length > 0 ? toggleFilterType('playlist') : playlistService.toggleSelectionMode()"
|
||||
class="filter-chip">
|
||||
{{ 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)">
|
||||
<ion-icon slot="end" name="close-circle"></ion-icon>
|
||||
</span>
|
||||
</ion-button>
|
||||
|
||||
<div class="comunita-filter-wrapper" style="position: relative; display: inline-flex; align-items: center; margin-right: 8px; z-index: 999;" *ngIf="settingsService.comunitaEnabled()">
|
||||
<ion-button
|
||||
[fill]="comunitaService.isFilterActive() ? 'solid' : 'outline'"
|
||||
@@ -116,19 +126,6 @@
|
||||
</span>
|
||||
</ion-button>
|
||||
|
||||
<ion-button
|
||||
[fill]="activeFilterType() === 'playlist' || playlistService.activeListName() !== null ? 'solid' : 'outline'"
|
||||
size="small"
|
||||
(click)="playlistService.allPlaylists().length > 0 ? toggleFilterType('playlist') : playlistService.toggleSelectionMode()"
|
||||
class="filter-chip">
|
||||
{{ 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)">
|
||||
<ion-icon slot="end" name="close-circle"></ion-icon>
|
||||
</span>
|
||||
</ion-button>
|
||||
|
||||
<ion-button
|
||||
[fill]="showTopTen() ? 'solid' : 'outline'"
|
||||
size="small"
|
||||
@@ -186,8 +183,16 @@
|
||||
<div
|
||||
class="momento-chip glass"
|
||||
[class.active]="showNonValidati()"
|
||||
(click)="toggleNonValidati()">
|
||||
Non Validati
|
||||
(click)="toggleNonValidati()"
|
||||
style="display: inline-flex; align-items: center; gap: 6px;">
|
||||
<span>Non Validati</span>
|
||||
<ion-icon
|
||||
*ngIf="showNonValidati() && !playlistService.selectionMode() && settingsService.showEditor()"
|
||||
name="add-outline"
|
||||
(click)="$event.stopPropagation()"
|
||||
routerLink="/propose-canto"
|
||||
style="font-size: 1.1rem; color: #000; font-weight: bold; background: rgba(0,0,0,0.1); border-radius: 50%; padding: 2px;">
|
||||
</ion-icon>
|
||||
</div>
|
||||
</ng-container>
|
||||
|
||||
@@ -213,28 +218,6 @@
|
||||
(click)="onInteraction()">
|
||||
|
||||
<div class="ion-padding no-padding-top">
|
||||
<!-- Premium PWA Android Install Banner -->
|
||||
<div class="pwa-android-banner glass ion-margin-bottom" *ngIf="showAndroidBanner()">
|
||||
<div class="banner-inner">
|
||||
<div class="banner-icon-wrapper">
|
||||
<ion-icon name="cloud-download-outline" class="banner-icon"></ion-icon>
|
||||
</div>
|
||||
<div class="banner-text-wrapper">
|
||||
<h3 class="banner-title outfit-font">Installa la nostra App!</h3>
|
||||
<p class="banner-subtitle outfit-font">
|
||||
Accedi a tutti i canti all'istante, anche offline, direttamente dalla tua home screen.
|
||||
</p>
|
||||
</div>
|
||||
<div class="banner-actions">
|
||||
<ion-button fill="solid" size="small" color="secondary" class="install-action-btn outfit-font" (click)="installAndroidPwa()">
|
||||
Installa
|
||||
</ion-button>
|
||||
<ion-button fill="clear" size="small" class="dismiss-action-btn" (click)="dismissAndroidBanner($event)">
|
||||
<ion-icon name="close-outline"></ion-icon>
|
||||
</ion-button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div *ngIf="cantiService.loading() && filteredCanti().length === 0" class="ion-text-center ion-padding loading-container">
|
||||
<div class="loading-wrapper">
|
||||
@@ -440,20 +423,4 @@
|
||||
</ion-toolbar>
|
||||
</ion-footer>
|
||||
|
||||
<!-- Premium PWA iOS Tooltip -->
|
||||
<div class="pwa-ios-tooltip-wrapper" [class.has-player]="youtubePlayerService.currentCantoId()" *ngIf="showIosTooltip()">
|
||||
<div class="pwa-ios-tooltip glass">
|
||||
<div class="tooltip-header">
|
||||
<span class="tooltip-title outfit-font">Installa su iPhone</span>
|
||||
<ion-button fill="clear" size="small" class="dismiss-btn" (click)="dismissIosTooltip($event)">
|
||||
<ion-icon name="close-outline"></ion-icon>
|
||||
</ion-button>
|
||||
</div>
|
||||
<div class="tooltip-body">
|
||||
<p class="tooltip-text outfit-font">
|
||||
Tocca il pulsante Condividi <span class="safari-icon-inline"><ion-icon name="share-outline"></ion-icon></span> in basso e seleziona <strong>"Aggiungi a schermata Home"</strong>.
|
||||
</p>
|
||||
</div>
|
||||
<div class="tooltip-arrow"></div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
+93
-69
@@ -43,9 +43,6 @@ export class HomePage implements OnDestroy {
|
||||
public isAddingSongs = signal<boolean>(false);
|
||||
public reorderList = signal<any[]>([]);
|
||||
public limit = signal<number>(30);
|
||||
|
||||
public showAndroidBanner = signal<boolean>(false);
|
||||
public showIosTooltip = signal<boolean>(false);
|
||||
public hasUpdateAvailable = signal<boolean>(false);
|
||||
|
||||
public fontSize = signal<number>(1.0);
|
||||
@@ -77,6 +74,7 @@ export class HomePage implements OnDestroy {
|
||||
|
||||
private firstInteraction = true;
|
||||
private updatePollInterval: any = null;
|
||||
private queryParamsSubscription: any = null;
|
||||
|
||||
async checkForAppUpdate(event?: Event) {
|
||||
if (event) event.stopPropagation();
|
||||
@@ -117,14 +115,32 @@ export class HomePage implements OnDestroy {
|
||||
await registration.update();
|
||||
}
|
||||
|
||||
let sub: any = null;
|
||||
let readyPromise: Promise<void> | undefined = undefined;
|
||||
|
||||
if (this.swUpdate.isEnabled) {
|
||||
readyPromise = new Promise<void>((resolve) => {
|
||||
sub = this.swUpdate.versionUpdates
|
||||
.pipe(
|
||||
filter((evt): evt is VersionReadyEvent => evt.type === 'VERSION_READY'),
|
||||
first()
|
||||
)
|
||||
.subscribe(() => {
|
||||
resolve();
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
if (this.swUpdate.isEnabled) {
|
||||
const swFoundUpdate = await this.swUpdate.checkForUpdate();
|
||||
if (swFoundUpdate) {
|
||||
await this.applyUpdateAndReload();
|
||||
await this.applyUpdateAndReload(readyPromise, sub);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
if (sub) sub.unsubscribe();
|
||||
|
||||
const versionMismatch = await this.checkVersionJson();
|
||||
if (versionMismatch) {
|
||||
await this.applyUpdateAndReload();
|
||||
@@ -144,7 +160,7 @@ export class HomePage implements OnDestroy {
|
||||
}
|
||||
}
|
||||
|
||||
private async applyUpdateAndReload() {
|
||||
private async applyUpdateAndReload(readyPromise?: Promise<void>, subscription?: any) {
|
||||
const overlay = showFullscreenUpdateOverlay();
|
||||
|
||||
let activated = false;
|
||||
@@ -164,20 +180,19 @@ export class HomePage implements OnDestroy {
|
||||
}, 600);
|
||||
};
|
||||
|
||||
if (this.swUpdate.isEnabled) {
|
||||
this.swUpdate.versionUpdates
|
||||
.pipe(
|
||||
filter((evt): evt is VersionReadyEvent => evt.type === 'VERSION_READY'),
|
||||
first()
|
||||
)
|
||||
.subscribe(() => {
|
||||
activateAndReload();
|
||||
});
|
||||
if (readyPromise) {
|
||||
Promise.race([
|
||||
readyPromise,
|
||||
new Promise((resolve) => setTimeout(resolve, 25000))
|
||||
]).then(() => {
|
||||
if (subscription) subscription.unsubscribe();
|
||||
activateAndReload();
|
||||
});
|
||||
} else {
|
||||
setTimeout(() => {
|
||||
activateAndReload();
|
||||
}, 1000);
|
||||
}
|
||||
|
||||
setTimeout(() => {
|
||||
activateAndReload();
|
||||
}, 6000);
|
||||
}
|
||||
|
||||
private async checkVersionJson(): Promise<boolean> {
|
||||
@@ -429,21 +444,7 @@ export class HomePage implements OnDestroy {
|
||||
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';
|
||||
|
||||
this.showAndroidBanner.set(
|
||||
this.settingsService.isAndroid() &&
|
||||
!this.settingsService.isStandalone() &&
|
||||
!androidDismissed
|
||||
);
|
||||
|
||||
this.showIosTooltip.set(
|
||||
this.settingsService.isIos() &&
|
||||
!this.settingsService.isStandalone() &&
|
||||
!iosDismissed
|
||||
);
|
||||
|
||||
// Track initial community filter state to avoid clearing during the initial run of the effect
|
||||
let prevFilterActive = this.comunitaService.isFilterActive();
|
||||
@@ -482,17 +483,43 @@ export class HomePage implements OnDestroy {
|
||||
}
|
||||
});
|
||||
|
||||
this.route.queryParams.subscribe(params => {
|
||||
if (params['import']) {
|
||||
this.handleImport(params['import']);
|
||||
let queryParamsSubscribed = false;
|
||||
effect(() => {
|
||||
if (this.settingsService.isVersionCheckComplete() && !queryParamsSubscribed) {
|
||||
queryParamsSubscribed = true;
|
||||
this.queryParamsSubscription = this.route.queryParams.subscribe(params => {
|
||||
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']);
|
||||
}
|
||||
});
|
||||
}
|
||||
if (params['playlist-uid']) {
|
||||
this.handleRemotePlaylistImport(params['playlist-uid'], params['playlist-id']);
|
||||
}, { allowSignalWrites: true });
|
||||
|
||||
let hasAutoActivatedPlaylist = false;
|
||||
effect(() => {
|
||||
const lastPl = this.playlistService.lastPlaylist();
|
||||
const versionReady = this.settingsService.isVersionCheckComplete();
|
||||
|
||||
if (versionReady && lastPl && !hasAutoActivatedPlaylist) {
|
||||
const params = this.route.snapshot.queryParams;
|
||||
if (!params['id'] && !params['import'] && !params['playlist-uid'] && !params['restore-uid']) {
|
||||
hasAutoActivatedPlaylist = true;
|
||||
this.selectPlaylist(lastPl);
|
||||
this.activeFilterType.set('playlist');
|
||||
if (lastPl.ids && lastPl.ids.length > 0) {
|
||||
this.goToCanto(lastPl.ids[0]);
|
||||
}
|
||||
} else {
|
||||
hasAutoActivatedPlaylist = true;
|
||||
}
|
||||
}
|
||||
if (params['restore-uid']) {
|
||||
this.handleRemoteRestore(params['restore-uid']);
|
||||
}
|
||||
});
|
||||
}, { allowSignalWrites: true });
|
||||
|
||||
let prevSelectionMode = false;
|
||||
|
||||
@@ -550,33 +577,7 @@ export class HomePage implements OnDestroy {
|
||||
}, { allowSignalWrites: true });
|
||||
}
|
||||
|
||||
dismissAndroidBanner(event?: Event) {
|
||||
if (event) event.stopPropagation();
|
||||
this.showAndroidBanner.set(false);
|
||||
localStorage.setItem('pwa-android-dismissed', 'true');
|
||||
}
|
||||
|
||||
dismissIosTooltip(event?: Event) {
|
||||
if (event) event.stopPropagation();
|
||||
this.showIosTooltip.set(false);
|
||||
localStorage.setItem('pwa-ios-dismissed', 'true');
|
||||
}
|
||||
|
||||
async installAndroidPwa() {
|
||||
if (this.settingsService.deferredPrompt()) {
|
||||
await this.settingsService.installPwa();
|
||||
this.dismissAndroidBanner();
|
||||
} else {
|
||||
const toast = await this.toastCtrl.create({
|
||||
message: 'Tocca il menu del browser (i tre puntini in alto a destra) e seleziona "Aggiungi a schermata Home" o "Installa app".',
|
||||
duration: 6000,
|
||||
position: 'bottom',
|
||||
color: 'secondary',
|
||||
buttons: [{ text: 'OK', role: 'cancel' }]
|
||||
});
|
||||
await toast.present();
|
||||
}
|
||||
}
|
||||
|
||||
async handleImport(base64: string) {
|
||||
try {
|
||||
@@ -631,7 +632,11 @@ export class HomePage implements OnDestroy {
|
||||
|
||||
if (this.playlistService.processImportJson(json)) {
|
||||
this.limit.set(50); // Mostra più canti inizialmente per le playlist speciali
|
||||
this.router.navigate([], { queryParams: { import: null }, queryParamsHandling: 'merge' });
|
||||
this.activeFilterType.set('playlist');
|
||||
await this.router.navigate([], { queryParams: { import: null }, queryParamsHandling: 'merge', replaceUrl: true });
|
||||
if (json.ids && json.ids.length > 0) {
|
||||
this.goToCanto(json.ids[0]);
|
||||
}
|
||||
}
|
||||
} catch (e) {
|
||||
console.error('Failed to import playlist', e);
|
||||
@@ -651,6 +656,7 @@ export class HomePage implements OnDestroy {
|
||||
});
|
||||
await loading.present();
|
||||
|
||||
let hasNavigatedToCanto = false;
|
||||
try {
|
||||
const response = await fetch(`https://api.canticristiani.it/${uid}.json?cb=${Date.now()}`, { cache: 'no-store' });
|
||||
if (!response.ok) {
|
||||
@@ -712,6 +718,7 @@ export class HomePage implements OnDestroy {
|
||||
|
||||
// Select it immediately
|
||||
this.selectPlaylist(selectedPl);
|
||||
this.activeFilterType.set('playlist');
|
||||
|
||||
await loading.dismiss();
|
||||
|
||||
@@ -721,6 +728,12 @@ export class HomePage implements OnDestroy {
|
||||
color: 'success'
|
||||
});
|
||||
await toast.present();
|
||||
|
||||
if (selectedPl.ids && selectedPl.ids.length > 0) {
|
||||
hasNavigatedToCanto = true;
|
||||
await this.router.navigate([], { queryParams: { 'playlist-uid': null, 'playlist-id': null }, queryParamsHandling: 'merge', replaceUrl: true });
|
||||
this.goToCanto(selectedPl.ids[0]);
|
||||
}
|
||||
} else {
|
||||
throw new Error('Formato dati non valido.');
|
||||
}
|
||||
@@ -734,7 +747,9 @@ export class HomePage implements OnDestroy {
|
||||
});
|
||||
await alert.present();
|
||||
} finally {
|
||||
this.router.navigate([], { queryParams: { 'playlist-uid': null, 'playlist-id': null }, queryParamsHandling: 'merge' });
|
||||
if (!hasNavigatedToCanto) {
|
||||
this.router.navigate([], { queryParams: { 'playlist-uid': null, 'playlist-id': null }, queryParamsHandling: 'merge' });
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -839,6 +854,9 @@ export class HomePage implements OnDestroy {
|
||||
if (this.updatePollInterval) {
|
||||
clearInterval(this.updatePollInterval);
|
||||
}
|
||||
if (this.queryParamsSubscription) {
|
||||
this.queryParamsSubscription.unsubscribe();
|
||||
}
|
||||
}
|
||||
|
||||
onSearch(event: any) {
|
||||
@@ -1408,6 +1426,12 @@ export class HomePage implements OnDestroy {
|
||||
|
||||
// Automatically save to the device!
|
||||
this.playlistService.savePlaylist(playlistName, ids);
|
||||
|
||||
// Activate the playlist filter and open the first song!
|
||||
this.activeFilterType.set('playlist');
|
||||
if (ids.length > 0) {
|
||||
this.goToCanto(ids[0]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user