From 6ed90ffff17ca7f7c98d21d80cb1533a32e2035c Mon Sep 17 00:00:00 2001 From: David Frassi Date: Thu, 18 Jun 2026 10:01:34 +0200 Subject: [PATCH] aggiunto schermo nero durante il play audio in playlist --- angular.json | 4 +- src/app/home/home.page.html | 46 ++++++++++- src/app/home/home.page.scss | 99 ++++++++++++++++++++++- src/app/home/home.page.ts | 93 ++++++++++++++++++++- src/app/pages/player/player.page.html | 39 ++++++++- src/app/pages/player/player.page.scss | 94 ++++++++++++++++++++- src/app/pages/player/player.page.ts | 21 ++++- src/app/pages/settings/settings.page.html | 8 ++ src/app/services/settings.service.ts | 20 +++++ src/app/version.ts | 2 +- 10 files changed, 407 insertions(+), 19 deletions(-) diff --git a/angular.json b/angular.json index 6cbf1a6..f2ce550 100644 --- a/angular.json +++ b/angular.json @@ -57,8 +57,8 @@ }, { "type": "anyComponentStyle", - "maximumWarning": "16kb", - "maximumError": "25kb" + "maximumWarning": "32kb", + "maximumError": "48kb" } ], "fileReplacements": [ diff --git a/src/app/home/home.page.html b/src/app/home/home.page.html index fa29d81..d47c7a0 100644 --- a/src/app/home/home.page.html +++ b/src/app/home/home.page.html @@ -389,15 +389,18 @@
+
- + - + + {{ formatSeconds(youtubePlayerService.videoProgress()) }} + + class="global-range" + style="margin: 0; padding: 0 4px;"> + {{ formatSeconds(youtubePlayerService.videoDuration()) }} + - + @@ -421,4 +427,36 @@ +
+
+
+ +

Schermo nero attivo

+
+ +
+

{{ getPlayingCanto()?.titolo }}

+

{{ getPlayingCanto()?.autore }}

+
+ +
+ {{ formatSeconds(youtubePlayerService.videoProgress()) }} / {{ formatSeconds(youtubePlayerService.videoDuration()) }} +
+ +
+ + + + + + + + + +
+ +

Tocca lo sfondo o l'icona per sbloccare

+
+
+ diff --git a/src/app/home/home.page.scss b/src/app/home/home.page.scss index dbd2a09..6578e83 100644 --- a/src/app/home/home.page.scss +++ b/src/app/home/home.page.scss @@ -730,7 +730,6 @@ ion-title { &.glass { background: rgba(26, 26, 46, 0.95) !important; backdrop-filter: blur(20px); - border-top: 1px solid rgba(255, 255, 255, 0.1); border-radius: 0 !important; box-shadow: none !important; } @@ -1342,4 +1341,102 @@ ion-title { } } +/* BLACK SCREEN OVERLAY FOR CAR MODE */ +.black-screen-overlay { + position: fixed; + top: 0; + left: 0; + right: 0; + bottom: 0; + width: 100%; + height: 100%; + background-color: #000000 !important; + z-index: 9999999 !important; + display: flex; + align-items: center; + justify-content: center; + cursor: pointer; + padding: env(safe-area-inset-top) env(safe-area-inset-right) env(safe-area-inset-bottom) env(safe-area-inset-left); + + .black-screen-content { + display: flex; + flex-direction: column; + align-items: center; + justify-content: center; + color: rgba(255, 255, 255, 0.25); + text-align: center; + padding: 20px; + gap: 12px; + + .car-mode-icon { + font-size: 3.5rem; + color: rgba(255, 255, 255, 0.2); + } + + .car-mode-text { + font-size: 1.2rem; + font-weight: 600; + margin: 0; + } + + .car-mode-subtext { + font-size: 0.85rem; + margin: 0; + opacity: 0.8; + } + + .car-mode-canto-info { + text-align: center; + margin-top: 10px; + + .car-mode-canto-title { + font-size: 1.4rem; + font-weight: 700; + color: rgba(255, 255, 255, 0.3); + margin: 0 0 4px 0; + } + + .car-mode-canto-author { + font-size: 0.95rem; + color: rgba(255, 255, 255, 0.2); + margin: 0; + } + } + + .car-mode-progress { + margin-top: 8px; + + .car-mode-time { + font-size: 1.1rem; + font-weight: 600; + color: rgba(255, 255, 255, 0.25); + } + } + + .car-mode-controls { + display: flex; + align-items: center; + gap: 24px; + margin-top: 15px; + + .car-mode-btn { + --color: rgba(255, 255, 255, 0.25); + --padding-start: 8px; + --padding-end: 8px; + height: 48px; + width: 48px; + margin: 0; + + ion-icon { + font-size: 1.8rem; + } + + &:active { + --color: rgba(255, 255, 255, 0.55); + } + } + } + } +} + diff --git a/src/app/home/home.page.ts b/src/app/home/home.page.ts index 94aca6e..43a5040 100644 --- a/src/app/home/home.page.ts +++ b/src/app/home/home.page.ts @@ -1,4 +1,4 @@ -import { Component, computed, signal, inject, effect, OnDestroy } from '@angular/core'; +import { Component, computed, signal, inject, effect, OnDestroy, HostListener } from '@angular/core'; import { DomSanitizer, SafeResourceUrl } from '@angular/platform-browser'; import { CantiService } from '../services/canti.service'; import { Router, ActivatedRoute } from '@angular/router'; @@ -27,6 +27,28 @@ import { showFullscreenUpdateOverlay } from '../app.component'; standalone: false, }) export class HomePage implements OnDestroy { + @HostListener('window:keydown', ['$event']) + handleKeyDown(event: KeyboardEvent) { + // Only handle keys if a song is currently loaded in the player + if (!this.youtubePlayerService.currentCantoId()) { + return; + } + const target = event.target as HTMLElement; + if (target && (target.tagName === 'INPUT' || target.tagName === 'TEXTAREA' || target.isContentEditable)) { + return; + } + if (event.key === 'ArrowUp' || event.key === 'MediaTrackPrevious') { + this.playPrevPreview(); + event.preventDefault(); + } else if (event.key === 'ArrowDown' || event.key === 'MediaTrackNext') { + this.playNextPreview(); + event.preventDefault(); + } else if (event.key === 'MediaPlayPause') { + this.youtubePlayerService.togglePlayPause(); + event.preventDefault(); + } + } + public searchQuery = signal(''); public selectedLiturgico = signal(null); public selectedTematico = signal(null); @@ -45,6 +67,13 @@ export class HomePage implements OnDestroy { public limit = signal(30); public hasUpdateAvailable = signal(false); + public isBlackOverlayDismissed = signal(false); + public isBlackOverlayActive = computed(() => + this.settingsService.carModeBlackScreen() && + this.youtubePlayerService.currentCantoId() !== null && + !this.isBlackOverlayDismissed() + ); + public fontSize = signal(1.0); public youtubePlayerService = inject(YoutubePlayerService); public comunitaService = inject(ComunitaService); @@ -488,6 +517,14 @@ export class HomePage implements OnDestroy { } }); + // Reset black screen dismissed state when play/pause changes + effect(() => { + const isPlaying = this.youtubePlayerService.isPlaying(); + if (!isPlaying) { + this.isBlackOverlayDismissed.set(false); + } + }, { allowSignalWrites: true }); + let queryParamsSubscribed = false; effect(() => { if (this.settingsService.isVersionCheckComplete() && !queryParamsSubscribed) { @@ -592,6 +629,16 @@ export class HomePage implements OnDestroy { this.isAddingSongs.set(false); } }, { allowSignalWrites: true }); + + // Fullscreen control when black screen overlay is active + effect(() => { + const isBlack = this.isBlackOverlayActive(); + if (isBlack) { + this.enterFullscreen(); + } else { + this.exitFullscreen(); + } + }); } @@ -1932,5 +1979,49 @@ export class HomePage implements OnDestroy { } return dateStr; } + + formatSeconds(seconds: number | undefined | null): string { + if (seconds === undefined || seconds === null || isNaN(seconds)) { + return '0:00'; + } + const mins = Math.floor(seconds / 60); + const secs = Math.floor(seconds % 60); + return `${mins}:${secs < 10 ? '0' : ''}${secs}`; + } + + private async enterFullscreen() { + try { + const docEl = document.documentElement; + if (docEl.requestFullscreen) { + await docEl.requestFullscreen(); + } else if ((docEl as any).webkitRequestFullscreen) { + await (docEl as any).webkitRequestFullscreen(); + } else if ((docEl as any).msRequestFullscreen) { + await (docEl as any).msRequestFullscreen(); + } + } catch (e) { + console.warn('[HomePage] Failed to enter fullscreen:', e); + } + } + + private async exitFullscreen() { + try { + if (document.exitFullscreen) { + if (document.fullscreenElement) { + await document.exitFullscreen(); + } + } else if ((document as any).webkitExitFullscreen) { + if ((document as any).webkitFullscreenElement) { + await (document as any).webkitExitFullscreen(); + } + } else if ((document as any).msExitFullscreen) { + if ((document as any).msFullscreenElement) { + await (document as any).msExitFullscreen(); + } + } + } catch (e) { + console.warn('[HomePage] Failed to exit fullscreen:', e); + } + } } diff --git a/src/app/pages/player/player.page.html b/src/app/pages/player/player.page.html index dfa3849..897d6fd 100644 --- a/src/app/pages/player/player.page.html +++ b/src/app/pages/player/player.page.html @@ -154,14 +154,19 @@
+ {{ formatSeconds(youtubePlayerService.videoProgress()) }} + + class="global-range" + style="margin: 0; padding: 0 4px;"> + + {{ formatSeconds(youtubePlayerService.videoDuration()) }}
@@ -258,7 +263,37 @@ -
+
+
+
+ +

Schermo nero attivo

+
+ +
+

{{ canto()?.titolo }}

+

{{ canto()?.autore }}

+
+ +
+ {{ formatSeconds(youtubePlayerService.videoProgress()) }} / {{ formatSeconds(youtubePlayerService.videoDuration()) }} +
+ +
+ + + + + + + + + +
+ +

Tocca lo sfondo o l'icona per sbloccare

+
+
diff --git a/src/app/pages/player/player.page.scss b/src/app/pages/player/player.page.scss index 92c40e1..5d32535 100644 --- a/src/app/pages/player/player.page.scss +++ b/src/app/pages/player/player.page.scss @@ -891,11 +891,97 @@ ion-content.full-screen-content { position: fixed; top: 0; left: 0; - width: 100vw; - height: 100vh; - background-color: #000000; - z-index: 99999; + right: 0; + bottom: 0; + width: 100%; + height: 100%; + background-color: #000000 !important; + z-index: 9999999 !important; + display: flex; + align-items: center; + justify-content: center; cursor: pointer; + padding: env(safe-area-inset-top) env(safe-area-inset-right) env(safe-area-inset-bottom) env(safe-area-inset-left); + + .black-screen-content { + display: flex; + flex-direction: column; + align-items: center; + justify-content: center; + color: rgba(255, 255, 255, 0.25); + text-align: center; + padding: 20px; + gap: 12px; + + .car-mode-icon { + font-size: 3.5rem; + color: rgba(255, 255, 255, 0.2); + } + + .car-mode-text { + font-size: 1.2rem; + font-weight: 600; + margin: 0; + } + + .car-mode-subtext { + font-size: 0.85rem; + margin: 0; + opacity: 0.8; + } + + .car-mode-canto-info { + text-align: center; + margin-top: 10px; + + .car-mode-canto-title { + font-size: 1.4rem; + font-weight: 700; + color: rgba(255, 255, 255, 0.3); + margin: 0 0 4px 0; + } + + .car-mode-canto-author { + font-size: 0.95rem; + color: rgba(255, 255, 255, 0.2); + margin: 0; + } + } + + .car-mode-progress { + margin-top: 8px; + + .car-mode-time { + font-size: 1.1rem; + font-weight: 600; + color: rgba(255, 255, 255, 0.25); + } + } + + .car-mode-controls { + display: flex; + align-items: center; + gap: 24px; + margin-top: 15px; + + .car-mode-btn { + --color: rgba(255, 255, 255, 0.25); + --padding-start: 8px; + --padding-end: 8px; + height: 48px; + width: 48px; + margin: 0; + + ion-icon { + font-size: 1.8rem; + } + + &:active { + --color: rgba(255, 255, 255, 0.55); + } + } + } + } } .landscape-playlist-btn { diff --git a/src/app/pages/player/player.page.ts b/src/app/pages/player/player.page.ts index 81a453a..c18b312 100644 --- a/src/app/pages/player/player.page.ts +++ b/src/app/pages/player/player.page.ts @@ -33,12 +33,15 @@ export class PlayerPage implements OnInit, AfterViewInit, OnDestroy { if (target && (target.tagName === 'INPUT' || target.tagName === 'TEXTAREA' || target.isContentEditable)) { return; } - if (event.key === 'ArrowUp') { + if (event.key === 'ArrowUp' || event.key === 'MediaTrackPrevious') { this.prevSong(); event.preventDefault(); - } else if (event.key === 'ArrowDown') { + } else if (event.key === 'ArrowDown' || event.key === 'MediaTrackNext') { this.nextSong(); event.preventDefault(); + } else if (event.key === 'MediaPlayPause') { + this.toggleAudio(); + event.preventDefault(); } } @@ -322,11 +325,12 @@ export class PlayerPage implements OnInit, AfterViewInit, OnDestroy { } }, { allowSignalWrites: true }); - // Fullscreen control in landscape mode + // Fullscreen control in landscape mode or when black screen is active effect(() => { const isLandscape = this.isLandscapeActive(); + const isBlack = this.isBlackScreen(); - if (isLandscape) { + if (isLandscape || isBlack) { this.enterFullscreen(); } else { this.exitFullscreen(); @@ -1279,4 +1283,13 @@ export class PlayerPage implements OnInit, AfterViewInit, OnDestroy { this.stopCameraNavigation(); this.channel.close(); } + + formatSeconds(seconds: number | undefined | null): string { + if (seconds === undefined || seconds === null || isNaN(seconds)) { + return '0:00'; + } + const mins = Math.floor(seconds / 60); + const secs = Math.floor(seconds % 60); + return `${mins}:${secs < 10 ? '0' : ''}${secs}`; + } } diff --git a/src/app/pages/settings/settings.page.html b/src/app/pages/settings/settings.page.html index c9d99fa..eb62850 100644 --- a/src/app/pages/settings/settings.page.html +++ b/src/app/pages/settings/settings.page.html @@ -150,6 +150,14 @@ + + + +

Schermo nero durante l'ascolto

+

Se attiva la riproduzione in home, mostra uno schermo nero per risparmiare batteria. Tocca per disattivare.

+
+ +
diff --git a/src/app/services/settings.service.ts b/src/app/services/settings.service.ts index c4e90fa..d9f5752 100644 --- a/src/app/services/settings.service.ts +++ b/src/app/services/settings.service.ts @@ -56,6 +56,9 @@ export class SettingsService { /** Vista orizzontale per proiezione: true = attiva layout landscape per proiezione */ public landscapeProjectionEnabled = signal(false); + /** Schermo nero per l'ascolto delle playlist in macchina: true = attivo */ + public carModeBlackScreen = signal(false); + /** Identificativo utente univoco per la gestione delle comunità */ public userUuid = signal(''); @@ -254,6 +257,13 @@ export class SettingsService { this.landscapeProjectionEnabled.set(false); } + const savedCarModeBlackScreen = localStorage.getItem('car-mode-black-screen'); + if (savedCarModeBlackScreen !== null) { + this.carModeBlackScreen.set(savedCarModeBlackScreen === 'true'); + } else { + this.carModeBlackScreen.set(false); + } + // Sync browser fullscreen state with listeners (supporting vendor prefixes) const updateFullscreenState = () => { const isFs = !!( @@ -311,6 +321,10 @@ export class SettingsService { localStorage.setItem('landscape-projection-enabled', this.landscapeProjectionEnabled().toString()); }); + effect(() => { + localStorage.setItem('car-mode-black-screen', this.carModeBlackScreen().toString()); + }); + effect(() => { const active = this.keepScreenOn(); localStorage.setItem('keep-screen-on', active.toString()); @@ -446,6 +460,12 @@ export class SettingsService { localStorage.setItem('landscape-projection-enabled', newValue.toString()); } + toggleCarModeBlackScreen() { + const newValue = !this.carModeBlackScreen(); + this.carModeBlackScreen.set(newValue); + localStorage.setItem('car-mode-black-screen', newValue.toString()); + } + setChordNotationPreference(val: 'diesis' | 'bemolle') { this.chordNotationPreference.set(val); } diff --git a/src/app/version.ts b/src/app/version.ts index 40758e0..40e9861 100644 --- a/src/app/version.ts +++ b/src/app/version.ts @@ -1 +1 @@ -export const VERSION = '2026.06.18.0234'; +export const VERSION = '2026.06.18.0959';