fix(scale): progress-bar restart loop + low-weight warning + gateway auto-reconnect cycle
- Fix progress-bar restarting continuously when weight is stable: add _cancelScaleTimersOnly() that stops timers/animations without _cancelScaleTimersOnly() so the same value resumes counting when stability returns instead of always restarting the 5-s wait. Add 'else if' branch in _scaleAutoFillUse / _scaleAutoFillRecipeUse to restart stability wait after brief instability for the same value. - Show red blinking warning in scale-live-box when weight < 10 g: adds scale-low-weight CSS class with pulsing border/shadow animation, the label shows '< 10 g · inserisci manualmente' instead of the stability progress bar. No auto-confirm fires below 10 g. - Gateway Android app: scale auto-reconnect now retries indefinitely. isAutoReconnecting flag keeps the scan→wait→scan cycle running until the scale is found again; onScanStopped schedules a new scan after 10 s whenever autoReconnect is active and scale is still offline.
This commit is contained in:
@@ -833,6 +833,72 @@ body {
|
||||
transition: none;
|
||||
}
|
||||
|
||||
/* ===== SCALE LIVE BOX ===== */
|
||||
.scale-live-box {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 10px;
|
||||
background: var(--bg-card);
|
||||
border: 1.5px solid var(--border);
|
||||
border-radius: 10px;
|
||||
padding: 8px 12px;
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
.scale-live-icon {
|
||||
font-size: 1.3rem;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
.scale-live-content {
|
||||
flex: 1;
|
||||
min-width: 0;
|
||||
}
|
||||
.scale-live-val {
|
||||
font-size: 1.15rem;
|
||||
font-weight: 700;
|
||||
color: var(--text);
|
||||
line-height: 1.2;
|
||||
}
|
||||
.scale-live-label {
|
||||
font-size: 0.72rem;
|
||||
color: var(--text-muted);
|
||||
margin-top: 2px;
|
||||
}
|
||||
.scale-live-progress {
|
||||
height: 3px;
|
||||
background: var(--border);
|
||||
border-radius: 2px;
|
||||
margin-top: 5px;
|
||||
overflow: hidden;
|
||||
}
|
||||
.scale-live-progress-bar {
|
||||
height: 100%;
|
||||
width: 0%;
|
||||
background: var(--accent, #7c3aed);
|
||||
border-radius: 2px;
|
||||
transition: none;
|
||||
}
|
||||
.scale-live-box.scale-low-weight {
|
||||
border-color: #dc2626;
|
||||
background: #fef2f2;
|
||||
animation: scaleLowWeightBlink 0.8s ease-in-out infinite alternate;
|
||||
}
|
||||
@media (prefers-color-scheme: dark) {
|
||||
.scale-live-box.scale-low-weight {
|
||||
background: #3b0000;
|
||||
}
|
||||
}
|
||||
.scale-low-weight .scale-live-val {
|
||||
color: #dc2626 !important;
|
||||
}
|
||||
.scale-low-weight .scale-live-label {
|
||||
color: #dc2626 !important;
|
||||
font-weight: 600;
|
||||
}
|
||||
@keyframes scaleLowWeightBlink {
|
||||
from { border-color: #dc2626; box-shadow: none; }
|
||||
to { border-color: #dc2626; box-shadow: 0 0 0 3px rgba(220,38,38,0.25); }
|
||||
}
|
||||
|
||||
.btn-accent {
|
||||
background: var(--accent);
|
||||
color: white;
|
||||
|
||||
+313
-32
@@ -68,7 +68,13 @@ let _scaleBattery = null;
|
||||
let _scaleReconnectTimer = null;
|
||||
let _scaleWeightCallback = null; // pending on-demand weight request callback
|
||||
let _scaleLatestWeight = null; // last received weight message
|
||||
let _scaleAutoFillPaused = false; // true when user manually edited use-quantity (stops live auto-fill)
|
||||
let _scaleAutoConfirmTimer = null; // countdown timer for auto-confirm after stable weight
|
||||
let _scaleAutoConfirmRAF = null; // rAF handle for auto-confirm progress bar animation
|
||||
let _scaleStabilityTimer = null; // setTimeout: wait 5 s stable before starting confirm bar
|
||||
let _scaleStabilityRAF = null; // rAF handle for stability progress bar in the live box
|
||||
let _scaleStabilityVal = null; // value we are currently timing for stability
|
||||
let _scaleUserDismissed = false; // user tapped or edited → don't retrigger for same value
|
||||
let _scaleRecipeAutoFillPaused = false; // pause flag for recipe-use modal only
|
||||
|
||||
function scaleInit() {
|
||||
const s = getSettings();
|
||||
@@ -111,19 +117,30 @@ function _scaleOnMessage(msg) {
|
||||
_scaleUpdateStatus(_scaleConnected ? 'connected' : 'searching');
|
||||
} else if (msg.type === 'weight') {
|
||||
_scaleLatestWeight = msg;
|
||||
// Update live reading overlay if visible
|
||||
// Update live reading modal overlay if visible (scale-read modal)
|
||||
const live = document.getElementById('scale-reading-live');
|
||||
if (live) live.textContent = `${msg.value} ${msg.unit || 'kg'}${msg.stable ? ' ✓' : ' …'}`;
|
||||
// Always update the persistent live box on the use page (every message, stable or not)
|
||||
_scaleUpdateLiveBox(msg);
|
||||
// If weight is NOT stable: stop any running timer/bar but keep the sentinel value.
|
||||
// The sentinel is reset only when a genuinely different stable value arrives.
|
||||
if (!msg.stable) {
|
||||
_cancelScaleTimersOnly();
|
||||
}
|
||||
// Fulfil pending callback on stable reading
|
||||
if (msg.stable && _scaleWeightCallback) {
|
||||
const cb = _scaleWeightCallback;
|
||||
_scaleWeightCallback = null;
|
||||
cb(msg);
|
||||
}
|
||||
// Live auto-fill use-quantity when on use page
|
||||
if (msg.stable && _currentPageId === 'use' && !_scaleAutoFillPaused) {
|
||||
// Drive stability logic on use page
|
||||
if (msg.stable && _currentPageId === 'use') {
|
||||
_scaleAutoFillUse(msg);
|
||||
}
|
||||
// Same for recipe-use modal
|
||||
if (msg.stable && document.getElementById('ruse-quantity') && !_scaleRecipeAutoFillPaused) {
|
||||
_scaleAutoFillRecipeUse(msg);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -161,43 +178,84 @@ function _scaleDensityForProduct(product) {
|
||||
}
|
||||
|
||||
/**
|
||||
* Auto-fill the use-quantity input with a stable scale reading (no modal needed).
|
||||
* Works for normal mode (g/ml) and conf sub-unit mode (e.g. latte = conf × 1000ml).
|
||||
* Skips pz, conf-unit mode, and all non-weight units.
|
||||
* Update the persistent live-weight box on the use page (called on every weight message).
|
||||
* Shows raw scale reading in real time regardless of stability or unit compatibility.
|
||||
*/
|
||||
function _scaleUpdateLiveBox(msg) {
|
||||
const box = document.getElementById('scale-live-box');
|
||||
if (!box) return;
|
||||
const s = getSettings();
|
||||
const active = s.scale_enabled && s.scale_gateway_url && _scaleConnected &&
|
||||
_currentPageId === 'use';
|
||||
box.style.display = active ? '' : 'none';
|
||||
if (!active) return;
|
||||
|
||||
const raw = parseFloat(msg.value);
|
||||
const rawUnit = (msg.unit || 'kg').toLowerCase();
|
||||
// Convert to grams for the < 10 g threshold check
|
||||
let gForCheck = isFinite(raw) ? raw : 0;
|
||||
if (rawUnit === 'kg') gForCheck = raw * 1000;
|
||||
if (rawUnit === 'lbs' || rawUnit === 'lb') gForCheck = raw * 453.592;
|
||||
|
||||
const valEl = document.getElementById('scale-live-val');
|
||||
const lblEl = document.getElementById('scale-live-label');
|
||||
|
||||
if (isFinite(raw) && gForCheck < 10 && gForCheck > 0) {
|
||||
// Weight too low — show red flashing warning
|
||||
box.classList.add('scale-low-weight');
|
||||
if (valEl) valEl.textContent = `${raw} ${msg.unit || 'kg'}`;
|
||||
if (lblEl) lblEl.textContent = '< 10 g · inserisci manualmente';
|
||||
} else {
|
||||
box.classList.remove('scale-low-weight');
|
||||
const stIcon = msg.stable ? ' ✓' : ' …';
|
||||
if (valEl) valEl.textContent = `${isFinite(raw) ? raw : '—'} ${msg.unit || 'kg'}${stIcon}`;
|
||||
if (lblEl) lblEl.textContent = '';
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Auto-fill: called on every STABLE weight message while on the use page.
|
||||
* - Updates the live box (conversion hint)
|
||||
* - After 5 s of stable unchanged value: fills the input and starts the confirm progress bar
|
||||
* - If value changes: resets the 5-s stability wait
|
||||
* - If user dismissed (touch/edit): does nothing for the same value; resets on value change
|
||||
*/
|
||||
function _scaleAutoFillUse(msg) {
|
||||
if (!msg) return;
|
||||
|
||||
// Determine the effective target unit:
|
||||
// - conf/sub mode → use the package sub-unit (e.g. ml for milk, g for pasta)
|
||||
// - normal mode → _useNormalUnit
|
||||
// Determine target unit
|
||||
let unit;
|
||||
if (_useConfMode && _useConfMode._activeUnit === 'sub') {
|
||||
unit = (_useConfMode.packageUnit || '').toLowerCase();
|
||||
} else {
|
||||
unit = _useNormalUnit;
|
||||
}
|
||||
if (unit !== 'g' && unit !== 'ml') return; // never touch pz/conf-unit/etc
|
||||
if (unit !== 'g' && unit !== 'ml') return; // pz / conf-unit: ignore
|
||||
|
||||
const rawVal = parseFloat(msg.value);
|
||||
if (!isFinite(rawVal) || rawVal <= 0) return;
|
||||
const srcUnit = (msg.unit || '').toLowerCase();
|
||||
|
||||
// Step 1 — normalise to grams (or handle ml-from-scale directly)
|
||||
// Normalise to grams
|
||||
let grams;
|
||||
let scaleAlreadyMl = false;
|
||||
if (srcUnit === 'g') grams = rawVal;
|
||||
else if (srcUnit === 'kg') grams = rawVal * 1000;
|
||||
else if (srcUnit === 'lbs' || srcUnit === 'lb') grams = rawVal * 453.592;
|
||||
else if (srcUnit === 'oz') grams = rawVal * 28.3495;
|
||||
else if (srcUnit === 'ml') { grams = rawVal; scaleAlreadyMl = true; } // scale in liquid mode
|
||||
else if (srcUnit === 'ml') { grams = rawVal; scaleAlreadyMl = true; }
|
||||
else grams = rawVal;
|
||||
|
||||
// Step 2 — convert to target unit
|
||||
// Reject if raw grams < 10 (piatto vuoto / tara / rumore)
|
||||
if (grams < 10) {
|
||||
_cancelScaleStabilityWait(); // stop bar only; keep sentinel & userDismissed
|
||||
return;
|
||||
}
|
||||
|
||||
// Convert to target unit
|
||||
let val;
|
||||
let hintExtra = '';
|
||||
if (unit === 'g') {
|
||||
// If scale reported ml, convert via density to get grams
|
||||
if (scaleAlreadyMl) {
|
||||
const density = _scaleDensityForProduct(currentProduct);
|
||||
val = Math.round(grams * density);
|
||||
@@ -205,9 +263,8 @@ function _scaleAutoFillUse(msg) {
|
||||
} else {
|
||||
val = Math.round(grams);
|
||||
}
|
||||
} else { // ml
|
||||
} else {
|
||||
if (scaleAlreadyMl) {
|
||||
// Scale already in ml — use directly without density conversion
|
||||
val = Math.round(grams);
|
||||
} else {
|
||||
const density = _scaleDensityForProduct(currentProduct);
|
||||
@@ -216,17 +273,224 @@ function _scaleAutoFillUse(msg) {
|
||||
}
|
||||
}
|
||||
|
||||
const inp = document.getElementById('use-quantity');
|
||||
if (inp) {
|
||||
inp.value = val;
|
||||
const hint = document.getElementById('scale-autofill-hint');
|
||||
if (hint) {
|
||||
hint.textContent = `⚖️ Lettura live dalla bilancia${hintExtra}`;
|
||||
hint.style.display = '';
|
||||
// Reject if converted value < 10 (density edge case)
|
||||
if (val < 10) {
|
||||
_cancelScaleStabilityWait();
|
||||
return;
|
||||
}
|
||||
|
||||
if (val !== _scaleStabilityVal) {
|
||||
// New (different) weight → clear dismissal, restart stability wait
|
||||
_scaleStabilityVal = val;
|
||||
_scaleUserDismissed = false;
|
||||
_cancelScaleTimersOnly();
|
||||
_startScaleStabilityWait(() => {
|
||||
// Fill the input after 5 s of stable weight
|
||||
const inp = document.getElementById('use-quantity');
|
||||
if (inp) inp.value = val;
|
||||
// Start the 5-s confirm progress bar
|
||||
_startScaleAutoConfirm(() => {
|
||||
const form = document.querySelector('#page-use form');
|
||||
if (form) form.dispatchEvent(new Event('submit', { bubbles: true, cancelable: true }));
|
||||
}, 'btn-use-submit');
|
||||
});
|
||||
} else if (!_scaleUserDismissed && !_scaleStabilityTimer && !_scaleAutoConfirmTimer) {
|
||||
// Same value, not dismissed, no timer running (e.g. after brief !stable interruption)
|
||||
// → restart stability wait so it eventually completes
|
||||
_cancelScaleTimersOnly();
|
||||
_startScaleStabilityWait(() => {
|
||||
const inp = document.getElementById('use-quantity');
|
||||
if (inp) inp.value = val;
|
||||
_startScaleAutoConfirm(() => {
|
||||
const form = document.querySelector('#page-use form');
|
||||
if (form) form.dispatchEvent(new Event('submit', { bubbles: true, cancelable: true }));
|
||||
}, 'btn-use-submit');
|
||||
});
|
||||
}
|
||||
// Same value + dismissed → do nothing (user explicitly dismissed this value)
|
||||
// Same value + timer running → do nothing (already counting down)
|
||||
}
|
||||
|
||||
/**
|
||||
* Auto-fill ruse-quantity input from a stable scale reading (recipe-use modal).
|
||||
*/
|
||||
function _scaleAutoFillRecipeUse(msg) {
|
||||
if (!msg) return;
|
||||
let unit;
|
||||
if (_recipeUseConfMode && _recipeUseConfMode._activeUnit === 'sub') {
|
||||
unit = (_recipeUseConfMode.packageUnit || '').toLowerCase();
|
||||
} else {
|
||||
unit = _recipeUseNormalUnit;
|
||||
}
|
||||
if (unit !== 'g' && unit !== 'ml') return;
|
||||
|
||||
const rawVal = parseFloat(msg.value);
|
||||
if (!isFinite(rawVal) || rawVal <= 0) return;
|
||||
const srcUnit = (msg.unit || '').toLowerCase();
|
||||
|
||||
let grams;
|
||||
let scaleAlreadyMl = false;
|
||||
if (srcUnit === 'g') grams = rawVal;
|
||||
else if (srcUnit === 'kg') grams = rawVal * 1000;
|
||||
else if (srcUnit === 'lbs' || srcUnit === 'lb') grams = rawVal * 453.592;
|
||||
else if (srcUnit === 'oz') grams = rawVal * 28.3495;
|
||||
else if (srcUnit === 'ml') { grams = rawVal; scaleAlreadyMl = true; }
|
||||
else grams = rawVal;
|
||||
|
||||
let val;
|
||||
let hintExtra = '';
|
||||
if (unit === 'g') {
|
||||
if (scaleAlreadyMl) {
|
||||
const density = _scaleDensityForProduct(currentProduct);
|
||||
val = Math.round(grams * density);
|
||||
if (density !== 1.00) hintExtra = ` (densità ${density} g/ml)`;
|
||||
} else {
|
||||
val = Math.round(grams);
|
||||
}
|
||||
} else {
|
||||
if (scaleAlreadyMl) {
|
||||
val = Math.round(grams);
|
||||
} else {
|
||||
const density = _scaleDensityForProduct(currentProduct);
|
||||
val = Math.round(grams / density);
|
||||
if (density !== 1.00) hintExtra = ` (densità ${density} g/ml)`;
|
||||
}
|
||||
}
|
||||
|
||||
// Update live hint in modal with the raw scale reading always
|
||||
const hint = document.getElementById('ruse-scale-hint');
|
||||
if (hint) {
|
||||
hint.textContent = `⚖️ Bilancia: ${msg.value} ${msg.unit || 'kg'}${msg.stable ? ' ✓' : ' …'}`;
|
||||
hint.style.display = '';
|
||||
}
|
||||
|
||||
if (val < 10) {
|
||||
_cancelScaleStabilityWait(); // stop bar only; keep sentinel
|
||||
return;
|
||||
}
|
||||
|
||||
if (val !== _scaleStabilityVal) {
|
||||
_scaleStabilityVal = val;
|
||||
_scaleUserDismissed = false;
|
||||
_cancelScaleTimersOnly();
|
||||
_startScaleStabilityWait(() => {
|
||||
const inp = document.getElementById('ruse-quantity');
|
||||
if (inp) inp.value = val;
|
||||
if (hint) {
|
||||
hint.textContent = `⚖️ Peso bilancia: ${val} ${unit}${hintExtra}`;
|
||||
hint.style.display = '';
|
||||
}
|
||||
_startScaleAutoConfirm(() => { submitRecipeUse(false); }, 'btn-ruse-submit');
|
||||
});
|
||||
} else if (!_scaleUserDismissed && !_scaleStabilityTimer && !_scaleAutoConfirmTimer) {
|
||||
_cancelScaleTimersOnly();
|
||||
_startScaleStabilityWait(() => {
|
||||
const inp = document.getElementById('ruse-quantity');
|
||||
if (inp) inp.value = val;
|
||||
_startScaleAutoConfirm(() => { submitRecipeUse(false); }, 'btn-ruse-submit');
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
/** Cancel auto-confirm countdown on any screen press (touch = dismiss). */
|
||||
function _cancelScaleAutoConfirmOnTouch() {
|
||||
_cancelScaleAutoConfirm(true);
|
||||
}
|
||||
|
||||
/**
|
||||
* Cancel timers, animations and button styles — does NOT touch _scaleStabilityVal
|
||||
* or _scaleUserDismissed. Use this when weight goes unstable so the sentinel
|
||||
* is preserved and the same value can resume counting when stability returns.
|
||||
*/
|
||||
function _cancelScaleTimersOnly() {
|
||||
if (_scaleAutoConfirmTimer) { clearTimeout(_scaleAutoConfirmTimer); _scaleAutoConfirmTimer = null; }
|
||||
if (_scaleAutoConfirmRAF) { cancelAnimationFrame(_scaleAutoConfirmRAF); _scaleAutoConfirmRAF = null; }
|
||||
_cancelScaleStabilityWait();
|
||||
const useBtn = document.getElementById('btn-use-submit');
|
||||
const ruseBtn = document.getElementById('btn-ruse-submit');
|
||||
if (useBtn) useBtn.style.background = '';
|
||||
if (ruseBtn) ruseBtn.style.background = '';
|
||||
document.removeEventListener('pointerdown', _cancelScaleAutoConfirmOnTouch, true);
|
||||
}
|
||||
|
||||
/**
|
||||
* Full cancel: stops timers AND updates state flags.
|
||||
* @param {boolean} fromTouch true = user tapped → set userDismissed
|
||||
* false = programmatic (page nav, closeModal, oninput) → reset sentinel
|
||||
*/
|
||||
function _cancelScaleAutoConfirm(fromTouch) {
|
||||
_cancelScaleTimersOnly();
|
||||
if (fromTouch) {
|
||||
_scaleUserDismissed = true;
|
||||
} else {
|
||||
_scaleStabilityVal = null;
|
||||
}
|
||||
}
|
||||
|
||||
/** Stop the stability wait and reset its progress bar. */
|
||||
function _cancelScaleStabilityWait() {
|
||||
if (_scaleStabilityTimer) { clearTimeout(_scaleStabilityTimer); _scaleStabilityTimer = null; }
|
||||
if (_scaleStabilityRAF) { cancelAnimationFrame(_scaleStabilityRAF); _scaleStabilityRAF = null; }
|
||||
const bar = document.getElementById('scale-live-progress-bar');
|
||||
if (bar) bar.style.width = '0%';
|
||||
}
|
||||
|
||||
/**
|
||||
* Start a 5-second stability wait with an animated progress bar in the live box.
|
||||
* Calls onStable() when weight unchanged for 5 s.
|
||||
*/
|
||||
function _startScaleStabilityWait(onStable) {
|
||||
_cancelScaleStabilityWait();
|
||||
const duration = 5000;
|
||||
const start = performance.now();
|
||||
const bar = document.getElementById('scale-live-progress-bar');
|
||||
|
||||
function tick() {
|
||||
const pct = Math.min(100, ((performance.now() - start) / duration) * 100);
|
||||
if (bar) bar.style.width = pct + '%';
|
||||
if (pct < 100) { _scaleStabilityRAF = requestAnimationFrame(tick); }
|
||||
}
|
||||
_scaleStabilityRAF = requestAnimationFrame(tick);
|
||||
|
||||
_scaleStabilityTimer = setTimeout(() => {
|
||||
_scaleStabilityTimer = null;
|
||||
if (_scaleStabilityRAF) { cancelAnimationFrame(_scaleStabilityRAF); _scaleStabilityRAF = null; }
|
||||
if (bar) bar.style.width = '0%';
|
||||
onStable();
|
||||
}, duration);
|
||||
}
|
||||
function _startScaleAutoConfirm(onConfirm, btnId) {
|
||||
if (_scaleAutoConfirmTimer) { clearTimeout(_scaleAutoConfirmTimer); _scaleAutoConfirmTimer = null; }
|
||||
if (_scaleAutoConfirmRAF) { cancelAnimationFrame(_scaleAutoConfirmRAF); _scaleAutoConfirmRAF = null; }
|
||||
const btn = btnId ? document.getElementById(btnId) : null;
|
||||
const baseBg = btn ? getComputedStyle(btn).backgroundColor : '';
|
||||
const duration = 5000;
|
||||
const start = performance.now();
|
||||
|
||||
function tick() {
|
||||
const elapsed = performance.now() - start;
|
||||
const pct = Math.min(100, (elapsed / duration) * 100);
|
||||
if (btn) {
|
||||
btn.style.background =
|
||||
`linear-gradient(to right, rgba(255,255,255,0.35) ${pct}%, rgba(255,255,255,0) ${pct}%), ${baseBg}`;
|
||||
}
|
||||
if (elapsed < duration) { _scaleAutoConfirmRAF = requestAnimationFrame(tick); }
|
||||
}
|
||||
_scaleAutoConfirmRAF = requestAnimationFrame(tick);
|
||||
|
||||
_scaleAutoConfirmTimer = setTimeout(() => {
|
||||
_scaleAutoConfirmTimer = null;
|
||||
if (btn) btn.style.background = '';
|
||||
document.removeEventListener('pointerdown', _cancelScaleAutoConfirmOnTouch, true);
|
||||
onConfirm();
|
||||
}, duration);
|
||||
|
||||
document.addEventListener('pointerdown', _cancelScaleAutoConfirmOnTouch, true);
|
||||
}
|
||||
|
||||
/**
|
||||
* Update the scale status indicator icon/class.
|
||||
*/
|
||||
function _scaleUpdateStatus(state) {
|
||||
const el = document.getElementById('scale-status-indicator');
|
||||
if (!el) return;
|
||||
@@ -303,6 +567,13 @@ function updateScaleReadButtons() {
|
||||
if (btnUse) {
|
||||
btnUse.style.display = (ready && (_useNormalUnit === 'g' || _useNormalUnit === 'ml')) ? '' : 'none';
|
||||
}
|
||||
// Live box: visible when scale enabled + connected + on use page + compatible unit
|
||||
const liveBox = document.getElementById('scale-live-box');
|
||||
if (liveBox) {
|
||||
const isWeightUnit = (_useNormalUnit === 'g' || _useNormalUnit === 'ml') ||
|
||||
(_useConfMode && (_useConfMode.packageUnit === 'g' || _useConfMode.packageUnit === 'ml'));
|
||||
liveBox.style.display = (ready && _scaleConnected && _currentPageId === 'use' && isWeightUnit) ? '' : 'none';
|
||||
}
|
||||
}
|
||||
|
||||
function onScaleEnabledChange() {
|
||||
@@ -2194,6 +2465,9 @@ function showItemDetail(inventoryId, productId) {
|
||||
|
||||
function closeModal() {
|
||||
document.getElementById('modal-overlay').style.display = 'none';
|
||||
_cancelScaleAutoConfirm(false);
|
||||
_scaleRecipeAutoFillPaused = false;
|
||||
_scaleUserDismissed = false;
|
||||
}
|
||||
|
||||
async function quickUse(productId, location) {
|
||||
@@ -4452,7 +4726,9 @@ async function submitAdd(e) {
|
||||
function showUseForm() {
|
||||
renderUsePreview();
|
||||
_useConfMode = null; // reset
|
||||
_scaleAutoFillPaused = false; // reset so scale can auto-fill again
|
||||
_scaleUserDismissed = false;
|
||||
_scaleStabilityVal = null;
|
||||
_cancelScaleAutoConfirm(false);
|
||||
document.getElementById('use-quantity').value = 1;
|
||||
document.getElementById('use-location').value = 'dispensa';
|
||||
document.getElementById('use-unit-switch').style.display = 'none';
|
||||
@@ -4603,15 +4879,15 @@ async function loadUseInventoryInfo() {
|
||||
|
||||
// Default to sub-unit mode
|
||||
switchUseUnit('sub');
|
||||
// Pre-fill with latest scale reading if available
|
||||
if (_scaleLatestWeight && !_scaleAutoFillPaused) _scaleAutoFillUse(_scaleLatestWeight);
|
||||
// Trigger a live-box refresh with the latest reading if on scale
|
||||
if (_scaleLatestWeight) _scaleAutoFillUse(_scaleLatestWeight);
|
||||
} else {
|
||||
// --- NORMAL MODE ---
|
||||
_useConfMode = null;
|
||||
_useNormalUnit = unit;
|
||||
unitSwitch.style.display = 'none';
|
||||
// Pre-fill with latest scale reading if available
|
||||
if (_scaleLatestWeight && !_scaleAutoFillPaused) _scaleAutoFillUse(_scaleLatestWeight);
|
||||
// Trigger a live-box refresh with the latest reading if on scale
|
||||
if (_scaleLatestWeight) _scaleAutoFillUse(_scaleLatestWeight);
|
||||
|
||||
infoEl.innerHTML = '<strong>📦 Disponibile:</strong> ' + items.map(i => {
|
||||
const loc = LOCATIONS[i.location] || { icon: '📦', label: i.location };
|
||||
@@ -4683,6 +4959,8 @@ function getSubUnitStep(pkgUnit) {
|
||||
}
|
||||
|
||||
function adjustUseQty(direction) {
|
||||
_scaleUserDismissed = true;
|
||||
_cancelScaleTimersOnly();
|
||||
const input = document.getElementById('use-quantity');
|
||||
let val = parseFloat(input.value) || 0;
|
||||
let step;
|
||||
@@ -7517,7 +7795,8 @@ async function useRecipeIngredient(idx, productId, location, qtyNumber, btn) {
|
||||
<p id="ruse-hint" style="font-size:0.85rem;color:var(--text-muted);margin-bottom:8px">Quantità in ${subLabel} (totale: ${Math.round(totalSub)}${subLabel})</p>
|
||||
<div class="qty-control">
|
||||
<button type="button" class="qty-btn" onclick="adjustRecipeUseQty(-1)">−</button>
|
||||
<input type="number" id="ruse-quantity" value="${defaultQtyValue}" min="${step}" step="${step}" class="qty-input">
|
||||
<input type="number" id="ruse-quantity" value="${defaultQtyValue}" min="${step}" step="${step}" class="qty-input"
|
||||
oninput="_scaleRecipeAutoFillPaused=true; _cancelScaleAutoConfirm(false); var h=document.getElementById('ruse-scale-hint'); if(h) h.style.display='none';">
|
||||
<button type="button" class="qty-btn" onclick="adjustRecipeUseQty(1)">+</button>
|
||||
</div>`;
|
||||
} else {
|
||||
@@ -7529,7 +7808,8 @@ async function useRecipeIngredient(idx, productId, location, qtyNumber, btn) {
|
||||
<p style="font-size:0.85rem;color:var(--text-muted);margin-bottom:8px">Quantità da usare (${unitLabel}):</p>
|
||||
<div class="qty-control">
|
||||
<button type="button" class="qty-btn" onclick="adjustRecipeUseQty(-1)">−</button>
|
||||
<input type="number" id="ruse-quantity" value="${defaultQtyValue}" min="${inputMin}" step="any" class="qty-input">
|
||||
<input type="number" id="ruse-quantity" value="${defaultQtyValue}" min="${inputMin}" step="any" class="qty-input"
|
||||
oninput="_scaleRecipeAutoFillPaused=true; _cancelScaleAutoConfirm(false); var h=document.getElementById('ruse-scale-hint'); if(h) h.style.display='none';">
|
||||
<button type="button" class="qty-btn" onclick="adjustRecipeUseQty(1)">+</button>
|
||||
</div>`;
|
||||
}
|
||||
@@ -7556,8 +7836,9 @@ async function useRecipeIngredient(idx, productId, location, qtyNumber, btn) {
|
||||
<div class="form-group">
|
||||
<label>Quanto?</label>
|
||||
${qtySection}
|
||||
<small id="ruse-scale-hint" style="display:none; color: var(--color-accent, #7c3aed); margin-top:4px"></small>
|
||||
</div>
|
||||
<button type="button" class="btn btn-large btn-danger full-width" onclick="submitRecipeUse(false)" style="margin-top:8px">
|
||||
<button type="button" id="btn-ruse-submit" class="btn btn-large btn-danger full-width move-countdown-btn" onclick="submitRecipeUse(false)" style="margin-top:8px">
|
||||
📤 Usa questa quantità
|
||||
</button>
|
||||
<button type="button" class="btn btn-large btn-secondary full-width" style="margin-top:8px" onclick="submitRecipeUse(true)">
|
||||
|
||||
+19
-3
@@ -41,6 +41,8 @@ class MainActivity : AppCompatActivity(), BleScaleListener, ServerEventListener
|
||||
private var debugVisible = false
|
||||
private var lastDebugUpdate = 0L
|
||||
private val debugTimeFmt = SimpleDateFormat("HH:mm:ss.SSS", Locale.getDefault())
|
||||
/** True while the app is trying to re-establish a lost connection automatically. */
|
||||
private var isAutoReconnecting = false
|
||||
private companion object {
|
||||
const val MAX_DEBUG_LINES = 150
|
||||
const val DEBUG_THROTTLE_MS = 200L
|
||||
@@ -174,6 +176,7 @@ class MainActivity : AppCompatActivity(), BleScaleListener, ServerEventListener
|
||||
binding.tvScanHint.text = "Scanning for BLE scales\u2026"
|
||||
binding.btnScan.isEnabled = false
|
||||
bleManager.enableAutoConnect()
|
||||
isAutoReconnecting = false // manual scan — stop any pending auto-reconnect cycle
|
||||
bleManager.startScan()
|
||||
}
|
||||
|
||||
@@ -230,6 +233,7 @@ class MainActivity : AppCompatActivity(), BleScaleListener, ServerEventListener
|
||||
}
|
||||
|
||||
override fun onConnected(deviceName: String) {
|
||||
isAutoReconnecting = false
|
||||
binding.tvScaleStatus.text = "\u2705 Connected: $deviceName"
|
||||
binding.tvWeight.text = "Waiting for weight\u2026"
|
||||
binding.cardConnection.setCardBackgroundColor(getColor(android.R.color.holo_green_light))
|
||||
@@ -247,10 +251,11 @@ class MainActivity : AppCompatActivity(), BleScaleListener, ServerEventListener
|
||||
// This handles the scale turning off by itself (auto-off) — when it powers
|
||||
// back on it will start advertising again and we will pick it up.
|
||||
if (bleManager.getSavedDeviceAddress() != null && bleManager.hasRequiredPermissions()) {
|
||||
isAutoReconnecting = true
|
||||
binding.tvScanHint.visibility = View.VISIBLE
|
||||
binding.tvScanHint.text = "🔄 Reconnecting to saved scale in 5 s…"
|
||||
binding.tvScanHint.text = "\uD83D\uDD04 Reconnecting to saved scale in 5 s\u2026"
|
||||
binding.root.postDelayed({
|
||||
if (!bleManager.isConnected) {
|
||||
if (!bleManager.isConnected && isAutoReconnecting) {
|
||||
bleManager.enableAutoConnect()
|
||||
bleManager.startScan()
|
||||
}
|
||||
@@ -286,7 +291,18 @@ class MainActivity : AppCompatActivity(), BleScaleListener, ServerEventListener
|
||||
|
||||
override fun onScanStopped() {
|
||||
binding.btnScan.isEnabled = true
|
||||
if (devices.isEmpty()) {
|
||||
if (isAutoReconnecting && !bleManager.isConnected && bleManager.getSavedDeviceAddress() != null) {
|
||||
// Scale not found yet — retry scan after 10 s indefinitely until reconnected
|
||||
binding.tvScanHint.visibility = View.VISIBLE
|
||||
binding.tvScanHint.text = "\uD83D\uDD04 Bilancia non trovata, riprovo tra 10 s\u2026"
|
||||
binding.root.postDelayed({
|
||||
if (!bleManager.isConnected && isAutoReconnecting) {
|
||||
binding.tvScanHint.text = "\uD83D\uDD04 Cerco la bilancia\u2026"
|
||||
bleManager.enableAutoConnect()
|
||||
bleManager.startScan()
|
||||
}
|
||||
}, 10_000L)
|
||||
} else if (devices.isEmpty()) {
|
||||
binding.tvScanHint.text = "No scale found. Make sure it's on, then scan again."
|
||||
} else {
|
||||
binding.tvScanHint.text = "Tap a scale to connect."
|
||||
|
||||
+11
-3
@@ -281,6 +281,15 @@
|
||||
<div class="form-group">
|
||||
<label>Quanto hai usato?</label>
|
||||
<button type="button" id="btn-scale-use" class="btn btn-secondary scale-read-btn" style="display:none" onclick="readScaleWeight('use-quantity', function(){ return _useNormalUnit || 'g'; })" data-i18n="scale.read_btn">⚖️ Leggi dalla bilancia</button>
|
||||
<!-- Live scale weight box (visible when scale connected and unit is g/ml) -->
|
||||
<div id="scale-live-box" class="scale-live-box" style="display:none">
|
||||
<span class="scale-live-icon">⚖️</span>
|
||||
<div class="scale-live-content">
|
||||
<div id="scale-live-val" class="scale-live-val">— —</div>
|
||||
<div class="scale-live-progress"><div id="scale-live-progress-bar" class="scale-live-progress-bar"></div></div>
|
||||
<div id="scale-live-label" class="scale-live-label"></div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="use-unit-switch" id="use-unit-switch" style="display:none">
|
||||
<button type="button" class="use-unit-btn active" id="use-unit-sub" onclick="switchUseUnit('sub')"></button>
|
||||
<button type="button" class="use-unit-btn" id="use-unit-conf" onclick="switchUseUnit('conf')">Confezioni</button>
|
||||
@@ -294,11 +303,10 @@
|
||||
<div class="qty-control">
|
||||
<button type="button" class="qty-btn" id="use-qty-minus" onclick="adjustUseQty(-1)">−</button>
|
||||
<input type="number" id="use-quantity" value="1" min="0.1" step="any" class="qty-input"
|
||||
oninput="_scaleAutoFillPaused=true; var h=document.getElementById('scale-autofill-hint'); if(h) h.style.display='none';">
|
||||
oninput="_scaleUserDismissed=true; _cancelScaleTimersOnly();">
|
||||
<button type="button" class="qty-btn" id="use-qty-plus" onclick="adjustUseQty(1)">+</button>
|
||||
</div>
|
||||
<small id="scale-autofill-hint" style="display:none; color: var(--color-accent, #7c3aed); margin-top:4px; display:none">⚖️ Lettura live dalla bilancia</small>
|
||||
<button type="submit" class="btn btn-large btn-warning full-width mt-2">📤 Usa questa quantità</button>
|
||||
<button type="submit" id="btn-use-submit" class="btn btn-large btn-warning full-width mt-2 move-countdown-btn">📤 Usa questa quantità</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
Reference in New Issue
Block a user