Commit Graph

389 Commits

Author SHA1 Message Date
dadaloop82 f04e227cc0 fix: kiosk title center + location pref 2 uses + update confirm before download
app.js:
- _injectKioskOverlay: move kiosk-mode class assignment BEFORE the
  _kiosk_overlay existence guard — fixes race where Kotlin onPageFinished
  injects buttons during the await api() pause, then JS skips the class
- _PREF_LOC_NEEDED: 3 → 2 (remember use-location after 2 picks, not 3)

KioskActivity.kt:
- showNativeUpdateBanner: remove auto-start of triggerApkDownload;
  banner now shows with 'Scarica' button enabled, download only starts
  when user taps it (confirms before install)
2026-05-06 11:24:29 +00:00
dadaloop82 8eba5c8573 fix: header title left-aligned by default, centered only in kiosk mode 2026-05-06 11:12:20 +00:00
dadaloop82 6f19d1bcd5 fix: screensaver watcher not activated when enabled from settings UI
initInactivityWatcher() was called once at startup and returned early if
screensaver was disabled at that moment. Enabling it later from the
settings panel had no effect until page reload.

- Make initInactivityWatcher() idempotent: attach DOM listeners only once
  (flag _inactivityListenersAttached), check screensaver_enabled dynamically
  inside each handler so disabling/enabling is reflected immediately
- Call initInactivityWatcher() at end of saveSettings() so the inactivity
  timer starts immediately when user enables screensaver
2026-05-06 05:35:37 +00:00
dadaloop82 ed89e74b94 fix: split Bimby/Cookeo appliances, add missing i18n keys, improve use_all dialog
- .env: split 'Bimby/Moulinex Cookeo' into 'Bimby' and 'Moulinex Cookeo'
- translations (it/en/de): add use_all_confirm_*, throw_all_confirm_*,
  confirm.cancel, confirm.proceed, location.dispensa/frigo/freezer
- app.js: when ANY opened package exists, always show disambiguation
  dialog ('solo questa confezione?' vs 'tutto?') instead of silently
  finishing one — was previously showing dialog only for 2+ opened packages
2026-05-06 05:22:49 +00:00
dadaloop82 f48fb02589 fix: center header title + bump to v1.7.3 for update detection
- CSS: header-title-wrap uses position:absolute with left:0/right:0
  + justify-content:center so title is truly centered regardless of
  button count on each side (kiosk has 2 left / 3 right)
- header-actions gets margin-left:auto to push right; both sides z-index:1
- Bump version 1.7.2 → 1.7.3 so already-open kiosk tabs will see
  the update badge once the new version is deployed to the server
- CSS cache bust: ?v=20260506a
2026-05-06 05:19:37 +00:00
dadaloop82 af9bae3093 fix: remove skeleton shimmer from dashboard stat cards 2026-05-06 05:15:44 +00:00
dadaloop82 521d8f8e47 fix: screensaver init timing + gemini key not wiped on settings save
- app.js: move initInactivityWatcher() inside syncSettingsFromDB().then()
  so it reads screensaver_enabled after server sync, not stale localStorage
- app.js: skip gemini_key/bring_password in save_settings POST when empty
  to avoid overwriting server .env with blank values
- api/index.php: add screensaver_enabled to getServerSettings() + saveSettings()
2026-05-06 05:14:10 +00:00
dadaloop82 c7b04f410b fix: alert banner — 'Spiega' button and title layout
- explainBannerAnomaly: querySelector('#alert-banner .banner-detail')
  was wrong (class is 'alert-banner-detail'); function returned null → no-op.
  Fixed to getElementById('alert-banner-detail').
- alert-banner-inner: position close button absolutely (top:10px right:10px)
  so it is removed from the flex row and can never push the title-body to
  collapse or wrap under it. Inner gets padding-right:44px to reserve space.
- alert-banner-body: flex:1 1 0 + overflow:hidden for robust sizing
- alert-banner-title: word-break/overflow-wrap so very long product names
  wrap cleanly inside the body instead of overflowing
2026-05-05 18:25:16 +00:00
dadaloop82 754f13111f feat: kiosk setup improvements + webapp scale indicator fixes
kiosk setup wizard:
- Scale step: ask user to power on the scale before scanning (new
  'Accendi la bilancia' card with 'Bilancia accesa → Cerca' button)
