Commit Graph

47 Commits

Author SHA1 Message Date
dadaloop82 e4869c4308 fix(kiosk): gateway install hardening + diagnostic dialog on failure
- installApk(): add runtime canRequestPackageInstalls() check with user dialog
  (permission may have been revoked or not active even if declared in manifest)
- STATUS_FAILURE else branch: show AlertDialog with full diagnostics on screen
  (status code, message, APK size, Android version, device model) so the
  problem is visible even when the server error report fails to deliver
- Dialog has Riprova button (retries installWithPackageInstaller immediately)
  and Salta button (skips to gateway status check)
- INSTALL_PERM_REQUEST onActivityResult: resume from installApk() if a local
  APK file is already present, not just from triggerApkDownload()
- ErrorReporter context enriched with device model string
2026-05-04 18:24:56 +00:00
dadaloop82 6d13b895ea fix(kiosk): gateway install STATUS_FAILURE root cause
Two bugs caused the gateway APK install to always fail with status=1:

1. setAppPackageName() removed from SessionParams
   This optional call forces the installer to verify the package name
   against the APK's manifest. On some OEM/Android versions this
   comparison fails even when the name is correct, returning the generic
   STATUS_FAILURE (1) with no EXTRA_STATUS_MESSAGE. Removing it lets
   the installer proceed without the extra check.

2. BroadcastReceiver was unregistered on STATUS_PENDING_USER_ACTION
   On Android 11+ the final install result (STATUS_SUCCESS/STATUS_FAILURE)
   arrives as a SECOND broadcast AFTER the user confirms the dialog.
   The receiver was being unregistered immediately on the first broadcast
   (PENDING_USER_ACTION), so the final result was never received.
   Fix: only unregister on terminal statuses (SUCCESS, FAILURE, ABORTED).

Additional improvements:
- STATUS_FAILURE_ABORTED (-1) handled explicitly: resets UI without
  showing an error (user just pressed back on the confirmation dialog)
- session.abandon() called on exception instead of letting .use{} close
- ErrorReporter now includes apk_kb and android API level in context
- onActivityResult(INSTALL_CONFIRM_REQUEST) no longer sets success/failure
  UI (the BroadcastReceiver is responsible for the final result)
2026-05-04 18:07:35 +00:00
dadaloop82 4f6592b749 fix: install_failure type mismatch — issue never reached GitHub
SetupActivity was sending type 'install-failure' (hyphen) but the PHP
version-guard bypass list only checked for 'install_failure' (underscore).
Result: if the kiosk was not on the latest released version the error was
silently discarded and no GitHub issue was created.

Fix:
- SetupActivity: change type to 'install_failure' (underscore, consistent
  with KioskActivity which already used the correct name)
- api/index.php: add 'install-failure' (hyphen) to the bypass list as
  defensive fallback so old APK builds already in the field are covered too
2026-05-04 17:45:46 +00:00
dadaloop82 5af62e61cd fix(kiosk): APK install error reporting + gateway pre-configuration
ErrorReporter:
- Init ErrorReporter at Setup onCreate using any previously saved URL
  (before the fix, init() was only called at step 3, so install errors
   happening in step 4 were silently dropped)
- Add ErrorReporter.reportMessage() call in the STATUS_FAILURE else branch
  of installWithPackageInstaller (was showing error UI but sending nothing)

Gateway pre-configuration:
- finishSetup() now detects has_scale=true + gateway installed
- If so, POSTs scale_enabled=true + scale_gateway_url=ws://127.0.0.1:8765
  to the EverShelf server's save_settings API endpoint
- This means the webapp works with the scale out-of-the-box after setup
  without the user having to go into web Settings and configure it manually
2026-05-04 17:36:11 +00:00
dadaloop82 d1040e5747 fix(kiosk): add ports 443 and 8443 to auto-discovery scan list 2026-05-04 17:17:41 +00:00
dadaloop82 cfcba32c45 fix(kiosk): permissions button transforms to 'Continue' after grant + fix subnet detection
Permissions step:
- Add btnGrantPerms as class field (was only inline findViewById)
- Extract onPermissionsGranted() helper: transforms button to green ' Permessi
  concessi — Continua →' and sets click listener to showStep(3)
  instead of calling onPermissionsGranted() which advances to step 3 (Server)
