Compare commits

...

4 Commits

3 changed files with 77 additions and 22 deletions
+13 -19
View File
@@ -303,14 +303,15 @@ function _scaleOnMessage(msg) {
const rawValue = parseFloat(msg.value); const rawValue = parseFloat(msg.value);
if (rawValue < 0) return; if (rawValue < 0) return;
// Ignore sub-gram jitter for stability decisions: only integer-gram changes matter. // Ignore sub-2g jitter for stability decisions: changes below 2g are considered noise.
const SCALE_NOISE_G = 2;
let effectiveStable = !!msg.stable; let effectiveStable = !!msg.stable;
const grams = _scaleToGrams(rawValue, msg.unit); const grams = _scaleToGrams(rawValue, msg.unit);
if (grams !== null) { if (grams !== null) {
if (effectiveStable) { if (effectiveStable) {
_scaleLastStableGrams = grams; _scaleLastStableGrams = grams;
} else if (_scaleLastStableGrams !== null) { } else if (_scaleLastStableGrams !== null) {
if (Math.round(grams) === Math.round(_scaleLastStableGrams)) { if (Math.abs(grams - _scaleLastStableGrams) < SCALE_NOISE_G) {
effectiveStable = true; effectiveStable = true;
} }
} }
@@ -402,7 +403,7 @@ function _scaleUpdateLiveBox(msg) {
const raw = parseFloat(msg.value); const raw = parseFloat(msg.value);
const rawUnit = (msg.unit || 'kg').toLowerCase(); const rawUnit = (msg.unit || 'kg').toLowerCase();
// Convert to grams for the < 10 g threshold check // Convert to grams for the < 2 g threshold check
let gForCheck = isFinite(raw) ? raw : 0; let gForCheck = isFinite(raw) ? raw : 0;
if (rawUnit === 'kg') gForCheck = raw * 1000; if (rawUnit === 'kg') gForCheck = raw * 1000;
if (rawUnit === 'lbs' || rawUnit === 'lb') gForCheck = raw * 453.592; if (rawUnit === 'lbs' || rawUnit === 'lb') gForCheck = raw * 453.592;
@@ -410,7 +411,7 @@ function _scaleUpdateLiveBox(msg) {
const valEl = document.getElementById('scale-live-val'); const valEl = document.getElementById('scale-live-val');
const lblEl = document.getElementById('scale-live-label'); const lblEl = document.getElementById('scale-live-label');
if (isFinite(raw) && gForCheck < 10 && gForCheck > 0) { if (isFinite(raw) && gForCheck < 2 && gForCheck > 0) {
// Weight too low — show red flashing warning // Weight too low — show red flashing warning
box.classList.add('scale-low-weight'); box.classList.add('scale-low-weight');
if (valEl) valEl.textContent = `${raw} ${msg.unit || 'kg'}`; if (valEl) valEl.textContent = `${raw} ${msg.unit || 'kg'}`;
@@ -475,14 +476,14 @@ function _scaleAutoFillUse(msg) {
else if (srcUnit === 'ml') { grams = rawVal; scaleAlreadyMl = true; } else if (srcUnit === 'ml') { grams = rawVal; scaleAlreadyMl = true; }
else grams = rawVal; else grams = rawVal;
// Reject if raw grams < 10 (piatto vuoto / tara / rumore) // Reject if raw grams < 2 (tara / rumore)
if (grams < 10) { if (grams < 2) {
_cancelScaleStabilityWait(); // stop bar only; keep sentinel & userDismissed _cancelScaleStabilityWait(); // stop bar only; keep sentinel & userDismissed
return; return;
} }
// Reject if weight hasn't changed enough from last confirmed reading (same product still on scale) // Reject if weight hasn't changed enough from last confirmed reading (same product still on scale)
if (_scaleLastConfirmedGrams !== null && Math.abs(grams - _scaleLastConfirmedGrams) < 10) { if (_scaleLastConfirmedGrams !== null && Math.abs(grams - _scaleLastConfirmedGrams) < 2) {
return; return;
} }
@@ -507,15 +508,8 @@ function _scaleAutoFillUse(msg) {
} }
} }
// Reject if converted value < 10 (density edge case) // Reject if converted value < 2 (density edge case)
if (val < 10) { if (val < 2) {
_cancelScaleStabilityWait();
return;
}
if (val !== _scaleStabilityVal) {
// New (different) weight → clear dismissal, restart stability wait
_scaleStabilityVal = val;
_scaleUserDismissed = false; _scaleUserDismissed = false;
_cancelScaleTimersOnly(); _cancelScaleTimersOnly();
_startScaleStabilityWait(() => { _startScaleStabilityWait(() => {
@@ -618,15 +612,15 @@ function _scaleAutoFillRecipeUse(msg) {
hint.style.display = ''; hint.style.display = '';
} }
if (val < 10) { if (val < 2) {
_cancelScaleStabilityWait(); // stop bar only; keep sentinel _cancelScaleStabilityWait(); // stop bar only; keep sentinel
if (livLabel) livLabel.textContent = t('scale.weight_too_low'); if (livLabel) livLabel.textContent = t('scale.weight_too_low');
return; return;
} }
// Reject if weight hasn't changed enough from last confirmed reading. // Reject if weight hasn't changed enough from last confirmed reading.
// Threshold: 5g — gives enough time to tare after opening the modal. // Threshold: 2g — noise filter.
if (_scaleLastConfirmedGrams !== null && Math.abs(grams - _scaleLastConfirmedGrams) < 5) { if (_scaleLastConfirmedGrams !== null && Math.abs(grams - _scaleLastConfirmedGrams) < 2) {
return; return;
} }
+2 -2
View File
@@ -11,8 +11,8 @@ android {
applicationId = "it.dadaloop.evershelf.kiosk" applicationId = "it.dadaloop.evershelf.kiosk"
minSdk = 24 minSdk = 24
targetSdk = 34 targetSdk = 34
versionCode = 12 versionCode = 13
versionName = "1.7.1" versionName = "1.7.2"
} }
signingConfigs { signingConfigs {
@@ -83,6 +83,16 @@ class KioskActivity : AppCompatActivity() {
private val pollHandler = Handler(Looper.getMainLooper()) private val pollHandler = Handler(Looper.getMainLooper())
private var activeDownloadId: Long = -1 private var activeDownloadId: Long = -1
// Periodic update-check handler (fires every 30 min; internal throttle in checkForUpdates limits real API calls to every 6h)
private val updateCheckHandler = Handler(Looper.getMainLooper())
private val updateCheckRunnable = Runnable { schedulePeriodicUpdateCheck() }
private val UPDATE_CHECK_INTERVAL_MS = 30L * 60 * 1000 // 30 minutes
private fun schedulePeriodicUpdateCheck() {
checkForUpdates(forceCheck = false)
updateCheckHandler.postDelayed(updateCheckRunnable, UPDATE_CHECK_INTERVAL_MS)
}
// File chooser // File chooser
private var fileChooserCallback: ValueCallback<Array<Uri>>? = null private var fileChooserCallback: ValueCallback<Array<Uri>>? = null
@@ -104,6 +114,9 @@ class KioskActivity : AppCompatActivity() {
private const val KIOSK_DOWNLOAD_URL = "https://github.com/dadaloop82/EverShelf/releases/download/kiosk-latest/evershelf-kiosk.apk" private const val KIOSK_DOWNLOAD_URL = "https://github.com/dadaloop82/EverShelf/releases/download/kiosk-latest/evershelf-kiosk.apk"
private const val SPLASH_DURATION = 1500L private const val SPLASH_DURATION = 1500L
private const val GITHUB_RELEASES_API = "https://api.github.com/repos/dadaloop82/EverShelf/releases/latest" private const val GITHUB_RELEASES_API = "https://api.github.com/repos/dadaloop82/EverShelf/releases/latest"
// Keys for persisting a pending update across restarts
private const val KEY_PENDING_UPDATE_VERSION = "pending_update_version"
private const val KEY_PENDING_UPDATE_URL = "pending_update_url"
} }
override fun attachBaseContext(newBase: Context) { override fun attachBaseContext(newBase: Context) {
@@ -648,7 +661,17 @@ class KioskActivity : AppCompatActivity() {
notifyJs(result) notifyJs(result)
if (!kioskNeedsUpdate) return@Thread if (!kioskNeedsUpdate) {
// Clear any stale pending update if the current version is now up to date
prefs.edit().remove(KEY_PENDING_UPDATE_VERSION).remove(KEY_PENDING_UPDATE_URL).apply()
return@Thread
}
// Persist the pending update so the banner reappears after a crash/restart
prefs.edit()
.putString(KEY_PENDING_UPDATE_VERSION, latestTag)
.putString(KEY_PENDING_UPDATE_URL, kioskApkUrl)
.apply()
val label = if (isSemver) "$currentKiosk$latestTag" else latestTag val label = if (isSemver) "$currentKiosk$latestTag" else latestTag
runOnUiThread { showNativeUpdateBanner("🔄 Kiosk $label", kioskApkUrl) } runOnUiThread { showNativeUpdateBanner("🔄 Kiosk $label", kioskApkUrl) }
@@ -658,6 +681,33 @@ class KioskActivity : AppCompatActivity() {
}.start() }.start()
} }
/**
* On resume: if a previous session detected an available update and saved it to prefs,
* restore the update banner immediately without a network round-trip.
*/
private fun restorePendingUpdateBanner() {
val savedVersion = prefs.getString(KEY_PENDING_UPDATE_VERSION, null) ?: return
val savedUrl = prefs.getString(KEY_PENDING_UPDATE_URL, null) ?: return
val currentKiosk = try { packageManager.getPackageInfo(packageName, 0).versionName ?: "" } catch (_: Exception) { "" }
// Normalise: strip non-numeric prefix for comparison
val norm = { v: String -> v.replace(Regex("^[^0-9]*"), "") }
fun semverNewer(remote: String, local: String): Boolean {
val r = remote.split(".").map { it.filter(Char::isDigit).toIntOrNull() ?: 0 }
val l = local.split(".").map { it.filter(Char::isDigit).toIntOrNull() ?: 0 }
for (i in 0 until maxOf(r.size, l.size)) {
val rv = r.getOrElse(i) { 0 }; val lv = l.getOrElse(i) { 0 }
if (rv != lv) return rv > lv
}
return false
}
if (currentKiosk.isNotEmpty() && semverNewer(norm(savedVersion), norm(currentKiosk))) {
showNativeUpdateBanner("🔄 Kiosk $currentKiosk$savedVersion", savedUrl)
} else {
// Update was installed or is no longer applicable — clear the saved entry
prefs.edit().remove(KEY_PENDING_UPDATE_VERSION).remove(KEY_PENDING_UPDATE_URL).apply()
}
}
private fun showNativeUpdateBanner(message: String, apkDownloadUrl: String) { private fun showNativeUpdateBanner(message: String, apkDownloadUrl: String) {
pendingApkDownloadUrl = apkDownloadUrl pendingApkDownloadUrl = apkDownloadUrl
tvUpdateMessage.text = "⬆️ Aggiornamento disponibile: $message" tvUpdateMessage.text = "⬆️ Aggiornamento disponibile: $message"
@@ -962,6 +1012,16 @@ class KioskActivity : AppCompatActivity() {
// Re-apply screensaver flag in case the user changed it in Settings // Re-apply screensaver flag in case the user changed it in Settings
applyScreensaverFlag() applyScreensaverFlag()
} }
// Show banner immediately if there is a pending update detected in a previous session
restorePendingUpdateBanner()
// Start (or restart) the periodic update check
updateCheckHandler.removeCallbacks(updateCheckRunnable)
updateCheckHandler.postDelayed(updateCheckRunnable, UPDATE_CHECK_INTERVAL_MS)
}
override fun onPause() {
super.onPause()
updateCheckHandler.removeCallbacks(updateCheckRunnable)
} }
@Suppress("DEPRECATION") @Suppress("DEPRECATION")
@@ -1051,6 +1111,7 @@ class KioskActivity : AppCompatActivity() {
override fun onDestroy() { override fun onDestroy() {
ErrorReporter.markCleanStop() ErrorReporter.markCleanStop()
updateCheckHandler.removeCallbacks(updateCheckRunnable)
tts?.stop() tts?.stop()
tts?.shutdown() tts?.shutdown()
tts = null tts = null