- BLE scan: filter results to only show compatible scales (scaleScore>0)
  hiding clearly non-scale BLE devices from the list
- After device selection: run a live connection test — connect to the
  scale, display the live weight, ask 'Corrisponde al peso sulla bilancia?'
  with  Sì /  Riprova / Skip buttons before confirming the device

webapp:
- Scale indicator not live on first load: scaleInit() was firing before
  syncSettingsFromDB() resolved; fixed by chaining .then(scaleInit)
- Scale icon green-on-green: connected state dot changed from #22c55e
  (green, invisible on dark-green header) to white with green border+glow,
  visible on any background color
2026-05-05 18:11:51 +00:00
dadaloop82 7ea5505a0d fix: scale indicator, logo crop, gateway LAN IP, setup spacing
webapp:
- Scale indicator: replace plain green dot with ⚖️ emoji + colored
  status badge (green/amber/grey/red); icon fades out when disconnected;
  tap shows a toast with device name + battery level
- Logo images: crop excess transparent padding from logo.png and
  logo_icon.png so content fills the frame at small display sizes
- style.css: reworked .scale-status-indicator CSS for emoji+badge

kiosk:
- SetupActivity: use device's real LAN IP for scale_gateway_url
  (was hardcoded 127.0.0.1 — only worked if server and kiosk run on
  the same machine); added getDeviceLanIp() helper (prefers wlan/eth)
- activity_setup.xml: reduce welcome step padding/margins so step 1
  fits on screen without scrolling; text sizes slightly reduced
- activity_setup.xml: fix feature bullet 'Bilancia Bluetooth via
  Gateway app' → 'Bilancia BLE integrata (nessuna app esterna)'
- strings.xml (en + it): rewrite all wizard_gateway_* strings to
  reflect integrated BLE service instead of external gateway APK
- ic_logo.png: regenerated at all densities from cropped source
2026-05-05 17:47:54 +00:00
dadaloop82 9cb29de1f0 kiosk: integrate BLE scale gateway + fix logo/branding
- Kiosk v1.6.0 (versionCode 10)
  - Integrate BLE scale gateway directly into kiosk app (no external app needed)
    - New scale/ package: BleScaleManager, GatewayWebSocketServer, ScaleProtocol, GatewayService
    - GatewayService: foreground service, runs BLE scan + WebSocket :8765 server
    - Auto-reconnect on BLE disconnect; protocol compatible with old gateway app
  - Setup step 4: replace gateway install flow with BLE device scan + selection (mandatory)
  - Permissions: added BLUETOOTH_SCAN, BLUETOOTH_CONNECT, ACCESS_FINE_LOCATION (pre-S),
    FOREGROUND_SERVICE, FOREGROUND_SERVICE_CONNECTED_DEVICE
  - KioskActivity: replace launchGatewayInBackground() with startGatewayService()
  - checkForUpdates: remove gateway APK check (gateway is now internal)
  - Remove GATEWAY_PACKAGE / GATEWAY_DOWNLOAD_URL constants

- Logo / branding
  - logo.png + logo_icon.png: transparent background (no more black)
  - ic_logo.png regenerated in all densities
  - Removed house emoji (🏠) from web UI: favicon, bottom nav, setup wizard header
  - Removed 🏠 prefix from all translations (it/en/de) and manifest
  - Setup wizard: logo shown in language + welcome steps
  - Setup wizard: footer with credits ('Creato da Stimpfl Daniel • Open Source')
  - CSS: .nav-logo-icon for bottom nav logo sizing

- Scale Gateway v2.1.1 (versionCode 8)
  - Fix false update notification: replace == comparison with proper semverNewer()
    (was reporting 'update available' whenever tag != current, e.g. v2.1.0 != 2.1.0)
2026-05-05 17:24:24 +00:00
dadaloop82 30aa2db0b9 branding: add EverShelf logo to kiosk splash + web header/preloader
- assets/img/logo/logo.png: trimmed full logo (icon + text, transparent bg)
- assets/img/logo/logo_icon.png: icon-only crop (no text, for header)
- drawable-*/ic_logo.png: multi-density PNGs for Android splash (mdpi→xxxhdpi)
- activity_kiosk.xml: replace ic_launcher_foreground with ic_logo at 260dp,
  remove redundant 'EverShelf' text row (already in logo image)
