diff --git a/assets/js/app.js b/assets/js/app.js index cb6d96e..cfbe96f 100644 --- a/assets/js/app.js +++ b/assets/js/app.js @@ -2053,6 +2053,9 @@ async function loadSettingsUI() { if (scaleKiosk) scaleKiosk.style.display = ''; // Auto-set URL to localhost gateway (always port 8765 in kiosk) if (scaleUrlUiEl && !scaleUrlUiEl.value) scaleUrlUiEl.value = 'ws://localhost:8765'; + // Show kiosk self-update panel + const updatePanel = document.getElementById('kiosk-update-panel'); + if (updatePanel) updatePanel.style.display = ''; } } @@ -2069,6 +2072,83 @@ function _kioskReconfigureScale() { } } +// ── Kiosk: manual update check ──────────────────────────────────────── +let _kioskPendingApkUrl = ''; + +/** Called by Kotlin with JSON: { has_update, current, latest, apk_url, error } */ +window._kioskUpdateResult = function(result) { + const btn = document.getElementById('btn-kiosk-check-update'); + const status = document.getElementById('kiosk-update-status'); + const installBtn = document.getElementById('btn-kiosk-install-update'); + const verLabel = document.getElementById('kiosk-update-version-label'); + if (!status) return; + + if (btn) { btn.disabled = false; btn.textContent = '🔍 Cerca aggiornamenti'; } + + if (result.error && !result.has_update) { + status.style.display = ''; + status.style.background = 'rgba(239,68,68,0.1)'; + status.style.border = '1px solid rgba(239,68,68,0.3)'; + status.style.color = ''; + status.innerHTML = `❌ Errore: ${result.error}`; + return; + } + + const current = result.current || '?'; + const latest = result.latest || '?'; + if (verLabel) verLabel.textContent = `Installata: ${current}`; + + if (result.has_update) { + _kioskPendingApkUrl = result.apk_url || ''; + status.style.display = ''; + status.style.background = 'rgba(245,158,11,0.1)'; + status.style.border = '1px solid rgba(245,158,11,0.35)'; + status.style.color = ''; + status.innerHTML = `⬆️ Nuova versione disponibile: ${latest} (installata: ${current})`; + if (installBtn) installBtn.style.display = ''; + } else { + _kioskPendingApkUrl = ''; + status.style.display = ''; + status.style.background = 'rgba(52,211,153,0.1)'; + status.style.border = '1px solid rgba(52,211,153,0.3)'; + status.style.color = ''; + status.innerHTML = `✅ Sei già aggiornato — versione ${current}`; + if (installBtn) installBtn.style.display = 'none'; + } +}; + +function _kioskCheckForUpdates() { + if (typeof _kioskBridge === 'undefined' || typeof _kioskBridge.checkForUpdates !== 'function') { + showToast('⚠️ Questa funzione richiede il kiosk nativo', 'warning'); + return; + } + const btn = document.getElementById('btn-kiosk-check-update'); + const status = document.getElementById('kiosk-update-status'); + const installBtn = document.getElementById('btn-kiosk-install-update'); + if (btn) { btn.disabled = true; btn.textContent = '⏳ Controllo…'; } + if (status) { status.style.display = 'none'; } + if (installBtn) { installBtn.style.display = 'none'; } + _kioskPendingApkUrl = ''; + try { _kioskBridge.checkForUpdates(); } catch(e) { + if (btn) { btn.disabled = false; btn.textContent = '🔍 Cerca aggiornamenti'; } + showToast('❌ Errore durante il controllo', 'error'); + } +} + +function _kioskInstallUpdate() { + if (!_kioskPendingApkUrl) return; + if (typeof _kioskBridge === 'undefined' || typeof _kioskBridge.installUpdate !== 'function') { + showToast('⚠️ Aggiorna il kiosk per usare questa funzione', 'warning'); + return; + } + const installBtn = document.getElementById('btn-kiosk-install-update'); + if (installBtn) { installBtn.disabled = true; installBtn.textContent = '⏳ Avvio download…'; } + try { _kioskBridge.installUpdate(_kioskPendingApkUrl); } catch(e) { + if (installBtn) { installBtn.disabled = false; installBtn.textContent = '⬇️ Installa aggiornamento'; } + showToast('❌ Errore avvio installazione', 'error'); + } +} + // ── Kiosk overlay: X (close) + ↻ (refresh) buttons ─────────────────── // Injected into #header-left (left zone of the 3-column header). // Only shown when _kioskBridge JS interface is available (Android WebView). diff --git a/evershelf-kiosk/app/build.gradle.kts b/evershelf-kiosk/app/build.gradle.kts index 4ca0eb6..edc9edd 100644 --- a/evershelf-kiosk/app/build.gradle.kts +++ b/evershelf-kiosk/app/build.gradle.kts @@ -11,8 +11,8 @@ android { applicationId = "it.dadaloop.evershelf.kiosk" minSdk = 24 targetSdk = 34 - versionCode = 10 - versionName = "1.6.0" + versionCode = 11 + versionName = "1.7.0" } signingConfigs { diff --git a/evershelf-kiosk/app/src/main/kotlin/it/dadaloop/evershelf/kiosk/KioskActivity.kt b/evershelf-kiosk/app/src/main/kotlin/it/dadaloop/evershelf/kiosk/KioskActivity.kt index ed60bfd..6a642a9 100644 --- a/evershelf-kiosk/app/src/main/kotlin/it/dadaloop/evershelf/kiosk/KioskActivity.kt +++ b/evershelf-kiosk/app/src/main/kotlin/it/dadaloop/evershelf/kiosk/KioskActivity.kt @@ -337,7 +337,7 @@ class KioskActivity : AppCompatActivity() { override fun onPageFinished(view: WebView?, url: String?) { super.onPageFinished(view, url) injectKioskOverlay() - checkForUpdates() + checkForUpdates(forceCheck = false) } } @@ -436,6 +436,24 @@ class KioskActivity : AppCompatActivity() { startActivity(intent) } } + /** + * Called by the webapp "Cerca aggiornamenti" button. + * Forces a GitHub release check regardless of the 6-hour throttle. + * Result is delivered back via window._kioskUpdateResult(jsonString). + */ + @JavascriptInterface + fun checkForUpdates() { + checkForUpdates(forceCheck = true, jsCallback = "window._kioskUpdateResult") + } + /** + * Trigger download + install of the kiosk APK. + * @param apkUrl direct URL to the .apk file (from checkForUpdates result) + */ + @JavascriptInterface + fun installUpdate(apkUrl: String) { + if (apkUrl.isBlank()) return + runOnUiThread { triggerApkDownload(apkUrl) } + } }, "_kioskBridge") val url = prefs.getString(KEY_URL, "http://evershelf.local") ?: "http://evershelf.local" @@ -492,23 +510,46 @@ class KioskActivity : AppCompatActivity() { // ── Update Check ────────────────────────────────────────────────────── - private fun checkForUpdates() { - val lastCheck = prefs.getLong("last_update_check", 0) + /** + * @param forceCheck bypass the 6-hour throttle + * @param jsCallback optional JS function name to call back with a JSON result + * Signature: fn({ has_update, current, latest, apk_url, error }) + */ + private fun checkForUpdates(forceCheck: Boolean = false, jsCallback: String? = null) { val now = System.currentTimeMillis() - if (now - lastCheck < 6 * 60 * 60 * 1000) return + if (!forceCheck) { + val lastCheck = prefs.getLong("last_update_check", 0) + if (now - lastCheck < 6 * 60 * 60 * 1000) return + } prefs.edit().putLong("last_update_check", now).apply() Thread { + fun notifyJs(obj: JSONObject) { + if (jsCallback == null) return + val escaped = obj.toString().replace("'", "\\'") + runOnUiThread { + webView.evaluateJavascript("$jsCallback($escaped)", null) + } + } try { val conn = URL(GITHUB_RELEASES_API).openConnection() as java.net.HttpURLConnection conn.setRequestProperty("Accept", "application/vnd.github+json") - conn.connectTimeout = 5000 - conn.readTimeout = 5000 + conn.connectTimeout = 8000 + conn.readTimeout = 8000 + val responseCode = conn.responseCode + if (responseCode != 200) { + notifyJs(JSONObject().put("has_update", false).put("error", "HTTP $responseCode")) + conn.disconnect() + return@Thread + } val body = conn.inputStream.bufferedReader().readText() conn.disconnect() val json = JSONObject(body) val latestTag = json.optString("tag_name", "") - if (latestTag.isEmpty()) return@Thread + if (latestTag.isEmpty()) { + notifyJs(JSONObject().put("has_update", false).put("error", "no tag")) + return@Thread + } val currentKiosk = try { packageManager.getPackageInfo(packageName, 0).versionName ?: "" @@ -540,16 +581,26 @@ class KioskActivity : AppCompatActivity() { if (name.contains("kiosk") && url.isNotEmpty()) kioskApkUrl = url } } + if (kioskApkUrl.isEmpty()) kioskApkUrl = KIOSK_DOWNLOAD_URL - val kioskNeedsUpdate = kioskApkUrl.isNotEmpty() && currentKiosk.isNotEmpty() && + val kioskNeedsUpdate = currentKiosk.isNotEmpty() && (!isSemver || semverNewer(norm(latestTag), norm(currentKiosk))) + val result = JSONObject() + .put("has_update", kioskNeedsUpdate) + .put("current", currentKiosk) + .put("latest", latestTag) + .put("apk_url", kioskApkUrl) + + notifyJs(result) + if (!kioskNeedsUpdate) return@Thread val label = if (isSemver) "$currentKiosk → $latestTag" else latestTag - val message = "🔄 Kiosk $label" - runOnUiThread { showNativeUpdateBanner(message, kioskApkUrl) } - } catch (_: Exception) { } + runOnUiThread { showNativeUpdateBanner("🔄 Kiosk $label", kioskApkUrl) } + } catch (e: Exception) { + notifyJs(JSONObject().put("has_update", false).put("error", e.message ?: "network error")) + } }.start() } diff --git a/index.html b/index.html index 27a3c05..ae6d526 100644 --- a/index.html +++ b/index.html @@ -11,7 +11,7 @@ EverShelf - + @@ -1155,6 +1155,22 @@

Modalità kiosk full-screen + gateway bilancia integrato. Sorgente: evershelf-kiosk/

+ + +