- Remove the 600ms auto-advance; user controls when to proceed
- Add setup_perms_granted_next string in EN/IT/DE

Network discovery — wrong subnet fix:
- Skip virtual/VPN/cellular interfaces: tun, ppp, rmnet, pdp, ccmni, dummy, sit,
  gre, v4-, v6-, p2p, ham, nordlynx prefixes
- Also skip intf.isVirtual interfaces
- Sort: wlan*/eth* interfaces first (highest priority), others after
- Show detected subnet(s) in UI immediately before scan starts
2026-05-04 17:11:17 +00:00
dadaloop82 09fd122718 fix(kiosk): rewrite autoDiscover — real-time IP feedback + CompletionService + TCP pre-check
Problems fixed:
- f.get() sequential collection blocked on timed-out futures in submission order
  → replaced with ExecutorCompletionService: results collected as soon as ready
- WifiManager.getConnectionInfo() deprecated on Android 10+, could return IP=0
  → replaced with NetworkInterface.getNetworkInterfaces() for subnet detection
- No real-time feedback: status stuck on 'Scanning…' throughout
  → UI updated every ~120ms showing current IP:port (n/total)
- TCP socket pre-check (600ms) before HTTP probe: filters unreachable hosts instantly
  → reduces scan time from minutes to seconds on typical /24 networks
- executor.shutdownNow() cancels remaining tasks the moment server is found
- 60-thread pool instead of 40 for faster parallel scanning
- Remove unused WifiManager import
2026-05-04 17:01:01 +00:00
dadaloop82 4df06b01f4 feat(kiosk): language selection as first setup step + screensaver step
- SetupActivity: new Step 0 — language picker (IT/EN/DE) with large buttons,
  hardcoded trilingual title so it's always readable; saves 'kiosk_language' pref,
  calls recreate() via onSaveInstanceState to reload the Activity in chosen locale
- SetupActivity: new Step 5 — screensaver toggle (before Done), saves 'screensaver_enabled'
- All existing steps shifted: Welcome→1, Permissions→2, Server→3, Scale→4, Done→6
- Progress dots updated to 5 dots (steps 1-5)
- attachBaseContext override in SetupActivity, KioskActivity, SettingsActivity to
  apply the saved locale to all Activities via SetupActivity.applyLocale()
- buildSummary now shows language, screensaver setting, and scale status
- New string resources: setup_screensaver_*, summary_lang, summary_scale_skip,
  summary_screensaver_on/off in IT, EN, DE
2026-05-04 16:47:56 +00:00
dadaloop82 3649be848a feat(kiosk): add screensaver toggle in settings (default off) 2026-05-04 15:42:51 +00:00
dadaloop82 fa9c52e997 feat(kiosk): complete setup wizard overhaul
- New SetupActivity (5 steps, NOT kiosk-locked, always has Exit button)
  - Step 0: Welcome — logo, tagline, privacy/offline notice, feature list
  - Step 1: Permissions — explain camera/mic/install before requesting
  - Step 2: Server — URL input + LAN auto-discovery + fixed API validation
  - Step 3: Scale — YES/NO question first; gateway info + install only after YES
  - Step 4: Done — summary + Launch button
- KioskActivity simplified: removed all wizard code (~700 lines)
  - kiosk lock only enabled AFTER setup completes
  - starts SetupActivity on first run; launches WebView on return