- index.html: add logo_icon.png in header title, logo.png in preloader
- style.css: add .app-preloader-logo and .header-logo-icon rules
2026-05-05 16:37:57 +00:00
dadaloop82 fc47cd8c27 fix: allow free-form quantity input for ml/g sub-units (no step constraint) 2026-05-05 05:19:51 +00:00
dadaloop82 74c4f8bd78 fix(webapp): remove redundant closeModal() before destructive confirm 2026-05-04 18:43:48 +00:00
dadaloop82 2e46090adc fix(webapp): confirmation dialog + undo button visibility
- Add _showDestructiveConfirm() helper: shows modal with 5-second
  auto-confirm countdown bar; user can confirm early or cancel
- throwAll(): now shows confirmation before discarding all stock
- submitUseAll(): same confirmation before marking all as used
- undoTransactionEntry(): replace native confirm() with modal
- Rename core logic to _doSubmitUseAll() / _doUndoTransaction()
- btn-log-undo: more visible (red tint + larger font) so user can
  easily undo accidental operations from the history log
- Bump app.js version to v=20260505a
2026-05-04 18:30:30 +00:00
dadaloop82 bff22d43b4 fix(i18n): translate antiwaste title in IT/DE + use quantity validation
- translations: antiwaste.title = 'Rapporto Anti-Spreco' (IT), 'Anti-Verschwendungs-Bericht' (DE)
- translations: add use.error_exceeds_stock in IT/EN/DE
- submitUse(): block submit if qty > available at selected location + shake animation
- adjustUseQty(): cap '+' button at max available at selected location (incl. sub-unit mode)
- style.css: add @keyframes inputShake + .input-shake class

Bump app.js cache buster to v=20260504c
2026-05-04 16:56:37 +00:00
dadaloop82 874ae90afa fix(settings): fix screensaver toggle (add toggle-slider, correct layout)
- Replace checkbox-label with toggle-row pattern matching other toggles
- Add missing <span class="toggle-slider"></span> inside toggle-switch
- Add data-i18n attributes to card title and hint
- Add screensaver.card_title / card_hint translations in all 3 locales

feat(demo): full demo mode implementation
- _applyDemoModeUI(): set _geminiAvailable=true + call _updateGeminiButtonState()
- api(): no-op all bring_add/bring_remove/bring_set_spec calls in demo mode
- api(): return in-memory shoppingItems for bring_list in demo mode
- loadShoppingList(): show placeholder list in demo mode, skip all Bring! calls

fix(shopping): graceful Bring! missing credentials handling
- Show friendly message with link to settings instead of raw PHP error
- Add shopping.bring_not_configured i18n key in IT/EN/DE

Bump app.js cache buster to v=20260504b
2026-05-04 16:52:22 +00:00
dadaloop82 d9602df3c3 fix(settings): ReferenceError s not defined in _populateLanguageSelector
Screensaver toggle init was incorrectly placed inside _populateLanguageSelector()
where 's' (getSettings()) is not in scope. Moved to loadSettingsUI() alongside
the other preference checkboxes where 's = getSettings()' is already defined.
2026-05-04 16:35:54 +00:00
dadaloop82 1fb00d48a9 fix(shopping): prevent cleanup from removing user-manually-added items
- cleanupObsoleteBringItems now protects items the user explicitly added
  from the suggestions panel via a '_userPinnedBring' localStorage set
  (30-day TTL, cleared on force-sync)
- cleanup now protects ALL smart-predicted items (any urgency), not only
  critical/high — if the algorithm still flags it, it should stay in list
- autoAddCriticalItems: bypass purchased-blocklist for depleted items
  (current_qty=0) so products that ran out are always re-added to Bring
