New job 'auto-merge-to-main' in ci.yml:
- needs: lint-php, lint-js, docker-build, validate-translations
- only runs when github.ref == refs/heads/develop
- uses git merge --no-ff so history is preserved
- push to develop → CI passes → main updated automatically
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
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
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
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
- 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)
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
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()
- 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.
- 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)
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.
- Replace simple bar chart with full Anti-Waste Report Card
- Grade system (A+ to D) based on user's waste rate
- Dual comparison bars: user waste rate vs national average (IT/DE/US)
- Estimated monthly savings in money, meals saved, CO2 avoided
- 3-month trend mini chart with colour-coded bars
- Backend: getStats() now returns 3×30d buckets (used_30d, used_prev_30d, used_prev_60d, etc.)
- Real-world benchmarks: IT 22%/5.4kg/mo (REDUCE), DE 20%/6.5kg/mo (Eurostat), US 30%/9.2kg/mo (USDA)
- All labels fully i18n: 18 new antiwaste.* keys in it/en/de translation files
- Section is fully JS-rendered; HTML now just an empty container