- activity_kiosk.xml simplified: removed wizard ScrollView
- AndroidManifest: added SetupActivity declaration
- Fixed testConnection(): validates /api/index.php?action=get_settings JSON
- Fixed scale freeze: scale YES/NO asked before any connection check
- Fixed gateway install status=1: same PackageInstaller flow + signature conflict dialog
- Auto-discovery: parallel scan of LAN (30 threads, ports 80/8080, common subnets)
- i18n: new setup strings in en/it/de
2026-05-04 15:40:45 +00:00
dadaloop82 7c61ae61bb fix(kiosk): fix ByteArray type inference error in APK magic-byte check
The try expression had a spurious 'true' result in one branch which
made Kotlin infer the type as Any? instead of ByteArray?.
Simplified to a single try block with explicit type annotation
ByteArray? to eliminate the ambiguity.
2026-05-03 20:14:19 +00:00
dadaloop82 22e506bd66 fix(kiosk): 4 bug fix — uninstall loop, PHP check, APK validation, ErrorReporter init
Bug 1 — Uninstall loop (kiosk lock task blocks system uninstall UI):
  startActivityForResult(ACTION_DELETE) was called while lock task was
  active. The system uninstall activity is not in the lock task whitelist
  so it either silently fails or creates an unresolvable loop.
  Fix: call disableKioskLock() immediately before every ACTION_DELETE
  intent (3 call sites). Call enableKioskLock() at the start of
  onActivityResult(UNINSTALL_REQUEST) before retrying install.
  Added 600 ms delay after uninstall so PackageManager finishes cleanup.

Bug 2 — Step 2 only checks HTTP connectivity, not PHP API:
  testConnection() was checking the root URL only. A generic web server
  could pass while the EverShelf PHP API was absent.
  Fix: after HTTP 200-399 on the root URL, do a second GET to
  /api/?action=check_update and check the response body contains
  'latest_tag'|'webapp_version'|'ok'. Shows:
     Server EverShelf trovato e API attiva!
    ⚠  Server raggiungibile ma API PHP non trovata (codice N)

Bug 3 — STATUS_FAILURE=1 even after uninstall (invalid APK file):
  GitHub DownloadManager follows redirects; if the release asset does
  not exist yet, GitHub returns a 404 HTML page but DownloadManager
  still reports STATUS_SUCCESSFUL. PackageInstaller then tries to parse
  HTML as an APK and returns STATUS_FAILURE=1.
  Fix: validate APK magic bytes (0x504B = 'PK') before calling
  installWithPackageInstaller. If invalid: show error, delete corrupt
  file, send ErrorReporter event, re-enable retry button.
  Also renamed install error string to install_error_install (separate
  from install_error_download) for clarity.

Bug 4 — ErrorReporter.serverBaseUrl empty during wizard install:
  ErrorReporter.init() is called in onCreate() with the saved URL.
  On first setup the URL is typed in step 2 and saved to prefs, but
  ErrorReporter still has serverBaseUrl='' for the rest of that session.
  Any install error in step 3 silently failed to POST.
  Fix: call ErrorReporter.init(this, url) in btnStep2Next immediately
  after prefs.edit().putString(KEY_URL, url) so step 3 has a live URL.
2026-05-03 20:10:40 +00:00
dadaloop82 38eb66cfbf feat(kiosk): server reachability check in step 3 + uninstall-on-generic-failure
Server check (wizard step 3):
- New horizontal card above the scale question always shows server status
  as soon as step 3 is entered:  checking →  reachable / ⚠️ not reachable
- Pings GET $serverUrl/api/?action=check_update (5 s timeout)
- If reachable: 'Error reporting active — failures sent to GitHub Issues'
- If not reachable: 'Check the URL in step 2' warning
- checkServerReachability() called every time goToStep(3) runs
- Strings added in EN / IT / DE

Signature-conflict fallback (else branch in installWithPackageInstaller):
- When PackageInstaller returns a generic STATUS_FAILURE and the target
  package is already installed, a signature conflict is the most likely
  cause (CONFLICT/INCOMPATIBLE are caught separately earlier)
- New AlertDialog: 'Disinstalla e riprova' → startActivityForResult
  ACTION_DELETE → UNINSTALL_REQUEST → auto-retries install on return
