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:
dadaloop82
2026-04-16 06:25:40 +00:00
parent 1c686fa842
commit 3ff91b3018
4 changed files with 409 additions and 38 deletions
+66
View File
@@ -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
View File
@@ -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)">
@@ -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
View File
@@ -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">&#8212; &#8212;</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>