- forceSyncBring also clears _userPinnedBring for a full reset
2026-05-04 16:34:34 +00:00
dadaloop82 108f3ef283 feat(settings): add screensaver toggle in Language tab (default off)
Toggle appears in the Language settings tab, below the language selector.
Default: disabled. When disabled, initInactivityWatcher() exits early so
the screensaver never activates. i18n added for it/en/de.
2026-05-04 16:17:36 +00:00
dadaloop82 bc0beea090 fix(header): full redesign — left-aligned title, uniform buttons, update badge
- Title always left-aligned (was centered via 3-col flex trick)
- In kiosk mode: exit/refresh buttons appear left of title via header-left
- All action buttons unified as .header-btn (42×42px, consistent style)
- Scan button: 48×48px + pulse animation to stand out from others
- Gemini button: no longer misuses header-scan-btn class; own indigo tint
- Scale status: same 42×42px .header-btn shape with colored .scale-dot
  inside instead of a tiny 22px standalone circle
- Update notification: uses #header-update-badge beside the title instead
  of replacing title innerHTML (title never disappears anymore)
- Fixed _scaleUpdateStatus() to preserve header-btn class on className reset
2026-05-04 16:12:43 +00:00
dadaloop82 e68d11a7fc fix(pwa): handle orientation.lock() promise rejection silently
screen.orientation.lock() returns a Promise; the old try/catch only
caught synchronous errors, leaving the rejection unhandled and triggering
the auto-reporter (issue #8). Added .catch(()=>{}) to suppress it.
Also fixed CI: add sleep+retry around gh release create to avoid 502
race condition after delete.

Closes #8
2026-05-04 15:46:17 +00:00
dadaloop82 f625e55526 fix: header 3-col layout, shopping_name migration, demo mode UI, kiosk buttons left
Header layout:
- Redesign header-content as 3-column flex (left / center / right)
- Add #header-left div: dedicated slot for kiosk buttons (empty by default)
- header-title: flex-shrink auto, no more position:absolute centering hack
- header-actions: flex:1 1 0 + justify-content:flex-end (right)
- header-left: flex:1 1 0 (left) — equal width balances the title visually

Kiosk exit/refresh buttons:
- _injectKioskOverlay() now appends to #header-left instead of
  insertBefore(firstChild) — buttons appear on LEFT, not mixed with center

DB migration:
- Add shopping_name TEXT DEFAULT '' to CREATE TABLE products schema
- Add ALTER TABLE migration in migrateDB() for existing databases
- Avoids repeated ALTER TABLE in seed code on every request

Demo mode UI:
- _applyDemoModeUI(): hides ⚙️ settings nav button in demo mode
- Suppresses first-run setup wizard when _demoMode === true
- Shows a small DEMO badge in header-left
- Called from both syncSettingsFromDB() and _initApp()
2026-05-04 12:52:29 +00:00
dadaloop82 bf27469228 security: fix 3 critical vulnerabilities
1. Remove raw API key from get_settings response
   - getServerSettings() no longer returns gemini_key in plain text
   - Only gemini_key_set (boolean) and settings_token_set (boolean)
   - JS updated to only check gemini_key_set (removes stale gemini_key fallback)

2. Protect save_settings with SETTINGS_TOKEN
   - If SETTINGS_TOKEN is set in .env, all save_settings calls must
     include matching X-Settings-Token header (uses hash_equals)
   - Empty token = no protection (backwards-compatible default)
   - Settings UI (Security tab) has a token input field
   - Wrong/missing token returns HTTP 403 with error 'unauthorized'
   - JS shows '🔒 Token non valido o mancante' on 403

3. DEMO_MODE native blocking in PHP
   - DEMO_MODE=false added to .env (default off)
   - When DEMO_MODE=true, all write actions return HTTP 403 before routing
   - Blocked: save_settings, product_save/delete/merge, inventory_add/use/update/remove,
     dismiss_anomaly, bring_add/remove/sync
   - demo_mode flag exposed via get_settings so JS can adapt UI
2026-05-04 06:20:23 +00:00
dadaloop82 529c09fda3 feat(ai): 3 new AI features — product storage hint, shopping tips, anomaly explain
Feature 1: AI product storage/shelf-life hint
- New API: gemini_product_hint → {location, expiry_days, reason}
- After opening the add form, Gemini suggests optimal storage and expiry
- Shown inline next to expiry estimate as a subtle AI badge with tooltip
- Also updates location buttons if AI suggests a different location
- Cached permanently in food_facts_cache.json (per name+lang)

Feature 2: AI-enriched shopping suggestions
- New API: gemini_shopping_enrich → adds tip field to each suggestion
- After bring_suggest renders, Gemini adds practical buying/storing tips
- Tips shown inline under each suggestion item in indigo italic text
- Cached per item list + lang in food_facts_cache.json

Feature 3: AI anomaly explanation
- New API: gemini_anomaly_explain → plain-language explanation
- '🤖 Spiega' button added to anomaly banners (when Gemini available)
- Explains in 2-3 conversational sentences why the discrepancy likely happened
- Replaces technical banner detail text with friendly explanation
- No caching (anomaly context is always specific)
2026-05-04 06:01:44 +00:00
dadaloop82 a85390b498 feat(ai): guard all Gemini features when API key is not configured
Added _geminiAvailable global flag (false by default):
- Set in _initApp() from serverSettings.gemini_key_set after app loads
- Updated in syncSettingsFromDB() so it stays current if key is added later

Added _requireGemini() helper:
- Returns true if Gemini key is configured → proceed normally
- Returns false + shows a warning toast if key is missing → abort

Added _updateGeminiButtonState():
- Adds .header-btn-no-ai CSS class to Gemini button when key is missing:
  greyed out, slight grayscale filter, amber dot badge in corner
- Updates button tooltip to explain what to do
- Removes class/restores normal appearance when key is present

All 6 AI entry points now call _requireGemini() as first line:
  captureForAI()          — AI product identification from scan page
  captureForAIFormFill()  — AI product fill in manual add form
  scanExpiryWithAI()      — AI expiry date reader
  openRecipeDialog()      — recipe generation dialog
  generateRecipe()        — recipe generation (direct call path)
  quickRecipeSuggestion() — quick expiring-products recipe (→ chat)
  showPage('chat')        — Gemini chat page

Previously: user would click the button, camera would open, API call
would fail, and only THEN see an error message deep in the flow.
Now: blocked immediately at the entry point with a clear toast.
2026-05-04 05:50:30 +00:00
dadaloop82 d635635577 fix(ui): update notification inline in header title area, no full-page banner
Instead of a fixed banner that covers the top of the page, the update
notification now replaces only the header title area (the centered title):
- .header-title content is swapped in-place with an animated pill:
    ⬆️ v1.x.x  [Aggiorna]  ✕
- Pulsing animation (header-update-pulse) draws attention without being
  intrusive; camera and Gemini buttons stay exactly where they are
- [Aggiorna] button does window.location.reload()
- [✕] dismisses: for a release update stores publishedAt so it won't reappear;
  for a server deploy simply restores title (reappears next 5-min check)
- Auto-restores after 60 s without marking as seen
- Removed the old fixed position:fixed banner entirely
2026-05-04 05:41:38 +00:00
dadaloop82 968e26cc6a fix(ui): header title always centered, actions to right, real-time update detection
CSS header fixes:
- .header-content: justify-content:flex-end so .header-actions (camera, Gemini)
  naturally stays at the right edge as a flex child
- .header-title: removed overflow:hidden and text-overflow:ellipsis that were
  clipping the version number; title stays absolutely centered
- Cleaned up unused max-width:none and margin:0 from previous broken attempt

Real-time webapp update detection:
- Added module-level _loadedVersion captured at page load (version in HTML header)
- _checkWebappUpdate() now has two checks:
  1. webapp_version from server vs _loadedVersion: if different, the server was
     updated since this page was loaded → show '🔄 Nuova versione disponibile' banner
  2. GitHub latest release vs _loadedVersion (existing behaviour)
  Different banner messages: deploy-changed shows simple reload prompt;
  release-newer shows version + changelog link (same as before)
- TTL reduced from 6h to 5 min so updates are detected quickly
- _checkWebappUpdate() now also fires on visibilitychange so the user sees
  the banner as soon as they return to the tab after a deploy
2026-05-04 05:32:57 +00:00
dadaloop82 6756b16ecb fix(ui): center header title on tablet + add skeleton loader to spesa stat card
Header title centering:
- .header-content: remove max-width:600px, use position:relative + justify-content:center
- .header-title: position:absolute; left:50%; transform:translateX(-50%)
  so the title is always at the exact center of the header regardless of
  screen width or how many action buttons are on the right
- Added max-width:calc(100% - 200px) to prevent overlap with action buttons
  on narrow screens

Spesa skeleton preloader:
- index.html: add stat-loading class to stat-spesa (was missing, other 3 had it)
- app.js showPage('dashboard'): add 'spesa' to the skeleton init array
- app.js loadShoppingCount(): remove stat-loading class after data loads
  (like loadDashboard() does for the other 3 locations)
2026-05-04 05:26:38 +00:00
dadaloop82 a701e5a239 fix: 'Aggiorna ora' banner + APK conflict auto-retry after uninstall
Webapp update banner:
- 'Vedi novità' link replaced with 'Aggiorna ora' button
- Clicking 'Aggiorna ora' does a hard page reload (?bust=timestamp)
  which forces the browser to fetch the latest files from the server
- GitHub release URL kept as a small secondary 'novità' link

APK install conflict (kiosk + scale gateway):
- STATUS_PENDING_USER_ACTION: changed startActivity → startActivityForResult
  (kiosk) / installConfirmLauncher.launch (gateway) so we get notified
  if the system installer fails due to signature conflict
- On non-OK result from system installer: show AlertDialog offering to
  uninstall, using UNINSTALL_REQUEST / uninstallLauncher
- STATUS_FAILURE_CONFLICT/INCOMPATIBLE: same uninstall flow
- After uninstall completes, install automatically retries with the
  saved APK file (pendingInstallFile) — no manual re-download needed
- Gateway: also saves destFile to pendingInstallFile at download time
2026-05-03 18:22:29 +00:00
dadaloop82 eb2e8fa1d0 fix: _checkWebappUpdate ReferenceError — convert IIFE to function declaration
The function was wrapped as a named function expression (function foo(){})
which scopes the name 'foo' only to the function body, not the outer scope.
_initApp called setTimeout(_checkWebappUpdate, 6000) causing:
  ReferenceError: _checkWebappUpdate is not defined

Fix: remove the (...)​ wrapper so it becomes a regular function declaration
visible to the entire module scope.

Fixes #7
2026-05-03 18:14:15 +00:00
dadaloop82 73fbb73974 fix: APK install conflict (PackageInstaller) + dashboard stat skeleton
APK install conflict:
- Replace ACTION_VIEW-based install with PackageInstaller.Session API (API 21+)
- PackageInstaller gives us the actual install status via BroadcastReceiver:
  STATUS_PENDING_USER_ACTION → launch system confirmation dialog automatically
  STATUS_SUCCESS → success toast
  STATUS_FAILURE_CONFLICT/INCOMPATIBLE → show AlertDialog offering to
    uninstall the old version (ACTION_DELETE) so user can re-download and install
- FileProvider no longer needed for install (still kept for other uses)
- Kiosk: derive target package from filename (gateway vs kiosk self-update)

Dashboard 0-flash:
- Replace hardcoded 0 in HTML stat-value spans with ... placeholder
- Add .stat-loading CSS class: shimmer skeleton animation (gradient sweep)
- showPage(dashboard): set ... + stat-loading before API call
- loadDashboard: remove stat-loading class and set real count after data arrives
2026-05-03 17:51:18 +00:00
dadaloop82 58e69625bd fix: preloader + update notification robustness
- Add full-screen CSS preloader to webapp (fades out when _initApp completes)
- Defer _checkWebappUpdate() to 6s after app init so it does not compete
  with startup API calls (fixes perceived slowness on first load)
- Switch update-check throttle from sessionStorage to localStorage (6h TTL);
  use release published_at instead of version string for comparison, so the
  banner correctly appears when a new release is published regardless of whether
  the tag is a semver or the rolling "latest" tag
- PHP _isLatestVersion(): return true (do not suppress error reports) when
  tag_name is non-semver (e.g. "latest") — was incorrectly blocking ALL reports
- Kiosk checkForUpdates(): show banner only when the release asset actually
  contains an APK for the component; handle non-semver tag by treating it
  as always-update (prevents silent no-op with rolling "latest" tag)
- Scale gateway checkForUpdates(): same non-semver fix; apkUrl now defaults
  to empty and bails out if no matching APK found in assets (prevents 404 install)
2026-05-03 17:46:42 +00:00
dadaloop82 9ef2a53aeb fix: hide update banner + app-header during cooking mode; raise overlay z-index
- .cooking-overlay z-index 500 → 99998 (above everything)
- body.cooking-mode-active: hide #_evershelf_update_banner and .app-header
- .cooking-mode-active #modal-overlay z-index 600 → 99999
2026-05-03 17:33:24 +00:00
dadaloop82 076cf13ed8 feat: version-aware error reporting, XOR token, update banners
in both PHP (api/index.php) and Scale Gateway (ErrorReporter.kt)
- Add _isLatestVersion() / _latestReleaseTag() / _appVersion() helpers in PHP;
  skip GitHub issue creation if caller is not on the latest released version
- Add checkUpdate() PHP endpoint (GET api/?action=check_update, no auth required)
- Webapp (app.js): fetch check_update on load, show dismissible amber top-banner
  when a newer GitHub release is available; auto-dismiss after 20 s
- Kiosk (KioskActivity.kt + activity_kiosk.xml): replace old JS bottom-banner with
  native Android top-banner; real APK download via DownloadManager + PackageInstaller
- Scale Gateway (MainActivity.kt + activity_main.xml): same native top-banner
  with checkForUpdates() / showNativeUpdateBanner() / triggerApkDownload() / installApk()
2026-05-03 17:24:26 +00:00
dadaloop82 f2e151d89b feat: centralized error reporting → auto GitHub Issues
PHP (api/index.php):
- reportError() endpoint (POST ?action=report_error): accepts source/type/message/stack/context/ua/version
- _createOrCommentGithubIssue(): creates new issue OR adds comment on existing one (dedup by sha1 fingerprint via GitHub search API)
- _appendErrorLog(): local data/error_reports.log fallback (500 KB rotation)
- _phpErrorReport(): called by set_exception_handler + register_shutdown_function → catches all PHP fatals and uncaught exceptions
- _githubRequest(): minimal curl-based GitHub REST v3 helper
- Rate limit bucket: error_report (20 req/min)
- Labels auto-created: auto-report, php-crash, js-error, kiosk-error, scale-error

JS (assets/js/app.js):
- reportError(payload): single POST to report_error, session-level dedup via _reportedFingerprints Set
- window.onerror: reports uncaught-error with message+stack+location context
- window.unhandledrejection: reports unhandled-promise with reason+stack
- api(): reports api-server-error on HTTP 5xx responses

Android Kiosk:
- ErrorReporter.kt: singleton with init(context, serverUrl), report(Throwable), reportMessage(type, message)
  - Thread.setDefaultUncaughtExceptionHandler → catches ALL unhandled JVM crashes
  - Async executor (single thread), per-session fingerprint dedup, synchronous fallback for crash handler
  - doPost(): HttpURLConnection POST to /api/?action=report_error with device/version info
- KioskActivity: ErrorReporter.init() in onCreate + finishWizard()
  - onReceivedError: reports webview-load-error with URL + error code
  - onConsoleMessage: reports webview-js-error for ERROR level console messages

Config: GITHUB_ISSUE_TOKEN + GITHUB_REPO added to .env.example
2026-05-03 15:36:03 +00:00
dadaloop82 a6c2fb93cf feat: offline OCR (Tesseract) + embedding category classifier (@xenova/transformers)
Tesseract OCR (PHP, server-side):
- Dockerfile: adds tesseract-ocr + tesseract-ocr-ita + libgd-dev (gd extension)
- api/index.php: new tesseractReadExpiry() — decodes base64 image, pre-processes with GD (2× upscale, greyscale, auto-contrast, sharpen), runs tesseract CLI with ita+eng PSM-6, extracts date with multi-pattern regex (DD/MM/YYYY, MM/YYYY, ISO, named-month), returns YYYY-MM-DD + confidence
- geminiReadExpiry() now: (1) tries Tesseract first; (2) falls back to Gemini Vision if OCR returns null or no date found; (3) passes source ('ocr'|'gemini') in response

@xenova/transformers embedding classifier (browser-side):
- index.html: ES-module bootstrap that lazy-loads 'Xenova/all-MiniLM-L6-v2' quantized (~23 MB, cached in browser) via window._getCategoryPipeline(); pre-warms on first scan page visit
- assets/js/app.js: classifyCategoryByEmbedding(name) — embeds product name + 16 category anchor descriptions, cosine similarity, threshold 0.30; results cached in _embeddingCache Map
- autoDetectCategory(): after keyword map misses, fires classifyCategoryByEmbedding async and updates select when resolved (respects manuallySet flag)
- createQuickProduct(): if regex returned 'altro', silently patches category with embedding result via a background api call
2026-05-03 13:17:14 +00:00
dadaloop82 c814d99d1f feat: smart use-all context, scale baseline reset, freezer-ok alert suppression, conf qty fix, low-stock finish button
- submitUseAll() now detects opened packages: if current location has an opened pack, finishes only that; if exactly one opened pack exists elsewhere, uses it automatically; multiple opened packs → disambiguation modal
- quickUse() resets scale baseline on page open so stale weight doesn't immediately trigger auto-fill
- Expired alerts (dashboard + banner) now filter out freezer items within their safety window (level='ok')
- Review banner: conf unit quantity displayed as sub-unit total (e.g. 800g) instead of raw pack count; high-qty threshold evaluated on sub-unit volume to prevent '400 confezioni' nonsense
- Low-stock review banner gains 'È finito tutto' button → new bannerFinishAll() handler
- New _submitUseAllAt() helper and _showUseAllDisambiguation() modal
- New translation keys: toast_opened_finished, disambiguation_hint, disambiguation_all, banner_review_action_finish (it/en/de)
2026-05-03 13:12:35 +00:00
dadaloop82 4e583127dd Banner: suppress low-qty alert when sibling product entries exist elsewhere
A partially-used fridge entry (e.g. 191 ml of milk) triggered a
'suspiciously low quantity' banner even when sealed packages of the
same product were present in another location (e.g. pantry).

Fix: before pushing a low-qty review alert, group all inventory rows
by product key (barcode, or name+brand fallback). If any sibling entry
for the same product has qty > 0 in a different row, skip the alert.
High-qty and suspicious package-size alerts are unaffected.
2026-04-30 05:28:43 +00:00
dadaloop82 8359b14931 Banner: adapt expired icon/color/title to safety level (non-alarmist)
- ok level (long-life/freezer safe): green banner,  icon, 'Scaduto (ancora ok)'
- warning level: amber banner, 👀 icon, 'Scaduto (controlla)'
- danger level: unchanged red 🚫 banner
- Added banner-expired-ok / banner-expired-warning CSS variants
- Added expiry.expired_suffix_ok / expired_suffix_warning i18n keys (IT/EN/DE)
- Updated README and CHANGELOG
2026-04-30 05:21:50 +00:00
dadaloop82 8d02e76501 Remove interval% from annual waste info; fix conf whole-qty using package expiry not opened shelf-life 2026-04-29 17:05:38 +00:00
dadaloop82 e71ef3aba3 Dashboard: move waste-chart above expiring; fix opened-items conf split, expiry cache, AI validation, MAX_SHOWN 20; remove DupliClick from README 2026-04-29 17:02:10 +00:00
dadaloop82 3c9fe7dfea Remove all Dupliclick/Spesa integration; merge annual waste info into status line 2026-04-29 16:52:36 +00:00
dadaloop82 9c1346019c Waste section: neutral terminology, drop trend-cards & meals badge, annual/range in bar, 5-min facts, AW facts in screensaver 2026-04-29 16:27:05 +00:00
dadaloop82 67f58172e5 Waste: bigger fonts, auto-fit badge row, 5-min rotation 2026-04-29 16:19:54 +00:00
dadaloop82 85274948b4 Waste section: single stacked comparison bar instead of two rows 2026-04-29 16:13:43 +00:00
dadaloop82 da46fec174 Fix anomaly banner when expected_qty is negative (untracked initial stock) 2026-04-29 15:26:59 +00:00
dadaloop82 22266cb620 Fix sealed/opened expiry; AI shelf-life cache; redesign waste UI 2026-04-29 06:42:21 +00:00
dadaloop82 e002955173 Anti-waste: daily food-facts API, 3-badge rotating row with fade 2026-04-29 06:28:46 +00:00
dadaloop82 7c4dd99289 Anti-waste: themed border, rich info badges, fix latte di montagna shelf-life, exclude opened from expiring_soon 2026-04-29 06:19:35 +00:00