- Only shown when all else has already failed
2026-05-03 19:35:20 +00:00
dadaloop82 15e1dfbd69 fix(kiosk): STATUS_FAILURE=1 (wrong package) + issue version-guard bypass
Bug 1 — Root cause of PackageInstaller STATUS_FAILURE=1:
  The dest file is always named 'evershelf-update.apk'. installApk()
  was trying to detect 'gateway' in the filename — always false.
  So setAppPackageName() was always passed 'it.dadaloop.evershelf.kiosk'
  even when installing the gateway APK (package scalegate).
  PackageInstaller rejects the mismatch with STATUS_FAILURE=1.

  Fix: save apkUrl into pendingApkDownloadUrl at the TOP of
  triggerApkDownload() (not only in the permission branch), then derive
  targetPkg from the URL (which does contain 'gateway'/'scale') instead
  of the filename.

Bug 2 — Install errors not reaching GitHub Issues:
  PHP reportError() has a version guard: if the client version is not
  the latest release, it silently skips GitHub issue creation.
  A device that is FAILING TO INSTALL an update is by definition on an
  old version, so every install error was silently dropped.

  Fix: bypass the version guard for types install_download_failed,
  install_failure, install_packager_exception.
2026-05-03 19:29:04 +00:00
dadaloop82 645162f063 fix(kiosk,gateway): use RECEIVER_EXPORTED for DownloadManager broadcast
Root cause of 'stuck on downloading' bug (Android 13+):

DownloadManager.ACTION_DOWNLOAD_COMPLETE is sent by the system process,
which is external to our app. Registering the receiver with
RECEIVER_NOT_EXPORTED silently drops the broadcast — the BroadcastReceiver
never fires, the install never starts, and the UI stays frozen at
whatever progress percentage the poller last saw.

Fix: use RECEIVER_EXPORTED for the DownloadManager completion receiver in
both kiosk and scale-gateway apps.

The PackageInstaller result receiver (internal PendingIntent broadcast,
same package) correctly keeps RECEIVER_NOT_EXPORTED — that one is
intentionally app-private.
2026-05-03 19:19:44 +00:00
dadaloop82 0dac10d05e fix(kiosk): fix compile error — ErrorReporter.report() → reportMessage()
ErrorReporter.report() takes a Throwable as first argument.
The three new calls added in fe633c9 incorrectly passed 'this'
(Context) instead, causing compileDebugKotlin to fail.

Replace with ErrorReporter.reportMessage(type, message) which
is the correct overload for non-exception error events.
2026-05-03 19:12:42 +00:00
dadaloop82 fe633c97cb fix(kiosk): real-time download progress bar + ErrorReporter on failures
Problem: tapping 'Aggiorna Scale Gateway' gave no visible feedback after
the button was pressed — user could not tell if the download was
happening, stuck, or had silently failed.

Changes:
- layout: add horizontal ProgressBar (determinate) + percentage TextView
  inside the wizard step-3 status card
- layout: add thin ProgressBar (4 dp) at the bottom of the update banner
  (banner changed to vertical orientation to accommodate it)
- startDownloadProgressPoll(downloadId): polls DownloadManager every
  500 ms, reads COLUMN_BYTES_DOWNLOADED_SO_FAR and COLUMN_TOTAL_SIZE_BYTES,
  updates status card + banner with 'Download: 45% (18.2 MB / 40.5 MB)'
- setInstallUI(): new 'progress' parameter (-2 = hide, -1 = indeterminate,
  0-100 = determinate) and 'progressText' for the label under the bar
  — both bars updated in sync
- Status transitions now visible:
     Download: 45% [====----] 18.2 MB / 40.5 MB
     Installazione in corso… [~~~~] (indeterminate)
     + 'Conferma nel dialog…'
     Installato! → bar hidden, gateway status re-checked after 3 s
     + error detail → bar hidden, button re-enabled as '↩ Riprova'
- All error paths (download fail, PackageInstaller exception, installer
  failure status) now call ErrorReporter.report() → GitHub Issue created
  automatically so failures are tracked without user intervention
- Dismiss button also cancels the progress poll + hides the bar
2026-05-03 19:07:19 +00:00
dadaloop82 7d8132a743 fix(kiosk): persistent install progress in UI — no more silent Toasts
Problem: tapping 'Aggiorna ora' showed a fleeting 'Download avviato'
Toast and then nothing — no feedback on download progress, installer
state, success or failure.

