vers. 2.6.0
This commit is contained in:
+11
-51
@@ -7,63 +7,23 @@ $is_ajax = !empty($_SERVER['HTTP_X_REQUESTED_WITH']) && strtolower($_SERVER['HTT
|
|||||||
|
|
||||||
// Se è una richiesta AJAX, carica solo il contenuto della pagina
|
// Se è una richiesta AJAX, carica solo il contenuto della pagina
|
||||||
if ($is_ajax) {
|
if ($is_ajax) {
|
||||||
switch ($page) {
|
$filePath = getPageFilePath($page, $param);
|
||||||
case 'home':
|
|
||||||
include './pages/page/home.php';
|
// Validazione specifica delle stazioni radio e tv per sicurezza
|
||||||
break;
|
if ($page === 'play' && !empty($param)) {
|
||||||
case 'radio':
|
|
||||||
include './pages/page/radio.php';
|
|
||||||
break;
|
|
||||||
case 'tv':
|
|
||||||
include './pages/page/tv.php';
|
|
||||||
break;
|
|
||||||
case 'play':
|
|
||||||
$stationId = (int)$param;
|
$stationId = (int)$param;
|
||||||
$station = getRadioStation($stationId);
|
$station = getRadioStation($stationId);
|
||||||
if ($station !== null) {
|
if ($station === null) {
|
||||||
include './pages/page/player.php';
|
$filePath = './pages/page/404.php';
|
||||||
} else {
|
|
||||||
include './pages/page/404.php';
|
|
||||||
}
|
}
|
||||||
break;
|
} elseif ($page === 'playtv' && !empty($param)) {
|
||||||
case 'playtv':
|
|
||||||
$stationId = (int)$param;
|
$stationId = (int)$param;
|
||||||
$station = getTVStation($stationId);
|
$station = getTVStation($stationId);
|
||||||
if ($station !== null) {
|
if ($station === null) {
|
||||||
include './pages/page/player_tv.php';
|
$filePath = './pages/page/404.php';
|
||||||
} else {
|
|
||||||
include './pages/page/404.php';
|
|
||||||
}
|
}
|
||||||
break;
|
|
||||||
case 'podcast':
|
|
||||||
include './pages/page/podcast.php';
|
|
||||||
break;
|
|
||||||
case 'page':
|
|
||||||
switch ($param) {
|
|
||||||
case 'about':
|
|
||||||
include './pages/page/about.php';
|
|
||||||
break;
|
|
||||||
case 'contact':
|
|
||||||
include './pages/page/contact.php';
|
|
||||||
break;
|
|
||||||
case 'copyright':
|
|
||||||
include './pages/page/copyright.php';
|
|
||||||
break;
|
|
||||||
case 'termini-condizioni':
|
|
||||||
include './pages/page/terminicondizioni.php';
|
|
||||||
break;
|
|
||||||
case 'policy-privacy':
|
|
||||||
include './pages/page/policyprivacy.php';
|
|
||||||
break;
|
|
||||||
case 'changelog':
|
|
||||||
include './pages/page/changelog.php';
|
|
||||||
break;
|
|
||||||
default:
|
|
||||||
include './pages/page/404.php';
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
default:
|
|
||||||
include './pages/page/404.php';
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
include $filePath;
|
||||||
exit; // Termina l'esecuzione per le richieste AJAX
|
exit; // Termina l'esecuzione per le richieste AJAX
|
||||||
}
|
}
|
||||||
+56
-35
@@ -4,10 +4,6 @@ header('Content-Type: text/html; charset=UTF-8');
|
|||||||
|
|
||||||
// File: config/getPage.inc.php
|
// File: config/getPage.inc.php
|
||||||
|
|
||||||
// Whitelist delle pagine valide
|
|
||||||
$validPages = ['home', 'radio', 'tv', 'play', 'playtv', 'page', 'podcast'];
|
|
||||||
$validSubPages = ['about', 'contact', 'copyright', 'termini-condizioni', 'policy-privacy', 'changelog'];
|
|
||||||
|
|
||||||
// Rileva se l'utente sta usando un dispositivo mobile
|
// Rileva se l'utente sta usando un dispositivo mobile
|
||||||
function isMobile() {
|
function isMobile() {
|
||||||
return preg_match("/(android|avantgo|blackberry|bolt|boost|cricket|docomo|fone|hiptop|mini|mobi|palm|phone|pie|tablet|up\.browser|up\.link|webos|wos)/i", $_SERVER["HTTP_USER_AGENT"]);
|
return preg_match("/(android|avantgo|blackberry|bolt|boost|cricket|docomo|fone|hiptop|mini|mobi|palm|phone|pie|tablet|up\.browser|up\.link|webos|wos)/i", $_SERVER["HTTP_USER_AGENT"]);
|
||||||
@@ -18,8 +14,7 @@ function sanitizePageInput($input) {
|
|||||||
// Rimuovi caratteri pericolosi
|
// Rimuovi caratteri pericolosi
|
||||||
$input = preg_replace('/[^a-zA-Z0-9\-_]/', '', $input);
|
$input = preg_replace('/[^a-zA-Z0-9\-_]/', '', $input);
|
||||||
// Previeni path traversal
|
// Previeni path traversal
|
||||||
$input = str_replace(['..', '/', '\\'], '', $input);
|
return str_replace(['..', '/', '\\'], '', $input);
|
||||||
return $input;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Recupera l'URL richiesto
|
// Recupera l'URL richiesto
|
||||||
@@ -40,45 +35,71 @@ if (isset($path_parts[0]) && $path_parts[0] == 'index.php') {
|
|||||||
array_shift($path_parts);
|
array_shift($path_parts);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Determina la pagina da mostrare in base all'URL con validazione
|
// Determina la pagina e il parametro iniziale
|
||||||
$page = 'home'; // Default sicuro
|
$page = 'home';
|
||||||
$param = '';
|
$param = '';
|
||||||
|
|
||||||
if (isset($path_parts[0]) && !empty($path_parts[0])) {
|
if (isset($path_parts[0]) && !empty($path_parts[0])) {
|
||||||
$requestedPage = sanitizePageInput($path_parts[0]);
|
$page = sanitizePageInput($path_parts[0]);
|
||||||
|
|
||||||
// Verifica se la pagina è nella whitelist
|
|
||||||
if (in_array($requestedPage, $validPages)) {
|
|
||||||
$page = $requestedPage;
|
|
||||||
} else {
|
|
||||||
// Pagina non valida, redirect a 404
|
|
||||||
$page = 'home';
|
|
||||||
error_log("Tentativo di accesso a pagina non valida: " . $path_parts[0]);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (isset($path_parts[1]) && !empty($path_parts[1])) {
|
if (isset($path_parts[1]) && !empty($path_parts[1])) {
|
||||||
$requestedParam = sanitizePageInput($path_parts[1]);
|
$param = sanitizePageInput($path_parts[1]);
|
||||||
|
}
|
||||||
|
|
||||||
// Validazione specifica per tipo di pagina
|
/**
|
||||||
if ($page === 'play' || $page === 'playtv') {
|
* Risolve dinamicamente il percorso del file PHP corrispondente alla pagina e al parametro.
|
||||||
// Per play/playtv, il parametro deve essere un numero
|
* Cerca automaticamente i file all'interno della cartella './pages/page/'.
|
||||||
if (ctype_digit($requestedParam)) {
|
* Se la pagina non esiste, restituisce il file di errore 404.
|
||||||
$param = $requestedParam;
|
*/
|
||||||
} else {
|
function getPageFilePath($page, $param) {
|
||||||
error_log("ID stazione non valido: " . $path_parts[1]);
|
$page = sanitizePageInput($page);
|
||||||
$page = 'home';
|
$param = sanitizePageInput($param);
|
||||||
|
|
||||||
|
// Gestione stazioni radio e TV (player)
|
||||||
|
if ($page === 'play') {
|
||||||
|
if (ctype_digit($param)) {
|
||||||
|
return './pages/page/player.php';
|
||||||
}
|
}
|
||||||
} elseif ($page === 'page') {
|
return './pages/page/404.php';
|
||||||
// Per page, il parametro deve essere nella whitelist
|
}
|
||||||
if (in_array($requestedParam, $validSubPages)) {
|
|
||||||
$param = $requestedParam;
|
if ($page === 'playtv') {
|
||||||
} else {
|
if (ctype_digit($param)) {
|
||||||
error_log("Sottopagina non valida: " . $path_parts[1]);
|
return './pages/page/player_tv.php';
|
||||||
$page = 'home';
|
}
|
||||||
|
return './pages/page/404.php';
|
||||||
|
}
|
||||||
|
|
||||||
|
// Gestione sottopagine generiche /page/{param}
|
||||||
|
if ($page === 'page' && !empty($param)) {
|
||||||
|
$param_no_dash = str_replace('-', '', $param);
|
||||||
|
$paths = [
|
||||||
|
"./pages/page/{$param}.php",
|
||||||
|
"./pages/page/{$param_no_dash}.php"
|
||||||
|
];
|
||||||
|
foreach ($paths as $path) {
|
||||||
|
if (file_exists($path)) {
|
||||||
|
return $path;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
return './pages/page/404.php';
|
||||||
|
}
|
||||||
|
|
||||||
|
// Gestione pagine principali (es: /home, /radio, /tv, /podcast)
|
||||||
|
$page_no_dash = str_replace('-', '', $page);
|
||||||
|
$paths = [
|
||||||
|
"./pages/page/{$page}.php",
|
||||||
|
"./pages/page/{$page_no_dash}.php"
|
||||||
|
];
|
||||||
|
foreach ($paths as $path) {
|
||||||
|
if (file_exists($path)) {
|
||||||
|
return $path;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return './pages/page/404.php';
|
||||||
}
|
}
|
||||||
|
|
||||||
// Debug (rimuovi in produzione)
|
// Debug (rimuovi in produzione)
|
||||||
error_log("Page: $page, Param: $param, Path: $path");
|
error_log("Page resolved: $page, Param: $param, Path resolved file: " . getPageFilePath($page, $param));
|
||||||
+417
@@ -419,3 +419,420 @@ button#formatToggleBtn {
|
|||||||
display: none !important;
|
display: none !important;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* PWA STYLES (Banner, Nav Button, and Instructions Modal) */
|
||||||
|
|
||||||
|
/* Hide all PWA install indicators when running as standalone installed app */
|
||||||
|
@media (display-mode: standalone) {
|
||||||
|
.pwa-install-btn,
|
||||||
|
.pwa-install-banner,
|
||||||
|
.pwa-install-btn-nav {
|
||||||
|
display: none !important;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* iOS navigation standalone fallback check */
|
||||||
|
@media (display-mode: standalone) {
|
||||||
|
body.appBody {
|
||||||
|
padding-top: env(safe-area-inset-top);
|
||||||
|
padding-bottom: env(safe-area-inset-bottom);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* PWA Install Banner Style */
|
||||||
|
.pwa-install-banner {
|
||||||
|
position: fixed;
|
||||||
|
bottom: 20px;
|
||||||
|
left: 50%;
|
||||||
|
transform: translateX(-50%) translateY(120px);
|
||||||
|
width: 90%;
|
||||||
|
max-width: 500px;
|
||||||
|
background: rgba(24, 34, 92, 0.95);
|
||||||
|
backdrop-filter: blur(15px);
|
||||||
|
-webkit-backdrop-filter: blur(15px);
|
||||||
|
border: 1px solid rgba(255, 255, 255, 0.15);
|
||||||
|
border-radius: 24px;
|
||||||
|
padding: 16px;
|
||||||
|
box-shadow: 0 12px 40px rgba(0, 0, 0, 0.5);
|
||||||
|
z-index: 100000;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
transition: transform 0.4s cubic-bezier(0.16, 1, 0.3, 1), opacity 0.4s;
|
||||||
|
opacity: 0;
|
||||||
|
color: white;
|
||||||
|
box-sizing: border-box;
|
||||||
|
}
|
||||||
|
|
||||||
|
.pwa-install-banner.show {
|
||||||
|
transform: translateX(-50%) translateY(0);
|
||||||
|
opacity: 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
.pwa-banner-content {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: space-between;
|
||||||
|
gap: 12px;
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.pwa-banner-logo {
|
||||||
|
width: 48px;
|
||||||
|
height: 48px;
|
||||||
|
border-radius: 12px;
|
||||||
|
box-shadow: 0 4px 10px rgba(0, 0, 0, 0.3);
|
||||||
|
flex-shrink: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.pwa-banner-text {
|
||||||
|
flex: 1;
|
||||||
|
text-align: left;
|
||||||
|
}
|
||||||
|
|
||||||
|
.pwa-banner-title {
|
||||||
|
font-weight: 700;
|
||||||
|
font-size: 1rem;
|
||||||
|
color: #fff;
|
||||||
|
}
|
||||||
|
|
||||||
|
.pwa-banner-desc {
|
||||||
|
font-size: 0.75rem;
|
||||||
|
color: rgba(255, 255, 255, 0.85);
|
||||||
|
margin-top: 2px;
|
||||||
|
line-height: 1.3;
|
||||||
|
}
|
||||||
|
|
||||||
|
.pwa-banner-actions {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 8px;
|
||||||
|
flex-shrink: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.pwa-banner-btn {
|
||||||
|
background: linear-gradient(135deg, #f7b835, #f5a623);
|
||||||
|
color: #10194b;
|
||||||
|
border: none;
|
||||||
|
padding: 8px 16px;
|
||||||
|
border-radius: 30px;
|
||||||
|
font-weight: 700;
|
||||||
|
font-size: 0.8rem;
|
||||||
|
cursor: pointer;
|
||||||
|
box-shadow: 0 4px 12px rgba(247, 184, 53, 0.3);
|
||||||
|
transition: transform 0.2s, box-shadow 0.2s, background 0.2s;
|
||||||
|
white-space: nowrap;
|
||||||
|
}
|
||||||
|
|
||||||
|
.pwa-banner-btn:hover {
|
||||||
|
background: linear-gradient(135deg, #ffd05c, #ffb633);
|
||||||
|
}
|
||||||
|
|
||||||
|
.pwa-banner-btn:active {
|
||||||
|
transform: scale(0.95);
|
||||||
|
}
|
||||||
|
|
||||||
|
.pwa-banner-close-btn {
|
||||||
|
background: rgba(255, 255, 255, 0.1);
|
||||||
|
border: none;
|
||||||
|
color: white;
|
||||||
|
width: 32px;
|
||||||
|
height: 32px;
|
||||||
|
border-radius: 50%;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
cursor: pointer;
|
||||||
|
transition: background 0.2s;
|
||||||
|
}
|
||||||
|
|
||||||
|
.pwa-banner-close-btn:hover {
|
||||||
|
background: rgba(255, 255, 255, 0.2);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Nav Link Install Button */
|
||||||
|
.pwa-install-btn-nav {
|
||||||
|
color: #f7b835 !important;
|
||||||
|
font-weight: 700;
|
||||||
|
border: 1px solid #f7b835;
|
||||||
|
padding: 4px 12px !important;
|
||||||
|
border-radius: 20px;
|
||||||
|
display: inline-flex !important;
|
||||||
|
align-items: center;
|
||||||
|
gap: 4px;
|
||||||
|
background: rgba(247, 184, 53, 0.1);
|
||||||
|
transition: all 0.3s ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
.pwa-install-btn-nav:hover {
|
||||||
|
background: #f7b835 !important;
|
||||||
|
color: #10194b !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* PWA Instructions Modal Overlay */
|
||||||
|
.pwa-modal-overlay {
|
||||||
|
position: fixed;
|
||||||
|
top: 0;
|
||||||
|
left: 0;
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
background: rgba(0, 0, 0, 0.7);
|
||||||
|
backdrop-filter: blur(8px);
|
||||||
|
-webkit-backdrop-filter: blur(8px);
|
||||||
|
z-index: 110000;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
opacity: 0;
|
||||||
|
transition: opacity 0.3s ease;
|
||||||
|
box-sizing: border-box;
|
||||||
|
}
|
||||||
|
|
||||||
|
.pwa-modal-overlay.show {
|
||||||
|
opacity: 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
.pwa-modal-content {
|
||||||
|
background: #18225c;
|
||||||
|
color: white;
|
||||||
|
width: 90%;
|
||||||
|
max-width: 420px;
|
||||||
|
border-radius: 24px;
|
||||||
|
border: 1px solid rgba(255, 255, 255, 0.15);
|
||||||
|
box-shadow: 0 20px 50px rgba(0, 0, 0, 0.5);
|
||||||
|
transform: scale(0.9);
|
||||||
|
transition: transform 0.3s cubic-bezier(0.34, 1.56, 0.64, 1);
|
||||||
|
overflow: hidden;
|
||||||
|
box-sizing: border-box;
|
||||||
|
}
|
||||||
|
|
||||||
|
.pwa-modal-overlay.show .pwa-modal-content {
|
||||||
|
transform: scale(1);
|
||||||
|
}
|
||||||
|
|
||||||
|
.pwa-modal-header {
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
align-items: center;
|
||||||
|
padding: 16px 20px;
|
||||||
|
border-bottom: 1px solid rgba(255, 255, 255, 0.08);
|
||||||
|
}
|
||||||
|
|
||||||
|
.pwa-modal-header h3 {
|
||||||
|
font-size: 1.1rem;
|
||||||
|
margin: 0;
|
||||||
|
font-weight: 700;
|
||||||
|
color: white;
|
||||||
|
}
|
||||||
|
|
||||||
|
.pwa-modal-close-btn-top {
|
||||||
|
background: none;
|
||||||
|
border: none;
|
||||||
|
color: rgba(255, 255, 255, 0.6);
|
||||||
|
cursor: pointer;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
padding: 0;
|
||||||
|
transition: color 0.2s;
|
||||||
|
}
|
||||||
|
|
||||||
|
.pwa-modal-close-btn-top:hover {
|
||||||
|
color: white;
|
||||||
|
}
|
||||||
|
|
||||||
|
.pwa-modal-body {
|
||||||
|
padding: 20px;
|
||||||
|
text-align: left;
|
||||||
|
}
|
||||||
|
|
||||||
|
.pwa-modal-app-info {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 12px;
|
||||||
|
margin-bottom: 20px;
|
||||||
|
border-bottom: 1px solid rgba(255, 255, 255, 0.08);
|
||||||
|
padding-bottom: 15px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.pwa-modal-logo {
|
||||||
|
width: 52px;
|
||||||
|
height: 52px;
|
||||||
|
border-radius: 12px;
|
||||||
|
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.3);
|
||||||
|
flex-shrink: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.pwa-modal-steps {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 16px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.pwa-step {
|
||||||
|
display: flex;
|
||||||
|
gap: 12px;
|
||||||
|
align-items: flex-start;
|
||||||
|
}
|
||||||
|
|
||||||
|
.pwa-step-num {
|
||||||
|
background: #f7b835;
|
||||||
|
color: #10194b;
|
||||||
|
width: 22px;
|
||||||
|
height: 22px;
|
||||||
|
border-radius: 50%;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
font-weight: 700;
|
||||||
|
font-size: 0.8rem;
|
||||||
|
flex-shrink: 0;
|
||||||
|
margin-top: 2px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.pwa-step-text {
|
||||||
|
font-size: 0.85rem;
|
||||||
|
line-height: 1.4;
|
||||||
|
color: rgba(255, 255, 255, 0.9);
|
||||||
|
}
|
||||||
|
|
||||||
|
.pwa-step-text strong {
|
||||||
|
color: #f7b835;
|
||||||
|
}
|
||||||
|
|
||||||
|
.pwa-inline-icon {
|
||||||
|
vertical-align: middle;
|
||||||
|
margin: 0 2px;
|
||||||
|
background: rgba(255, 255, 255, 0.12);
|
||||||
|
border-radius: 6px;
|
||||||
|
padding: 2px 4px;
|
||||||
|
display: inline-flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (max-width: 480px) {
|
||||||
|
.pwa-install-banner {
|
||||||
|
width: 95%;
|
||||||
|
bottom: 10px;
|
||||||
|
padding: 12px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.pwa-banner-content {
|
||||||
|
gap: 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.pwa-banner-desc {
|
||||||
|
font-size: 0.7rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.pwa-banner-btn {
|
||||||
|
padding: 6px 12px;
|
||||||
|
font-size: 0.75rem;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* OFFLINE SCREEN STYLES */
|
||||||
|
.offline-overlay {
|
||||||
|
position: fixed;
|
||||||
|
top: 0;
|
||||||
|
left: 0;
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
background: rgba(16, 25, 75, 0.96);
|
||||||
|
backdrop-filter: blur(20px);
|
||||||
|
-webkit-backdrop-filter: blur(20px);
|
||||||
|
z-index: 999999;
|
||||||
|
display: flex;
|
||||||
|
justify-content: center;
|
||||||
|
align-items: center;
|
||||||
|
opacity: 0;
|
||||||
|
visibility: hidden;
|
||||||
|
transition: opacity 0.5s ease, visibility 0.5s ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
.offline-overlay.active {
|
||||||
|
opacity: 1;
|
||||||
|
visibility: visible;
|
||||||
|
}
|
||||||
|
|
||||||
|
.offline-content {
|
||||||
|
text-align: center;
|
||||||
|
color: white;
|
||||||
|
padding: 40px;
|
||||||
|
max-width: 400px;
|
||||||
|
width: 90%;
|
||||||
|
font-family: 'Poppins', sans-serif;
|
||||||
|
}
|
||||||
|
|
||||||
|
.offline-icon {
|
||||||
|
font-size: 80px;
|
||||||
|
color: #ff5e5e;
|
||||||
|
margin-bottom: 24px;
|
||||||
|
animation: pulse-offline 2s infinite ease-in-out;
|
||||||
|
display: inline-block;
|
||||||
|
}
|
||||||
|
|
||||||
|
.offline-title {
|
||||||
|
font-size: 1.8rem;
|
||||||
|
font-weight: 700;
|
||||||
|
margin-bottom: 12px;
|
||||||
|
letter-spacing: -0.5px;
|
||||||
|
color: white;
|
||||||
|
}
|
||||||
|
|
||||||
|
.offline-desc {
|
||||||
|
font-size: 1rem;
|
||||||
|
color: rgba(255, 255, 255, 0.7);
|
||||||
|
margin-bottom: 30px;
|
||||||
|
line-height: 1.5;
|
||||||
|
}
|
||||||
|
|
||||||
|
.offline-btn {
|
||||||
|
background: linear-gradient(135deg, #f7b835, #f5a623);
|
||||||
|
color: #10194b;
|
||||||
|
border: none;
|
||||||
|
padding: 12px 30px;
|
||||||
|
font-size: 0.95rem;
|
||||||
|
font-weight: 700;
|
||||||
|
border-radius: 50px;
|
||||||
|
cursor: pointer;
|
||||||
|
box-shadow: 0 4px 15px rgba(245, 166, 35, 0.4);
|
||||||
|
transition: transform 0.2s, box-shadow 0.2s;
|
||||||
|
display: inline-flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
gap: 8px;
|
||||||
|
text-decoration: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.offline-btn:hover {
|
||||||
|
transform: translateY(-2px);
|
||||||
|
box-shadow: 0 6px 20px rgba(245, 166, 35, 0.6);
|
||||||
|
color: #10194b;
|
||||||
|
}
|
||||||
|
|
||||||
|
.offline-btn:active {
|
||||||
|
transform: translateY(0);
|
||||||
|
}
|
||||||
|
|
||||||
|
.offline-btn:disabled {
|
||||||
|
opacity: 0.8;
|
||||||
|
cursor: not-allowed;
|
||||||
|
transform: none;
|
||||||
|
box-shadow: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes pulse-offline {
|
||||||
|
0% { transform: scale(1); opacity: 0.8; }
|
||||||
|
50% { transform: scale(1.08); opacity: 1; }
|
||||||
|
100% { transform: scale(1); opacity: 0.8; }
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes spin {
|
||||||
|
0% { transform: rotate(0deg); }
|
||||||
|
100% { transform: rotate(360deg); }
|
||||||
|
}
|
||||||
|
|
||||||
|
.spinner-offline {
|
||||||
|
display: inline-block;
|
||||||
|
}
|
||||||
@@ -2,6 +2,15 @@
|
|||||||
|
|
||||||
<changelog>
|
<changelog>
|
||||||
|
|
||||||
|
<version>
|
||||||
|
<number>2.6.0</number>
|
||||||
|
<logs>
|
||||||
|
<log>Aggiornato il sistema di gestione della visualizzazione delle pagine.</log>
|
||||||
|
<log>Aggiunto il pulsante "guida" per il download dell'applicazione su iOS e Android.</log>
|
||||||
|
<log>Correzione e bugfix di problematiche varie.</log>
|
||||||
|
</logs>
|
||||||
|
</version>
|
||||||
|
|
||||||
<version>
|
<version>
|
||||||
<number>2.5.1 (a)</number>
|
<number>2.5.1 (a)</number>
|
||||||
<logs>
|
<logs>
|
||||||
|
|||||||
@@ -1043,12 +1043,76 @@ document.addEventListener('DOMContentLoaded', function () {
|
|||||||
if (homeNav) homeNav.style.display = 'block';
|
if (homeNav) homeNav.style.display = 'block';
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gestione dello stato Offline
|
||||||
|
*/
|
||||||
|
function initOfflineDetection() {
|
||||||
|
let offlineOverlay = document.getElementById('offlineOverlay');
|
||||||
|
if (!offlineOverlay) {
|
||||||
|
offlineOverlay = document.createElement('div');
|
||||||
|
offlineOverlay.id = 'offlineOverlay';
|
||||||
|
offlineOverlay.className = 'offline-overlay';
|
||||||
|
offlineOverlay.innerHTML = `
|
||||||
|
<div class="offline-content">
|
||||||
|
<span class="material-icons offline-icon">wifi_off</span>
|
||||||
|
<h2 class="offline-title">Nessuna Connessione</h2>
|
||||||
|
<p class="offline-desc">Sembra che tu sia offline. Controlla la tua connessione internet per continuare ad ascoltare RPIGroup Play.</p>
|
||||||
|
<button class="offline-btn" id="offlineRetryBtn">
|
||||||
|
<span class="material-icons">refresh</span> Riprova
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
`;
|
||||||
|
document.body.appendChild(offlineOverlay);
|
||||||
|
|
||||||
|
const retryBtn = document.getElementById('offlineRetryBtn');
|
||||||
|
if (retryBtn) {
|
||||||
|
retryBtn.addEventListener('click', function() {
|
||||||
|
retryBtn.disabled = true;
|
||||||
|
retryBtn.innerHTML = '<span class="material-icons spinner-offline" style="animation: spin 1s infinite linear; vertical-align: middle;">sync</span> Verifica in corso...';
|
||||||
|
|
||||||
|
fetch(BASE_PATH + '/robots.txt', { cache: 'no-store', mode: 'no-cors' })
|
||||||
|
.then(() => {
|
||||||
|
offlineOverlay.classList.remove('active');
|
||||||
|
window.location.reload();
|
||||||
|
})
|
||||||
|
.catch(() => {
|
||||||
|
setTimeout(() => {
|
||||||
|
retryBtn.disabled = false;
|
||||||
|
retryBtn.innerHTML = '<span class="material-icons">refresh</span> Riprova';
|
||||||
|
}, 1000);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function showOfflineOverlay() {
|
||||||
|
offlineOverlay.classList.add('active');
|
||||||
|
}
|
||||||
|
|
||||||
|
function hideOfflineOverlay() {
|
||||||
|
offlineOverlay.classList.remove('active');
|
||||||
|
}
|
||||||
|
|
||||||
|
window.addEventListener('offline', showOfflineOverlay);
|
||||||
|
window.addEventListener('online', () => {
|
||||||
|
hideOfflineOverlay();
|
||||||
|
console.log('Dispositivo tornato online. Ripristino...');
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!navigator.onLine) {
|
||||||
|
showOfflineOverlay();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Inizializzazione principale
|
* Inizializzazione principale
|
||||||
*/
|
*/
|
||||||
function init() {
|
function init() {
|
||||||
console.log('Inizializzazione app...');
|
console.log('Inizializzazione app...');
|
||||||
|
|
||||||
|
// Rilevamento stato offline
|
||||||
|
initOfflineDetection();
|
||||||
|
|
||||||
setupOpenAppButton();
|
setupOpenAppButton();
|
||||||
|
|
||||||
if (document.querySelector('.container') || document.querySelector('.container-fluid')) {
|
if (document.querySelector('.container') || document.querySelector('.container-fluid')) {
|
||||||
|
|||||||
@@ -0,0 +1,236 @@
|
|||||||
|
/**
|
||||||
|
* PWA Install and Service Worker Helper
|
||||||
|
* Powered by Antigravity AI
|
||||||
|
*/
|
||||||
|
|
||||||
|
(function () {
|
||||||
|
const BASE_PATH = window.BASE_PATH || '';
|
||||||
|
|
||||||
|
// Register Service Worker
|
||||||
|
if ('serviceWorker' in navigator) {
|
||||||
|
window.addEventListener('load', () => {
|
||||||
|
const swUrl = `${BASE_PATH}/sw.js`;
|
||||||
|
navigator.serviceWorker.register(swUrl)
|
||||||
|
.then((registration) => {
|
||||||
|
console.log('[PWA] Service Worker registrato con successo:', registration.scope);
|
||||||
|
})
|
||||||
|
.catch((error) => {
|
||||||
|
console.error('[PWA] Registrazione Service Worker fallita:', error);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// Detectors
|
||||||
|
const isStandalone = window.matchMedia('(display-mode: standalone)').matches || window.navigator.standalone === true;
|
||||||
|
const isIOS = /iPad|iPhone|iPod/.test(navigator.userAgent) && !window.MSStream;
|
||||||
|
const isMobile = /Android|webOS|iPhone|iPad|iPod|BlackBerry|IEMobile|Opera Mini/i.test(navigator.userAgent);
|
||||||
|
|
||||||
|
// Se l'app viene aperta in modalità standalone (PWA) su desktop, reindirizza all'interno dell'app
|
||||||
|
if (isStandalone && !isMobile) {
|
||||||
|
const urlParams = new URLSearchParams(window.location.search);
|
||||||
|
if (!urlParams.has('app') || urlParams.get('app') !== 'true') {
|
||||||
|
const currentPath = window.location.pathname;
|
||||||
|
const pathWithoutBase = currentPath.replace(BASE_PATH, '').replace(/^\//, '');
|
||||||
|
const redirectParam = pathWithoutBase ? '&redirect=' + encodeURIComponent(pathWithoutBase) : '';
|
||||||
|
window.location.href = BASE_PATH + '/?app=true' + redirectParam;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Global variables
|
||||||
|
window.deferredPrompt = null;
|
||||||
|
|
||||||
|
// Check if banner was dismissed in the last 7 days
|
||||||
|
function isBannerDismissed() {
|
||||||
|
const dismissedTime = localStorage.getItem('pwa-banner-dismissed');
|
||||||
|
if (!dismissedTime) return false;
|
||||||
|
|
||||||
|
const sevenDays = 7 * 24 * 60 * 60 * 1000;
|
||||||
|
return (Date.now() - parseInt(dismissedTime, 10)) < sevenDays;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Dismiss banner
|
||||||
|
function dismissBanner() {
|
||||||
|
localStorage.setItem('pwa-banner-dismissed', Date.now().toString());
|
||||||
|
const banner = document.getElementById('pwa-install-banner');
|
||||||
|
if (banner) {
|
||||||
|
banner.classList.remove('show');
|
||||||
|
setTimeout(() => {
|
||||||
|
banner.style.display = 'none';
|
||||||
|
}, 400);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Initialize UI components and listeners
|
||||||
|
function initPwaUI() {
|
||||||
|
// If already in standalone mode, hide all promotional components
|
||||||
|
if (isStandalone) {
|
||||||
|
console.log('[PWA] Applicazione avviata in modalità Standalone. Nascondo elementi PWA.');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const headerBtn = document.getElementById('pwa-header-install-btn');
|
||||||
|
const desktopBtn = document.getElementById('desktopPwaInstallBtn');
|
||||||
|
const banner = document.getElementById('pwa-install-banner');
|
||||||
|
const bannerInstallBtn = document.getElementById('pwa-banner-install-btn');
|
||||||
|
const bannerCloseBtn = document.getElementById('pwa-banner-close-btn');
|
||||||
|
|
||||||
|
// On iOS, show the installation buttons immediately (as they don't support beforeinstallprompt)
|
||||||
|
if (isIOS) {
|
||||||
|
console.log('[PWA] Rilevato dispositivo iOS');
|
||||||
|
if (headerBtn) headerBtn.style.display = 'inline-flex';
|
||||||
|
if (desktopBtn) desktopBtn.style.display = 'inline-flex';
|
||||||
|
|
||||||
|
if (banner && !isBannerDismissed()) {
|
||||||
|
banner.style.display = 'flex';
|
||||||
|
setTimeout(() => banner.classList.add('show'), 100);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Attach click listeners to all PWA installation buttons
|
||||||
|
const triggerInstall = (e) => {
|
||||||
|
e.preventDefault();
|
||||||
|
|
||||||
|
if (window.deferredPrompt) {
|
||||||
|
// Show native install prompt (Android / Desktop Chrome / Edge)
|
||||||
|
window.deferredPrompt.prompt();
|
||||||
|
window.deferredPrompt.userChoice.then((choiceResult) => {
|
||||||
|
if (choiceResult.outcome === 'accepted') {
|
||||||
|
console.log('[PWA] Utente ha installato la PWA');
|
||||||
|
dismissBanner();
|
||||||
|
} else {
|
||||||
|
console.log('[PWA] Utente ha annullato l\'installazione');
|
||||||
|
}
|
||||||
|
window.deferredPrompt = null;
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
// Fallback for iOS Safari or other unsupported prompts
|
||||||
|
showInstructionsModal();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
if (headerBtn) headerBtn.addEventListener('click', triggerInstall);
|
||||||
|
if (desktopBtn) desktopBtn.addEventListener('click', triggerInstall);
|
||||||
|
if (bannerInstallBtn) bannerInstallBtn.addEventListener('click', triggerInstall);
|
||||||
|
|
||||||
|
if (bannerCloseBtn) {
|
||||||
|
bannerCloseBtn.addEventListener('click', (e) => {
|
||||||
|
e.preventDefault();
|
||||||
|
dismissBanner();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// Modal Close logic
|
||||||
|
const modal = document.getElementById('pwa-instructions-modal');
|
||||||
|
const modalCloseBtnTop = document.getElementById('pwa-modal-close-btn');
|
||||||
|
|
||||||
|
const closeModal = () => {
|
||||||
|
if (modal) {
|
||||||
|
modal.classList.remove('show');
|
||||||
|
setTimeout(() => {
|
||||||
|
modal.style.display = 'none';
|
||||||
|
}, 300);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
if (modalCloseBtnTop) modalCloseBtnTop.addEventListener('click', closeModal);
|
||||||
|
if (modal) {
|
||||||
|
modal.addEventListener('click', (e) => {
|
||||||
|
if (e.target === modal) closeModal();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Listen for beforeinstallprompt (Chrome / Android / Edge)
|
||||||
|
window.addEventListener('beforeinstallprompt', (e) => {
|
||||||
|
// Prevent Chrome 67 and earlier from automatically showing the prompt
|
||||||
|
e.preventDefault();
|
||||||
|
|
||||||
|
// Se siamo su desktop, non salviamo il prompt e non mostriamo nessun banner promozionale PWA
|
||||||
|
if (!isMobile) {
|
||||||
|
console.log('[PWA] Installazione via browser intercettata su desktop. Ignorata per requisiti.');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Stash the event so it can be triggered later.
|
||||||
|
window.deferredPrompt = e;
|
||||||
|
|
||||||
|
console.log('[PWA] Evento beforeinstallprompt intercettato.');
|
||||||
|
|
||||||
|
// Show custom buttons and banner (if not dismissed)
|
||||||
|
const headerBtn = document.getElementById('pwa-header-install-btn');
|
||||||
|
const desktopBtn = document.getElementById('desktopPwaInstallBtn');
|
||||||
|
const banner = document.getElementById('pwa-install-banner');
|
||||||
|
|
||||||
|
if (headerBtn) headerBtn.style.display = 'inline-flex';
|
||||||
|
if (desktopBtn) desktopBtn.style.display = 'inline-flex';
|
||||||
|
|
||||||
|
if (banner && !isBannerDismissed()) {
|
||||||
|
banner.style.display = 'flex';
|
||||||
|
setTimeout(() => banner.classList.add('show'), 100);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// Track successful installation
|
||||||
|
window.addEventListener('appinstalled', () => {
|
||||||
|
console.log('[PWA] L\'applicazione è stata installata con successo.');
|
||||||
|
window.deferredPrompt = null;
|
||||||
|
|
||||||
|
// Hide all promotional components
|
||||||
|
const headerBtn = document.getElementById('pwa-header-install-btn');
|
||||||
|
const desktopBtn = document.getElementById('desktopPwaInstallBtn');
|
||||||
|
const banner = document.getElementById('pwa-install-banner');
|
||||||
|
|
||||||
|
if (headerBtn) headerBtn.style.display = 'none';
|
||||||
|
if (desktopBtn) desktopBtn.style.display = 'none';
|
||||||
|
if (banner) banner.style.display = 'none';
|
||||||
|
});
|
||||||
|
|
||||||
|
// Show instructions modal (iOS / Fallback)
|
||||||
|
function showInstructionsModal() {
|
||||||
|
const modal = document.getElementById('pwa-instructions-modal');
|
||||||
|
const content = document.getElementById('pwa-instructions-content');
|
||||||
|
if (!modal || !content) return;
|
||||||
|
|
||||||
|
if (isIOS) {
|
||||||
|
content.innerHTML = `
|
||||||
|
<div class="pwa-step">
|
||||||
|
<div class="pwa-step-num">1</div>
|
||||||
|
<div class="pwa-step-text">Tocca l'icona di <strong>Condividi</strong> <span class="pwa-inline-icon"><i class="material-icons" style="font-size:18px; color:#f7b835;">share</i></span> (il quadrato con la freccia verso l'alto) nella barra in basso di Safari.</div>
|
||||||
|
</div>
|
||||||
|
<div class="pwa-step">
|
||||||
|
<div class="pwa-step-num">2</div>
|
||||||
|
<div class="pwa-step-text">Scorri l'elenco delle opzioni verso il basso e seleziona <strong>Aggiungi alla schermata Home</strong> <span class="pwa-inline-icon"><i class="material-icons" style="font-size:18px; color:#f7b835;">add_box</i></span>.</div>
|
||||||
|
</div>
|
||||||
|
<div class="pwa-step">
|
||||||
|
<div class="pwa-step-num">3</div>
|
||||||
|
<div class="pwa-step-text">Tocca <strong>Aggiungi</strong> nell'angolo in alto a destra per inserire l'icona sul tuo telefono.</div>
|
||||||
|
</div>
|
||||||
|
`;
|
||||||
|
} else {
|
||||||
|
content.innerHTML = `
|
||||||
|
<div class="pwa-step">
|
||||||
|
<div class="pwa-step-num">1</div>
|
||||||
|
<div class="pwa-step-text">Apri il menu delle impostazioni del browser toccando i <strong>tre puntini</strong> <span class="pwa-inline-icon"><i class="material-icons" style="font-size:18px; color:#f7b835;">more_vert</i></span> in alto a destra.</div>
|
||||||
|
</div>
|
||||||
|
<div class="pwa-step">
|
||||||
|
<div class="pwa-step-num">2</div>
|
||||||
|
<div class="pwa-step-text">Seleziona la voce <strong>Installa applicazione</strong> o <strong>Aggiungi a schermata Home</strong>.</div>
|
||||||
|
</div>
|
||||||
|
<div class="pwa-step">
|
||||||
|
<div class="pwa-step-num">3</div>
|
||||||
|
<div class="pwa-step-text">Conferma per scaricare la PWA sul tuo telefono.</div>
|
||||||
|
</div>
|
||||||
|
`;
|
||||||
|
}
|
||||||
|
|
||||||
|
modal.style.display = 'flex';
|
||||||
|
setTimeout(() => modal.classList.add('show'), 10);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Run on DOM loaded
|
||||||
|
if (document.readyState === 'loading') {
|
||||||
|
document.addEventListener('DOMContentLoaded', initPwaUI);
|
||||||
|
} else {
|
||||||
|
initPwaUI();
|
||||||
|
}
|
||||||
|
})();
|
||||||
+53
-67
@@ -7,7 +7,7 @@
|
|||||||
<div class="logo-section">
|
<div class="logo-section">
|
||||||
<img src="<?=$base_path?>/img/RpiGroupPlayWHITE.png" alt="Logo">
|
<img src="<?=$base_path?>/img/RpiGroupPlayWHITE.png" alt="Logo">
|
||||||
</div>
|
</div>
|
||||||
<div class="menu-section">
|
<div class="menu-section" style="display: flex; justify-content: center; align-items: center; flex-wrap: wrap; gap: 5px;">
|
||||||
<a href="<?php echo $base_path; ?>/" data-page="home" class="navLink <?php echo $page == 'home' ? 'active' : ''; ?>">Home</a>
|
<a href="<?php echo $base_path; ?>/" data-page="home" class="navLink <?php echo $page == 'home' ? 'active' : ''; ?>">Home</a>
|
||||||
<a href="<?php echo $base_path; ?>/radio" data-page="radio" class="navLink <?php echo ($page == 'radio' || $page == 'play') ? 'active' : ''; ?>">Radio</a>
|
<a href="<?php echo $base_path; ?>/radio" data-page="radio" class="navLink <?php echo ($page == 'radio' || $page == 'play') ? 'active' : ''; ?>">Radio</a>
|
||||||
<a href="<?php echo $base_path; ?>/tv" data-page="tv" class="navLink <?php echo ($page == 'tv' || $page == 'playtv') ? 'active' : ''; ?>">TV</a>
|
<a href="<?php echo $base_path; ?>/tv" data-page="tv" class="navLink <?php echo ($page == 'tv' || $page == 'playtv') ? 'active' : ''; ?>">TV</a>
|
||||||
@@ -17,79 +17,24 @@
|
|||||||
|
|
||||||
<main class="container-fluid" id="content">
|
<main class="container-fluid" id="content">
|
||||||
<?php
|
<?php
|
||||||
// Carica il contenuto iniziale in base all'URL
|
$filePath = getPageFilePath($page, $param);
|
||||||
switch ($page) {
|
|
||||||
case 'home':
|
// Validazione specifica delle stazioni radio e tv per sicurezza
|
||||||
include './pages/page/home.php';
|
if ($page === 'play' && !empty($param)) {
|
||||||
break;
|
|
||||||
case 'radio':
|
|
||||||
include './pages/page/radio.php';
|
|
||||||
break;
|
|
||||||
case 'tv':
|
|
||||||
include './pages/page/tv.php';
|
|
||||||
break;
|
|
||||||
case 'play':
|
|
||||||
if (!empty($param)) {
|
|
||||||
$stationId = (int)$param;
|
$stationId = (int)$param;
|
||||||
$station = getRadioStation($stationId);
|
$station = getRadioStation($stationId);
|
||||||
if ($station !== null) {
|
if ($station === null) {
|
||||||
include './pages/page/player.php';
|
$filePath = './pages/page/404.php';
|
||||||
} else {
|
|
||||||
include './pages/page/404.php';
|
|
||||||
}
|
}
|
||||||
} else {
|
} elseif ($page === 'playtv' && !empty($param)) {
|
||||||
// Se non c'è ID, torna alla home
|
|
||||||
include './pages/page/home.php';
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
case 'playtv':
|
|
||||||
if (!empty($param)) {
|
|
||||||
$stationId = (int)$param;
|
$stationId = (int)$param;
|
||||||
$station = getTVStation($stationId);
|
$station = getTVStation($stationId);
|
||||||
if ($station !== null) {
|
if ($station === null) {
|
||||||
include './pages/page/player_tv.php';
|
$filePath = './pages/page/404.php';
|
||||||
} else {
|
|
||||||
include './pages/page/404.php';
|
|
||||||
}
|
}
|
||||||
} else {
|
|
||||||
// Se non c'è ID, torna alla home
|
|
||||||
include './pages/page/home.php';
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
case 'podcast':
|
|
||||||
include './pages/page/podcast.php';
|
|
||||||
break;
|
|
||||||
case 'page':
|
|
||||||
if (!empty($param)) {
|
|
||||||
switch ($param) {
|
|
||||||
case 'about':
|
|
||||||
include './pages/page/about.php';
|
|
||||||
break;
|
|
||||||
case 'copyright':
|
|
||||||
include './pages/page/copyright.php';
|
|
||||||
break;
|
|
||||||
case 'contact':
|
|
||||||
include './pages/page/contact.php';
|
|
||||||
break;
|
|
||||||
case 'termini-condizioni':
|
|
||||||
include './pages/page/terminicondizioni.php';
|
|
||||||
break;
|
|
||||||
case 'policy-privacy':
|
|
||||||
include './pages/page/policyprivacy.php';
|
|
||||||
break;
|
|
||||||
case 'changelog':
|
|
||||||
include './pages/page/changelog.php';
|
|
||||||
break;
|
|
||||||
default:
|
|
||||||
include './pages/page/404.php';
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
include './pages/page/404.php';
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
default:
|
|
||||||
include './pages/page/home.php';
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
include $filePath;
|
||||||
?>
|
?>
|
||||||
</main>
|
</main>
|
||||||
|
|
||||||
@@ -109,3 +54,44 @@
|
|||||||
</div>
|
</div>
|
||||||
<div class="copyright-section" <?php if($is_mobile){ ?> style="padding: 10px 0 25px;" <?php } ?>>© 2025 RPIGroup • Versione: <?php echo $version_app; ?></div>
|
<div class="copyright-section" <?php if($is_mobile){ ?> style="padding: 10px 0 25px;" <?php } ?>>© 2025 RPIGroup • Versione: <?php echo $version_app; ?></div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<!-- PWA Install Banner (Mobile view bottom prompt) -->
|
||||||
|
<div id="pwa-install-banner" class="pwa-install-banner" style="display: none;">
|
||||||
|
<div class="pwa-banner-content">
|
||||||
|
<img src="<?=$base_path?>/img/logoapp.png" alt="App Logo" class="pwa-banner-logo">
|
||||||
|
<div class="pwa-banner-text">
|
||||||
|
<div class="pwa-banner-title">RPIGroup Play</div>
|
||||||
|
<div class="pwa-banner-desc">Installa l'app per ascoltare musica ovunque e a schermo intero!</div>
|
||||||
|
</div>
|
||||||
|
<div class="pwa-banner-actions">
|
||||||
|
<button id="pwa-banner-install-btn" class="pwa-banner-btn">SCARICA APP</button>
|
||||||
|
<button id="pwa-banner-close-btn" class="pwa-banner-close-btn">
|
||||||
|
<i class="material-icons" style="font-size: 18px; vertical-align: middle;">close</i>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- PWA Instructions Modal (iOS Safari & Fallback) -->
|
||||||
|
<div id="pwa-instructions-modal" class="pwa-modal-overlay" style="display: none;">
|
||||||
|
<div class="pwa-modal-content">
|
||||||
|
<div class="pwa-modal-header">
|
||||||
|
<h3>Installa l'applicazione</h3>
|
||||||
|
<button id="pwa-modal-close-btn" class="pwa-modal-close-btn-top">
|
||||||
|
<i class="material-icons">close</i>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
<div class="pwa-modal-body">
|
||||||
|
<div class="pwa-modal-app-info">
|
||||||
|
<img src="<?=$base_path?>/img/logoapp.png" alt="App Logo" class="pwa-modal-logo">
|
||||||
|
<div>
|
||||||
|
<h4 style="color: white; font-weight: 700; margin: 0;">RPIGroup Play</h4>
|
||||||
|
<p style="color: rgba(255,255,255,0.7); font-size: 0.8rem; margin: 2px 0 0;">Ascolta le tue stazioni radio preferite</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div id="pwa-instructions-content" class="pwa-modal-steps">
|
||||||
|
<!-- Will be populated dynamically by pwa-install.js -->
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
@@ -1,4 +1,5 @@
|
|||||||
|
|
||||||
|
<script src="<?php echo $base_path; ?>/js/pwa-install.js?v=<?=time()?>"></script>
|
||||||
<script src="<?php echo $base_path; ?>/js/app.js?v=<?=time()?>"></script>
|
<script src="<?php echo $base_path; ?>/js/app.js?v=<?=time()?>"></script>
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
|
|||||||
+4
-2
@@ -25,10 +25,12 @@ header('Referrer-Policy: strict-origin-when-cross-origin');
|
|||||||
<meta name="mobile-web-app-capable" content="yes">
|
<meta name="mobile-web-app-capable" content="yes">
|
||||||
<meta name="application-name" content="<?php echo $title_site; ?>">
|
<meta name="application-name" content="<?php echo $title_site; ?>">
|
||||||
<meta name="apple-mobile-web-app-capable" content="yes">
|
<meta name="apple-mobile-web-app-capable" content="yes">
|
||||||
<meta name="apple-mobile-web-app-status-bar-style" content="black">
|
<meta name="apple-mobile-web-app-status-bar-style" content="black-translucent">
|
||||||
<meta name="apple-mobile-web-app-title" content="<?php echo $title_site; ?>">
|
<meta name="apple-mobile-web-app-title" content="<?php echo $title_site; ?>">
|
||||||
<meta name="theme-color" content="#2a377e">
|
<meta name="theme-color" content="#2a377e">
|
||||||
<meta name="apple-mobile-web-app-status-bar-style" content="#2a377e">
|
<link rel="apple-touch-icon" href="<?=$base_path?>/img/logoapp.png">
|
||||||
|
<link rel="apple-touch-icon" sizes="152x152" href="<?=$base_path?>/img/icons/icon-152x152.png">
|
||||||
|
<link rel="apple-touch-icon" sizes="192x192" href="<?=$base_path?>/img/icons/icon-192x192.png">
|
||||||
<meta name="screen-orientation" content="portrait">
|
<meta name="screen-orientation" content="portrait">
|
||||||
<meta name="x5-orientation" content="portrait">
|
<meta name="x5-orientation" content="portrait">
|
||||||
<meta name="x5-fullscreen" content="true">
|
<meta name="x5-fullscreen" content="true">
|
||||||
|
|||||||
@@ -0,0 +1,99 @@
|
|||||||
|
const CACHE_NAME = 'rpigroupplay-v2';
|
||||||
|
const ASSETS = [
|
||||||
|
'./',
|
||||||
|
'./index.php',
|
||||||
|
'./manifest.json',
|
||||||
|
'./css/style.css',
|
||||||
|
'./css/bootstrap.css',
|
||||||
|
'./css/animation.css',
|
||||||
|
'./js/app.js',
|
||||||
|
'./js/bootstrap.js',
|
||||||
|
'./img/RpiGroupPlayWHITE.png',
|
||||||
|
'./img/logoapp.png'
|
||||||
|
];
|
||||||
|
|
||||||
|
// Install Event
|
||||||
|
self.addEventListener('install', (e) => {
|
||||||
|
e.waitUntil(
|
||||||
|
caches.open(CACHE_NAME)
|
||||||
|
.then((cache) => {
|
||||||
|
console.log('[Service Worker] Caching static shell');
|
||||||
|
return cache.addAll(ASSETS).catch(err => {
|
||||||
|
console.warn('[Service Worker] Asset caching warning:', err);
|
||||||
|
});
|
||||||
|
})
|
||||||
|
.then(() => self.skipWaiting())
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
// Activate Event
|
||||||
|
self.addEventListener('activate', (e) => {
|
||||||
|
e.waitUntil(
|
||||||
|
caches.keys().then((keys) => {
|
||||||
|
return Promise.all(
|
||||||
|
keys.map((key) => {
|
||||||
|
if (key !== CACHE_NAME) {
|
||||||
|
console.log('[Service Worker] Removing old cache', key);
|
||||||
|
return caches.delete(key);
|
||||||
|
}
|
||||||
|
})
|
||||||
|
);
|
||||||
|
}).then(() => self.clients.claim())
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
// Fetch Event
|
||||||
|
self.addEventListener('fetch', (e) => {
|
||||||
|
const url = e.request.url;
|
||||||
|
|
||||||
|
// Skip non-GET requests
|
||||||
|
if (e.request.method !== 'GET') {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Bypass streaming content, APIs, and external domains (like radio/tv streams)
|
||||||
|
if (
|
||||||
|
url.includes('stream') ||
|
||||||
|
url.includes('.mp3') ||
|
||||||
|
url.includes('.aac') ||
|
||||||
|
url.includes('.m3u8') ||
|
||||||
|
url.includes('.ts') ||
|
||||||
|
url.includes('icecast') ||
|
||||||
|
url.includes('shoutcast') ||
|
||||||
|
url.includes('radiocitta105') ||
|
||||||
|
url.includes('radiodiffusionelibera') ||
|
||||||
|
!url.startsWith(self.location.origin)
|
||||||
|
) {
|
||||||
|
return; // Let the browser handle standard streaming/external fetch requests directly
|
||||||
|
}
|
||||||
|
|
||||||
|
e.respondWith(
|
||||||
|
caches.match(e.request).then((cachedResponse) => {
|
||||||
|
if (cachedResponse) {
|
||||||
|
// Return cached resource immediately and fetch fresh copy in background (Stale-While-Revalidate)
|
||||||
|
fetch(e.request).then((networkResponse) => {
|
||||||
|
if (networkResponse.status === 200) {
|
||||||
|
caches.open(CACHE_NAME).then((cache) => {
|
||||||
|
cache.put(e.request, networkResponse);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}).catch(() => {
|
||||||
|
// Silent catch network failures
|
||||||
|
});
|
||||||
|
return cachedResponse;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Fallback to network
|
||||||
|
return fetch(e.request).then((response) => {
|
||||||
|
// Cache standard successful requests
|
||||||
|
if (response.status === 200) {
|
||||||
|
const responseClone = response.clone();
|
||||||
|
caches.open(CACHE_NAME).then((cache) => {
|
||||||
|
cache.put(e.request, responseClone);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
return response;
|
||||||
|
});
|
||||||
|
})
|
||||||
|
);
|
||||||
|
});
|
||||||
Reference in New Issue
Block a user