feat: kiosk manual update check + install from settings
KioskActivity.kt:
- checkForUpdates(forceCheck, jsCallback): accepts force flag + optional
JS callback to deliver result as JSON to the WebView
- New @JavascriptInterface checkForUpdates(): bypasses 6h throttle,
calls back window._kioskUpdateResult({has_update, current, latest, apk_url})
- New @JavascriptInterface installUpdate(apkUrl): triggers APK download+install
- Improved error handling: HTTP status check + network error JSON response
app.js:
- window._kioskUpdateResult(): callback receives update check JSON,
shows green/amber status box with version info, shows install button
- _kioskCheckForUpdates(): triggers native check, shows spinner while waiting
- _kioskInstallUpdate(): passes apk_url to native installUpdate()
- loadSettings(): shows #kiosk-update-panel when in kiosk WebView
index.html:
- #kiosk-update-panel: version label, 'Cerca aggiornamenti' button,
status box, 'Installa aggiornamento' button (hidden until update found)
- CSS cache bump ?v=20260506e
build.gradle.kts: version 1.6.0 → 1.7.0 (versionCode 10 → 11)
This commit is contained in:
@@ -2053,6 +2053,9 @@ async function loadSettingsUI() {
|
|||||||
if (scaleKiosk) scaleKiosk.style.display = '';
|
if (scaleKiosk) scaleKiosk.style.display = '';
|
||||||
// Auto-set URL to localhost gateway (always port 8765 in kiosk)
|
// Auto-set URL to localhost gateway (always port 8765 in kiosk)
|
||||||
if (scaleUrlUiEl && !scaleUrlUiEl.value) scaleUrlUiEl.value = 'ws://localhost:8765';
|
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: <strong>${latest}</strong> (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 <strong>${current}</strong>`;
|
||||||
|
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 ───────────────────
|
// ── Kiosk overlay: X (close) + ↻ (refresh) buttons ───────────────────
|
||||||
// Injected into #header-left (left zone of the 3-column header).
|
// Injected into #header-left (left zone of the 3-column header).
|
||||||
// Only shown when _kioskBridge JS interface is available (Android WebView).
|
// Only shown when _kioskBridge JS interface is available (Android WebView).
|
||||||
|
|||||||
@@ -11,8 +11,8 @@ android {
|
|||||||
applicationId = "it.dadaloop.evershelf.kiosk"
|
applicationId = "it.dadaloop.evershelf.kiosk"
|
||||||
minSdk = 24
|
minSdk = 24
|
||||||
targetSdk = 34
|
targetSdk = 34
|
||||||
versionCode = 10
|
versionCode = 11
|
||||||
versionName = "1.6.0"
|
versionName = "1.7.0"
|
||||||
}
|
}
|
||||||
|
|
||||||
signingConfigs {
|
signingConfigs {
|
||||||
|
|||||||
@@ -337,7 +337,7 @@ class KioskActivity : AppCompatActivity() {
|
|||||||
override fun onPageFinished(view: WebView?, url: String?) {
|
override fun onPageFinished(view: WebView?, url: String?) {
|
||||||
super.onPageFinished(view, url)
|
super.onPageFinished(view, url)
|
||||||
injectKioskOverlay()
|
injectKioskOverlay()
|
||||||
checkForUpdates()
|
checkForUpdates(forceCheck = false)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -436,6 +436,24 @@ class KioskActivity : AppCompatActivity() {
|
|||||||
startActivity(intent)
|
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")
|
}, "_kioskBridge")
|
||||||
|
|
||||||
val url = prefs.getString(KEY_URL, "http://evershelf.local") ?: "http://evershelf.local"
|
val url = prefs.getString(KEY_URL, "http://evershelf.local") ?: "http://evershelf.local"
|
||||||
@@ -492,23 +510,46 @@ class KioskActivity : AppCompatActivity() {
|
|||||||
|
|
||||||
// ── Update Check ──────────────────────────────────────────────────────
|
// ── 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()
|
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()
|
prefs.edit().putLong("last_update_check", now).apply()
|
||||||
|
|
||||||
Thread {
|
Thread {
|
||||||
|
fun notifyJs(obj: JSONObject) {
|
||||||
|
if (jsCallback == null) return
|
||||||
|
val escaped = obj.toString().replace("'", "\\'")
|
||||||
|
runOnUiThread {
|
||||||
|
webView.evaluateJavascript("$jsCallback($escaped)", null)
|
||||||
|
}
|
||||||
|
}
|
||||||
try {
|
try {
|
||||||
val conn = URL(GITHUB_RELEASES_API).openConnection() as java.net.HttpURLConnection
|
val conn = URL(GITHUB_RELEASES_API).openConnection() as java.net.HttpURLConnection
|
||||||
conn.setRequestProperty("Accept", "application/vnd.github+json")
|
conn.setRequestProperty("Accept", "application/vnd.github+json")
|
||||||
conn.connectTimeout = 5000
|
conn.connectTimeout = 8000
|
||||||
conn.readTimeout = 5000
|
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()
|
val body = conn.inputStream.bufferedReader().readText()
|
||||||
conn.disconnect()
|
conn.disconnect()
|
||||||
val json = JSONObject(body)
|
val json = JSONObject(body)
|
||||||
val latestTag = json.optString("tag_name", "")
|
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 {
|
val currentKiosk = try {
|
||||||
packageManager.getPackageInfo(packageName, 0).versionName ?: ""
|
packageManager.getPackageInfo(packageName, 0).versionName ?: ""
|
||||||
@@ -540,16 +581,26 @@ class KioskActivity : AppCompatActivity() {
|
|||||||
if (name.contains("kiosk") && url.isNotEmpty()) kioskApkUrl = url
|
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)))
|
(!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
|
if (!kioskNeedsUpdate) return@Thread
|
||||||
|
|
||||||
val label = if (isSemver) "$currentKiosk → $latestTag" else latestTag
|
val label = if (isSemver) "$currentKiosk → $latestTag" else latestTag
|
||||||
val message = "🔄 Kiosk $label"
|
runOnUiThread { showNativeUpdateBanner("🔄 Kiosk $label", kioskApkUrl) }
|
||||||
runOnUiThread { showNativeUpdateBanner(message, kioskApkUrl) }
|
} catch (e: Exception) {
|
||||||
} catch (_: Exception) { }
|
notifyJs(JSONObject().put("has_update", false).put("error", e.message ?: "network error"))
|
||||||
|
}
|
||||||
}.start()
|
}.start()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
+17
-1
@@ -11,7 +11,7 @@
|
|||||||
<title>EverShelf</title>
|
<title>EverShelf</title>
|
||||||
<link rel="manifest" href="manifest.json">
|
<link rel="manifest" href="manifest.json">
|
||||||
<link rel="icon" type="image/png" href="assets/img/logo/logo_icon.png">
|
<link rel="icon" type="image/png" href="assets/img/logo/logo_icon.png">
|
||||||
<link rel="stylesheet" href="assets/css/style.css?v=20260506d">
|
<link rel="stylesheet" href="assets/css/style.css?v=20260506e">
|
||||||
<!-- QuaggaJS for barcode scanning -->
|
<!-- QuaggaJS for barcode scanning -->
|
||||||
<script src="https://cdn.jsdelivr.net/npm/@ericblade/quagga2@1.8.4/dist/quagga.min.js"></script>
|
<script src="https://cdn.jsdelivr.net/npm/@ericblade/quagga2@1.8.4/dist/quagga.min.js"></script>
|
||||||
<!-- @xenova/transformers: ES-module bootstrap that exposes a lazy category-classifier as window._categoryPipelinePromise -->
|
<!-- @xenova/transformers: ES-module bootstrap that exposes a lazy category-classifier as window._categoryPipelinePromise -->
|
||||||
@@ -1155,6 +1155,22 @@
|
|||||||
<p class="settings-hint" style="margin-top:8px" data-i18n="settings.kiosk.download_sub">Modalità kiosk full-screen + gateway bilancia integrato. Sorgente: <code>evershelf-kiosk/</code></p>
|
<p class="settings-hint" style="margin-top:8px" data-i18n="settings.kiosk.download_sub">Modalità kiosk full-screen + gateway bilancia integrato. Sorgente: <code>evershelf-kiosk/</code></p>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<!-- Kiosk self-update panel (visible only inside kiosk WebView) -->
|
||||||
|
<div id="kiosk-update-panel" style="display:none;background:rgba(16,185,129,0.06);border:1.5px solid rgba(16,185,129,0.2);border-radius:12px;padding:16px;margin-top:16px">
|
||||||
|
<div style="display:flex;align-items:center;justify-content:space-between;gap:8px;margin-bottom:10px">
|
||||||
|
<div style="display:flex;align-items:center;gap:8px">
|
||||||
|
<span style="font-size:1.4rem">📦</span>
|
||||||
|
<div>
|
||||||
|
<p style="margin:0;font-weight:700;font-size:0.9rem">Aggiornamento Kiosk</p>
|
||||||
|
<p class="settings-hint" style="margin:2px 0 0" id="kiosk-update-version-label">—</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<button class="btn btn-secondary" style="white-space:nowrap;min-width:120px" id="btn-kiosk-check-update" onclick="_kioskCheckForUpdates()">🔍 Cerca aggiornamenti</button>
|
||||||
|
</div>
|
||||||
|
<div id="kiosk-update-status" style="display:none;padding:10px 12px;border-radius:8px;font-size:0.85rem;line-height:1.4"></div>
|
||||||
|
<button id="btn-kiosk-install-update" style="display:none;width:100%;margin-top:10px" class="btn btn-accent btn-large" onclick="_kioskInstallUpdate()">⬇️ Installa aggiornamento</button>
|
||||||
|
</div>
|
||||||
|
|
||||||
<button class="btn btn-large btn-success full-width mt-2" onclick="saveSettings()" data-i18n="btn.save_config">💾 Salva Configurazione</button>
|
<button class="btn btn-large btn-success full-width mt-2" onclick="saveSettings()" data-i18n="btn.save_config">💾 Salva Configurazione</button>
|
||||||
<div id="settings-status" class="settings-status" style="display:none"></div>
|
<div id="settings-status" class="settings-status" style="display:none"></div>
|
||||||
</section>
|
</section>
|
||||||
|
|||||||
Reference in New Issue
Block a user