Solution — setInstallUI() central helper:
- Updates the wizard step-3 status card (icon + title + detail line)
  OR the update banner (tvUpdateMessage) depending on which is visible
- Always updates and enables/disables the button that triggered the flow

States shown (status card + button text):
   Scaricamento in corso…  (download started)
   Installazione in corso… (download done, PackageInstaller running)
   Installazione in corso… + 'Conferma nel dialog…' (user action needed)
   Installato con successo! (onActivityResult RESULT_OK or STATUS_SUCCESS)
     → after 3 s auto-refreshes gateway status + closes banner
   Download fallito / Installazione non riuscita
     → button re-enabled as '↩ Riprova'

Strings added (EN default + IT + DE):
  install_downloading, install_downloading_detail
  install_installing, install_confirm_detail
  install_success, install_success_detail
  install_error_download, install_error_download_detail
  install_perm_detail, install_btn_retry
2026-05-03 18:54:27 +00:00
dadaloop82 4897da571d feat(kiosk): ask if scale is present; check+update gateway in wizard
Wizard step 3 — 'Do you have a Bluetooth smart scale?':
- New question card with two buttons shown first:
     Yes → reveal gateway status card + bottom nav buttons
    ➡️ No  → save KEY_HAS_SCALE=false, skip to web view
- KEY_HAS_SCALE pref controls whether the gateway is auto-launched
  both after wizard completion and on every subsequent app start
- checkGatewayStatus(): uses string resources (multilingual)
- checkGatewayUpdate(): fetches GitHub release, compares version;
  if gateway needs an update shows '📥 Update Scale Gateway' button
  that triggers triggerApkDownload() (full PackageInstaller flow)
- onResume step-3 re-check only fires when status card is visible
  (i.e. user already answered 'Yes') — handles return from install

Multi-language: strings.xml added for EN (default), IT, DE
strings: wizard_step3_question/yes/no, wizard_gateway_installed/
  not_installed/checking/up_to_date/update_available/update_detail,
  btn_back/launch/launch_no_scale/download_gateway/update_gateway
2026-05-03 18:31:22 +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 9e4a8323c3 chore: bump versions + update CHANGELOG/README for v1.6.0
Webapp:    v1.5.0 → v1.6.0
Kiosk:     v1.3.0 → v1.4.0 (versionCode 4→5)
Scale GW:  v2.0.0 → v2.1.0 (versionCode 6→7)

CI: build-scale-gateway.yml now also triggers on develop branch
    (was main-only, causing APK builds to not run on feature branches)

CHANGELOG: added [1.6.0] entry covering PackageInstaller OTA fixes,
  dashboard skeleton, update banners, cooking mode z-index, XOR token
