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
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
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.