README: updated 'Recent Updates' section with 1.6.0 highlights
2026-05-03 18:00:46 +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 f9718fee6d fix: APK self-update download+install in kiosk and scale gateway
Root causes fixed:
- REQUEST_INSTALL_PACKAGES permission missing from both manifests
- FileProvider not declared in either manifest (FileProvider.getUriForFile() crashed)
- res/xml/file_paths.xml missing (required by FileProvider)
- setDestinationInExternalPublicDir() used public Downloads dir (needs storage
  permission + FileProvider can't serve it); replaced with getExternalFilesDir()
  which is app-private, needs no permission, and IS accessible by FileProvider
- canRequestPackageInstalls() check returned early with startActivity (fire-and-
  forget); user could never retry. Now uses startActivityForResult/installPermLauncher
  so the download auto-retries when user returns from the Settings screen
- Added download status check (COLUMN_STATUS == STATUS_SUCCESSFUL) before installing
2026-05-03 17:37:45 +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 ea40c8e02b feat: centralized error reporting → GitHub Issues
- PHP (api/index.php): hardcode GH_ISSUE_TOKEN/GH_REPO constants at top of
  file (before exception handler runs); fix $fp_ variable interpolation bug;
  global set_exception_handler + register_shutdown_function; reportError()
  endpoint (POST ?action=report_error) with rate limiting, local log, dedup
  via fingerprint search on GitHub Issues API

- Kiosk (ErrorReporter.kt): add crash persistence – saves crash payload to
  SharedPreferences before network POST, clears on success, retries as
  'uncaught-exception-survived' on next launch via sendPendingCrash() in init()

- Scale Gateway: new ErrorReporter.kt – calls GitHub Issues API directly
  (no relay needed, token hardcoded, scoped Issues R+W only); crash
  persistence via SharedPreferences; MainActivity.kt hooked at onCreate,
  startGatewayServer catch, onError (BLE errors)

Tested end-to-end: issues #3-#6 created and closed during QA.
2026-05-03 17:11:11 +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 95389ebe87 fix: native Android TTS bridge in kiosk — bypass Web Speech API voice issues
On Android WebView, window.speechSynthesis.getVoices() often returns empty
because the Web Speech API cannot enumerate the device's TTS voices.
This caused the kiosk to show 'nessuna voce offline è supportata'.

Changes:
- KioskActivity.kt: initialise Android TextToSpeech engine on startup;
  expose speak(text, rate, pitch), stopSpeech() and isTtsReady() via
  the existing _kioskBridge JavascriptInterface; release TTS in onDestroy.
- app.js (_speakBrowser): when _kioskBridge.speak is available, delegate
  to it instead of using speechSynthesis — works even without offline voice
  packs installed.
- app.js (_initBrowserTtsVoices): show 'Voce nativa Android (kiosk)'
  in the voice dropdown when running inside the kiosk WebView.
- app.js (testTTS): use the bridge path when testing TTS inside the kiosk.
2026-04-27 11:52:30 +00:00
dadaloop82 5bbedc8a3b fix: aggiorna urgenza lista spesa live e più frequentemente
- Cache smart_shopping: 10min → 3min (urgenza fresca)
- backgroundBringSync: ogni 10min → ogni 5min + setInterval continuo
- backgroundBringSync: aggiunge anche 'high' (non solo 'critical') a Bring
- autoSyncUrgencySpecs: aggiorna spec anche se il livello di urgenza sale/scende
- Risultato: Latte/prodotti urgenti compaiono su Bring in max ~5min
2026-04-21 05:32:06 +00:00
dadaloop82 cd4fd55006 fix: sposta bottone settings kiosk in basso-sinistra (non copre più camera/Gemini) 2026-04-20 17:40:19 +00:00
dadaloop82 517a615d11 fix: forza no-cache per JS/CSS + WebView LOAD_NO_CACHE
- .htaccess: aggiunge Cache-Control no-cache/no-store per .js/.css
- Kiosk Kotlin: aggiunge cacheMode=LOAD_NO_CACHE al WebView
- Il pulsante refresh del kiosk ora carica sempre l'ultima versione
2026-04-20 17:34:23 +00:00
dadaloop82 076593c564 fix: sposta pulsanti kiosk (X/refresh) a sinistra, prima del titolo 2026-04-20 17:18:32 +00:00
dadaloop82 2edd5a6ebd fix(kiosk): skip debug keystore config when file does not exist (CI fix) 2026-04-18 18:56:27 +00:00
dadaloop82 07bdfe6b87 fix: kiosk overlay, preferred use-location, scale reconnect, Bring! translation, smart cache invalidation
- Kiosk: replace header-inject overlay with position:fixed div appended to <html>
  so buttons appear regardless of SPA init timing
- Kiosk: bump versionCode 3→4, versionName 1.2.0→1.3.0
- Kiosk: add explicit signingConfigs block (debug keystore) to avoid signature
  mismatch on updates; update banner now shows uninstall instruction + 12s timeout
- Web: v1.4.0 → v1.5.0
- Preferred use-location: remember last N location choices per product; after 3+
  consistent picks auto-select and collapse location picker (with 'cambia' link)
- Scale: call updateScaleReadButtons() on every status change so live-box and
  read button appear instantly on reconnect without manual refresh
- Smart shopping cache: invalidate JSON cache file on every inventory_add and
  inventory_use so next shopping-page load always sees current stock
- isLowStock: conf threshold changed <= 1 → < 1 (1 full pack is not low stock)
- italianToBring: replace substring matching with whole-word matching (min 4 chars)
  to prevent 'gin' matching 'original', 'rum' matching 'crumble', etc.
  Philadelphia original was silently mapped to Gin and skipped as duplicate
- Storico: add undo support (transaction_undo endpoint, undone column, JS undo btn)
- LOG → Storico rename in UI, nav, translations
- Bring! sync: urgency-aware purchased blocklist TTL (critical 30m, high 90m, others 4h)
- forceSyncBring() button to clear all guards and re-sync from scratch
- Scale live-box: position:fixed CSS class, 1.6rem/800 value, direct ml display
- Recipe use modal: scale live-box with 10s stability + 5s auto-confirm countdown
- Recipe use modal: show recipe quantity as highlighted row in Usa popup
2026-04-18 18:50:15 +00:00
dadaloop82 9e2a24def4 feat: v1.3.0 — banner notifications, quick-access, swipe navigation, bug fixes
Added:
- Expired/expiring product banner alerts with use, throw, edit, dismiss actions
- Priority-sorted notifications (expired > expiring > suspicious qty > predictions)
- Touch swipe navigation for banner with dot indicators and arrow buttons
- Quick-access buttons on inventory (4 recent + 8 popular products)
- Auto-refresh banner every 5 min on dashboard
- Edit expiry dates directly from expired/expiring notifications

Fixed:
- Ignore negative BLE scale readings
- Banner re-appearing after edit (confirmation now persisted)
- False consumption predictions when inventory was manually edited
- Kiosk overlay no longer blocks web app header
2026-04-18 05:37:03 +00:00
dadaloop82 ccd59269d4 feat(kiosk): move exit button left of title, add hard-refresh button
- Exit ✕ and Refresh ↻ buttons now appear left of the title
- Refresh clears WebView cache and reloads (picks up web app updates)
- Uses native bridge hardReload() for true cache-busting reload
- Banner alerts reload automatically when dashboard is shown
2026-04-17 05:24:22 +00:00
dadaloop82 9083e25f37 feat(kiosk): add camera, microphone, storage permissions
- Manifest: CAMERA, RECORD_AUDIO, READ_EXTERNAL_STORAGE, READ_MEDIA_IMAGES
- Runtime: requests all permissions on startup (requestAllPermissions)
- WebView: onPermissionRequest checks runtime grants, requests if needed
- onRequestPermissionsResult grants pending WebView permission after user allows
- Camera and mic now work inside the kiosk WebView
2026-04-16 19:07:30 +00:00
dadaloop82 bc70f330f8 fix(kiosk): replace header overlay with exit button next to Gemini
- Removed invisible overlay that was blocking camera/Gemini buttons
- Added a visible ✕ button in the header-actions bar
- Tap shows confirm dialog 'Uscire dalla modalità kiosk?'
- All header buttons (camera, Gemini, scale) work normally again
2026-04-16 18:59:53 +00:00
dadaloop82 c45b8ddbb9 fix(kiosk): remove FLAG_ACTIVITY_LAUNCH_ADJACENT causing split-screen
Was triggering multi-window split mode instead of launching gateway
behind the kiosk. Now uses only FLAG_ACTIVITY_NEW_TASK.
2026-04-16 18:46:27 +00:00
dadaloop82 45040f250c fix: triple-tap exit, update banner auto-dismiss, .env overwrite bug
- Triple-tap exit zone now covers full header height (was 6px, untappable)
- Uses touchend event instead of click for reliable tablet interaction
- JS bridge registered once before loadUrl (not on every page load)
- Update banner auto-dismisses after 3 seconds
- CRITICAL: _finishSetup() no longer sends empty strings to save_settings
  → was overwriting .env values (Gemini key, Bring credentials) with blanks
  → now only sends non-empty values to the API
2026-04-16 17:36:48 +00:00
dadaloop82 e38a6cb7f6 feat(kiosk): true kiosk mode, gateway bg launch, update checks, wizard fix v1.2.0
- Screen pinning (startLockTask) blocks home/recent buttons
- Gateway launches in background, kiosk returns to front after 1.5s
- Injected thin green bar at top of WebView for triple-tap exit
- JavaScript bridge for kiosk exit from WebView context
- Update check via GitHub releases API (every 6h)
- Shows banner in WebView when kiosk/gateway updates available
- Setup wizard no longer re-appears after completion/skip (evershelf_setup_done flag)
- REORDER_TASKS permission for moveTaskToFront
- singleTask launch mode for proper kiosk behavior
- Version bumped to 1.2.0 (versionCode 3)
2026-04-16 17:25:47 +00:00
dadaloop82 5991e666ec fix(kiosk): connection test, gateway detection, splash, triple-tap exit
- Fix 404: test base URL directly instead of appending /api/
- Fix gateway not detected: add <queries> block for Android 11+ package visibility
- Add splash screen with icon + loading spinner (1.5s)
- Add triple-tap on wizard title to exit kiosk mode
- Accept 3xx redirects as valid responses
- New house/shelf vector icon for launcher
- Replace emoji icon with drawable on welcome screen
- Version 1.1.0
2026-04-16 17:04:25 +00:00
dadaloop82 9363bc147e refactor(kiosk): remove built-in scale, add SSL + gateway detection
- Remove BLE/scale code (BleScaleManager, ScaleProtocol, GatewayWebSocketServer, ScaleGatewayService)
- Kiosk is now a pure WebView wrapper — scale handled by standalone gateway app
- Fix SSL certificate error: accept self-signed certs for local servers (WebView + connection test)
- Add gateway APK detection: check if it.dadaloop.evershelf.scalegate is installed
- If gateway installed: show green status, auto-launch on finish
- If not installed: show download link to GitHub releases
- Remove BLE/foreground service permissions from manifest
- Remove java-websocket dependency
- Bump version to 1.1.0
2026-04-16 16:40:11 +00:00
dadaloop82 f8c8dfb990 fix(kiosk): match statusCallback signature (3 params) 2026-04-16 16:27:09 +00:00
dadaloop82 95b6258ad8 feat(kiosk): add setup wizard on first launch
- 3-step wizard: Welcome → Server URL → Scale Setup → Launch
- Connection test with live feedback
- BLE scale status during wizard
- Dark theme with modern UI (slate/purple palette)
- Settings page with URL edit, connection test, scale info, wizard reset
- Skip scale option for users without BLE scales
- Error page with retry button when server unreachable
- All UI in English
2026-04-16 16:23:13 +00:00
dadaloop82 d931b471f0 fix: add missing launcher icons for kiosk app
- Adaptive icon XMLs (API 26+) with vector foreground + green background
- PNG fallbacks for all density buckets (mdpi through xxxhdpi)
- Added ic_launcher_background color to colors.xml
2026-04-16 16:06:25 +00:00
dadaloop82 3e25fcd5df feat: banner alerts, consumption predictions, scale improvements, kiosk app
- Banner notification system: suspicious quantities + consumption prediction alerts
- Consumption predictions API: tracks 90-day usage patterns, flags >30% deviations
- Scale stability timeout: 5s → 10s, auto-confirm remains 5s
- Scale integration in edit form: weigh button with inline live display
- Banner edit/weigh actions open edit form directly with scale activation
- Cooking mode: Italian aliases + stem-prefix matching for ingredients
- Recipe regeneration: tracks rejected ingredients for diversity
- Settings migration: localStorage → .env server-side storage
- Expiry priority: mandatory ≤3 days, recommended ≤7 days in recipes
- Scale bug fixes: clear stale weight, double-submit guard, cap deduction
- Android kiosk app (evershelf-kiosk): WebView + embedded BLE scale gateway
- Version bump to 1.4.0
2026-04-16 14:46:30 +00:00