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
This commit is contained in:
dadaloop82
2026-05-03 18:31:22 +00:00
parent a701e5a239
commit 4897da571d
5 changed files with 246 additions and 33 deletions
@@ -77,6 +77,8 @@ class KioskActivity : AppCompatActivity() {
private lateinit var scaleStatusIcon: TextView private lateinit var scaleStatusIcon: TextView
private lateinit var scaleStatusText: TextView private lateinit var scaleStatusText: TextView
private lateinit var scaleStatusDetail: TextView private lateinit var scaleStatusDetail: TextView
private lateinit var scaleQuestionLayout: LinearLayout
private lateinit var step3BottomButtons: LinearLayout
// Update banner (native, shown at the top over the WebView) // Update banner (native, shown at the top over the WebView)
private lateinit var updateBanner: LinearLayout private lateinit var updateBanner: LinearLayout
private lateinit var tvUpdateMessage: TextView private lateinit var tvUpdateMessage: TextView
@@ -106,6 +108,7 @@ class KioskActivity : AppCompatActivity() {
private const val PREFS_NAME = "evershelf_kiosk" private const val PREFS_NAME = "evershelf_kiosk"
private const val KEY_URL = "evershelf_url" private const val KEY_URL = "evershelf_url"
private const val KEY_SETUP_COMPLETE = "setup_complete" private const val KEY_SETUP_COMPLETE = "setup_complete"
private const val KEY_HAS_SCALE = "has_scale"
private const val GATEWAY_PACKAGE = "it.dadaloop.evershelf.scalegate" private const val GATEWAY_PACKAGE = "it.dadaloop.evershelf.scalegate"
private const val GATEWAY_DOWNLOAD_URL = "https://github.com/dadaloop82/EverShelf/releases/latest/download/evershelf-scale-gateway.apk" private const val GATEWAY_DOWNLOAD_URL = "https://github.com/dadaloop82/EverShelf/releases/latest/download/evershelf-scale-gateway.apk"
private const val KIOSK_DOWNLOAD_URL = "https://github.com/dadaloop82/EverShelf/releases/latest/download/evershelf-kiosk.apk" private const val KIOSK_DOWNLOAD_URL = "https://github.com/dadaloop82/EverShelf/releases/latest/download/evershelf-kiosk.apk"
@@ -166,6 +169,8 @@ class KioskActivity : AppCompatActivity() {
scaleStatusIcon = findViewById(R.id.scaleStatusIcon) scaleStatusIcon = findViewById(R.id.scaleStatusIcon)
scaleStatusText = findViewById(R.id.scaleStatusText) scaleStatusText = findViewById(R.id.scaleStatusText)
scaleStatusDetail = findViewById(R.id.scaleStatusDetail) scaleStatusDetail = findViewById(R.id.scaleStatusDetail)
scaleQuestionLayout = findViewById(R.id.scaleQuestionLayout)
step3BottomButtons = findViewById(R.id.step3BottomButtons)
// Update banner // Update banner
updateBanner = findViewById(R.id.updateBanner) updateBanner = findViewById(R.id.updateBanner)
@@ -204,10 +209,21 @@ class KioskActivity : AppCompatActivity() {
goToStep(2) goToStep(2)
} }
findViewById<MaterialButton>(R.id.btnFinish).setOnClickListener { findViewById<MaterialButton>(R.id.btnFinish).setOnClickListener {
prefs.edit().putBoolean(KEY_HAS_SCALE, true).apply()
launchGatewayInBackground() launchGatewayInBackground()
finishWizard() finishWizard()
} }
findViewById<MaterialButton>(R.id.btnSkipScale).setOnClickListener { // "Yes" → reveal gateway status and proceed flow
findViewById<MaterialButton>(R.id.btnScaleYes).setOnClickListener {
scaleQuestionLayout.visibility = View.GONE
val statusCard = findViewById<LinearLayout>(R.id.scaleStatusCard)
statusCard.visibility = View.VISIBLE
step3BottomButtons.visibility = View.VISIBLE
checkGatewayStatus()
}
// "No" → save pref and skip to web view
findViewById<MaterialButton>(R.id.btnScaleNo).setOnClickListener {
prefs.edit().putBoolean(KEY_HAS_SCALE, false).apply()
finishWizard() finishWizard()
} }
@@ -320,7 +336,12 @@ class KioskActivity : AppCompatActivity() {
updateStepIndicator() updateStepIndicator()
if (step == 3) { if (step == 3) {
checkGatewayStatus() // Reset to question state every time step 3 is entered
scaleQuestionLayout.visibility = View.VISIBLE
val statusCard = findViewById<LinearLayout>(R.id.scaleStatusCard)
statusCard.visibility = View.GONE
step3BottomButtons.visibility = View.GONE
findViewById<MaterialButton>(R.id.btnSkipScale).visibility = View.GONE
} }
} }
@@ -376,6 +397,7 @@ class KioskActivity : AppCompatActivity() {
} }
private fun launchGatewayInBackground() { private fun launchGatewayInBackground() {
if (!prefs.getBoolean(KEY_HAS_SCALE, false)) return
if (!isGatewayInstalled()) return if (!isGatewayInstalled()) return
val launchIntent = packageManager.getLaunchIntentForPackage(GATEWAY_PACKAGE) ?: return val launchIntent = packageManager.getLaunchIntentForPackage(GATEWAY_PACKAGE) ?: return
launchIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK) launchIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
@@ -389,32 +411,93 @@ class KioskActivity : AppCompatActivity() {
private fun checkGatewayStatus() { private fun checkGatewayStatus() {
if (isGatewayInstalled()) { if (isGatewayInstalled()) {
scaleStatusIcon.text = "" scaleStatusIcon.text = "\u2705"
scaleStatusText.text = "Scale Gateway is installed" scaleStatusText.text = getString(R.string.wizard_gateway_installed)
scaleStatusDetail.text = "It will be launched in the background when you proceed" scaleStatusDetail.text = getString(R.string.wizard_gateway_checking)
scaleStatusDetail.setTextColor(0xFF34d399.toInt()) scaleStatusDetail.setTextColor(0xFF94a3b8.toInt())
findViewById<MaterialButton>(R.id.btnSkipScale).visibility = View.GONE findViewById<MaterialButton>(R.id.btnSkipScale).visibility = View.GONE
findViewById<MaterialButton>(R.id.btnFinish).text = "🚀 Launch EverShelf" findViewById<MaterialButton>(R.id.btnFinish).text = getString(R.string.btn_launch)
// Check async if a newer version is available
checkGatewayUpdate()
} else { } else {
scaleStatusIcon.text = "📥" scaleStatusIcon.text = "\uD83D\uDCE5"
scaleStatusText.text = "Scale Gateway not installed" scaleStatusText.text = getString(R.string.wizard_gateway_not_installed)
scaleStatusDetail.text = "Install the Scale Gateway app to use a Bluetooth scale" scaleStatusDetail.text = getString(R.string.wizard_gateway_not_installed_detail)
scaleStatusDetail.setTextColor(0xFFfbbf24.toInt()) scaleStatusDetail.setTextColor(0xFFfbbf24.toInt())
findViewById<MaterialButton>(R.id.btnFinish).text = getString(R.string.btn_launch_no_scale)
findViewById<MaterialButton>(R.id.btnFinish).text = "🚀 Launch without scale"
findViewById<MaterialButton>(R.id.btnSkipScale).apply { findViewById<MaterialButton>(R.id.btnSkipScale).apply {
text = "📥 Download Scale Gateway" text = getString(R.string.btn_download_gateway)
setTextColor(0xFF7c3aed.toInt()) setTextColor(0xFFa78bfa.toInt())
visibility = View.VISIBLE visibility = View.VISIBLE
setOnClickListener { setOnClickListener { triggerApkDownload(GATEWAY_DOWNLOAD_URL) }
val intent = Intent(Intent.ACTION_VIEW, Uri.parse(GATEWAY_DOWNLOAD_URL))
startActivity(intent)
}
} }
} }
} }
/** Fetches the latest GitHub release and, if the gateway has an available update,
* shows the update button in the wizard status card. */
private fun checkGatewayUpdate() {
val currentVersion = try {
packageManager.getPackageInfo(GATEWAY_PACKAGE, 0).versionName ?: return
} catch (_: Exception) { return }
Thread {
try {
val conn = URL(GITHUB_RELEASES_API).openConnection() as java.net.HttpURLConnection
conn.setRequestProperty("Accept", "application/vnd.github+json")
conn.connectTimeout = 5000
conn.readTimeout = 5000
val json = JSONObject(conn.inputStream.bufferedReader().readText())
conn.disconnect()
val latestTag = json.optString("tag_name", "")
if (latestTag.isEmpty()) { showGatewayUpToDate(); return@Thread }
val isSemver = latestTag.trimStart('v').matches(Regex("\\d+\\.\\d+.*"))
val norm = { v: String -> v.trimStart('v') }
val needsUpdate = !isSemver || norm(latestTag) != norm(currentVersion)
if (!needsUpdate) { showGatewayUpToDate(); return@Thread }
// Locate the gateway APK among release assets
var apkUrl = GATEWAY_DOWNLOAD_URL
val assets = json.optJSONArray("assets")
if (assets != null) {
for (i in 0 until assets.length()) {
val a = assets.getJSONObject(i)
val name = a.optString("name", "").lowercase()
val url = a.optString("browser_download_url", "")
if ((name.contains("gateway") || name.contains("scale")) && url.isNotEmpty()) {
apkUrl = url; break
}
}
}
val finalUrl = apkUrl
runOnUiThread {
scaleStatusIcon.text = "\uD83D\uDD04"
scaleStatusText.text = getString(R.string.wizard_gateway_update_available)
scaleStatusDetail.text = getString(R.string.wizard_gateway_update_detail)
scaleStatusDetail.setTextColor(0xFFfbbf24.toInt())
pendingInstallPkg = GATEWAY_PACKAGE
pendingApkDownloadUrl = finalUrl
findViewById<MaterialButton>(R.id.btnSkipScale).apply {
text = getString(R.string.btn_update_gateway)
setTextColor(0xFFfbbf24.toInt())
visibility = View.VISIBLE
setOnClickListener { triggerApkDownload(finalUrl) }
}
}
} catch (_: Exception) {
showGatewayUpToDate()
}
}.start()
}
private fun showGatewayUpToDate() = runOnUiThread {
scaleStatusDetail.text = getString(R.string.wizard_gateway_installed_detail)
scaleStatusDetail.setTextColor(0xFF34d399.toInt())
}
// ── Connection Test ─────────────────────────────────────────────────── // ── Connection Test ───────────────────────────────────────────────────
private fun testConnection() { private fun testConnection() {
@@ -996,7 +1079,9 @@ class KioskActivity : AppCompatActivity() {
showWizard() showWizard()
} }
if (currentStep == 3 && wizardContainer.visibility == View.VISIBLE) { if (currentStep == 3 && wizardContainer.visibility == View.VISIBLE) {
checkGatewayStatus() val statusCard = findViewById<LinearLayout>(R.id.scaleStatusCard)
// Only re-check if the user has already answered "Yes" (status card visible)
if (statusCard.visibility == View.VISIBLE) checkGatewayStatus()
} }
} }
@@ -302,7 +302,7 @@
<TextView <TextView
android:layout_width="wrap_content" android:layout_width="wrap_content"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:text="Smart Scale (Optional)" android:text="@string/wizard_step3_title"
android:textColor="#f1f5f9" android:textColor="#f1f5f9"
android:textSize="24sp" android:textSize="24sp"
android:textStyle="bold" android:textStyle="bold"
@@ -311,14 +311,56 @@
<TextView <TextView
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:text="To use a Bluetooth kitchen scale, you need the EverShelf Scale Gateway app installed separately." android:text="@string/wizard_step3_description"
android:textColor="#94a3b8" android:textColor="#94a3b8"
android:textSize="15sp" android:textSize="15sp"
android:gravity="center" android:gravity="center"
android:lineSpacingExtra="4dp" android:lineSpacingExtra="4dp"
android:layout_marginBottom="24dp" /> android:layout_marginBottom="24dp" />
<!-- Scale status card --> <!-- Scale question card — shown first, hidden after answer -->
<LinearLayout
android:id="@+id/scaleQuestionLayout"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical"
android:background="@drawable/card_background"
android:padding="20dp"
android:layout_marginBottom="16dp">
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="@string/wizard_step3_question"
android:textColor="#f1f5f9"
android:textSize="17sp"
android:textStyle="bold"
android:gravity="center"
android:layout_marginBottom="20dp" />
<com.google.android.material.button.MaterialButton
android:id="@+id/btnScaleYes"
android:layout_width="match_parent"
android:layout_height="52dp"
android:text="@string/wizard_step3_yes"
android:textSize="15sp"
android:textAllCaps="false"
android:backgroundTint="#059669"
android:layout_marginBottom="10dp" />
<com.google.android.material.button.MaterialButton
android:id="@+id/btnScaleNo"
android:layout_width="match_parent"
android:layout_height="52dp"
android:text="@string/wizard_step3_no"
android:textSize="15sp"
android:textAllCaps="false"
style="@style/Widget.MaterialComponents.Button.OutlinedButton"
android:strokeColor="#334155"
android:textColor="#94a3b8" />
</LinearLayout>
<!-- Scale status card — shown after user answers "Yes" -->
<LinearLayout <LinearLayout
android:id="@+id/scaleStatusCard" android:id="@+id/scaleStatusCard"
android:layout_width="match_parent" android:layout_width="match_parent"
@@ -326,7 +368,8 @@
android:orientation="vertical" android:orientation="vertical"
android:background="@drawable/card_background" android:background="@drawable/card_background"
android:padding="20dp" android:padding="20dp"
android:layout_marginBottom="16dp"> android:layout_marginBottom="16dp"
android:visibility="gone">
<TextView <TextView
android:id="@+id/scaleStatusIcon" android:id="@+id/scaleStatusIcon"
@@ -357,19 +400,22 @@
android:gravity="center" /> android:gravity="center" />
</LinearLayout> </LinearLayout>
<!-- Bottom nav (Back / Launch) — hidden until user answers the question -->
<LinearLayout <LinearLayout
android:id="@+id/step3BottomButtons"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:orientation="horizontal" android:orientation="horizontal"
android:gravity="center" android:gravity="center"
android:layout_marginTop="16dp"> android:layout_marginTop="16dp"
android:visibility="gone">
<com.google.android.material.button.MaterialButton <com.google.android.material.button.MaterialButton
android:id="@+id/btnStep3Back" android:id="@+id/btnStep3Back"
android:layout_width="0dp" android:layout_width="0dp"
android:layout_height="48dp" android:layout_height="48dp"
android:layout_weight="1" android:layout_weight="1"
android:text="Back" android:text="@string/btn_back"
android:textSize="15sp" android:textSize="15sp"
android:textAllCaps="false" android:textAllCaps="false"
style="@style/Widget.MaterialComponents.Button.OutlinedButton" style="@style/Widget.MaterialComponents.Button.OutlinedButton"
@@ -382,22 +428,24 @@
android:layout_width="0dp" android:layout_width="0dp"
android:layout_height="56dp" android:layout_height="56dp"
android:layout_weight="2" android:layout_weight="2"
android:text="🚀 Launch EverShelf" android:text="@string/btn_launch"
android:textSize="16sp" android:textSize="16sp"
android:textAllCaps="false" android:textAllCaps="false"
android:backgroundTint="#059669" /> android:backgroundTint="#059669" />
</LinearLayout> </LinearLayout>
<!-- Install/Update gateway button — shown by checkGatewayStatus() as needed -->
<com.google.android.material.button.MaterialButton <com.google.android.material.button.MaterialButton
android:id="@+id/btnSkipScale" android:id="@+id/btnSkipScale"
android:layout_width="wrap_content" android:layout_width="wrap_content"
android:layout_height="36dp" android:layout_height="wrap_content"
android:text="Skip — I don't have a scale" android:textSize="14sp"
android:textSize="13sp"
android:textAllCaps="false" android:textAllCaps="false"
style="@style/Widget.MaterialComponents.Button.TextButton" style="@style/Widget.MaterialComponents.Button.OutlinedButton"
android:textColor="#64748b" android:strokeColor="#7c3aed"
android:layout_marginTop="12dp" /> android:textColor="#a78bfa"
android:layout_marginTop="12dp"
android:visibility="gone" />
</LinearLayout> </LinearLayout>
</LinearLayout> </LinearLayout>
@@ -0,0 +1,28 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<string name="app_name">EverShelf Kiosk</string>
<!-- Wizard Schritt 3: Smart-Waage -->
<string name="wizard_step3_title">Smart-Waage (Optional)</string>
<string name="wizard_step3_description">Um eine Bluetooth-Küchenwaage zu verwenden, musst du die EverShelf Scale Gateway App separat installieren.</string>
<string name="wizard_step3_question">Hast du eine Bluetooth-Küchenwaage?</string>
<string name="wizard_step3_yes">✅ Ja, ich habe eine Waage</string>
<string name="wizard_step3_no">➡️ Nein, überspringen</string>
<!-- Gateway-Statusmeldungen -->
<string name="wizard_gateway_installed">Scale Gateway installiert ✅</string>
<string name="wizard_gateway_installed_detail">Wird beim Fortfahren im Hintergrund gestartet.</string>
<string name="wizard_gateway_not_installed">Scale Gateway nicht installiert</string>
<string name="wizard_gateway_not_installed_detail">Installiere die Scale Gateway App, um eine Bluetooth-Waage zu nutzen.</string>
<string name="wizard_gateway_checking">Prüfe auf Updates…</string>
<string name="wizard_gateway_up_to_date">Scale Gateway ist aktuell.</string>
<string name="wizard_gateway_update_available">Update für Scale Gateway verfügbar</string>
<string name="wizard_gateway_update_detail">Tippe auf den Button, um jetzt zu aktualisieren.</string>
<!-- Schaltflächen -->
<string name="btn_back">Zurück</string>
<string name="btn_launch">🚀 EverShelf starten</string>
<string name="btn_launch_no_scale">🚀 Ohne Waage starten</string>
<string name="btn_download_gateway">📥 Scale Gateway installieren</string>
<string name="btn_update_gateway">📥 Scale Gateway aktualisieren</string>
</resources>
@@ -0,0 +1,28 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<string name="app_name">EverShelf Kiosk</string>
<!-- Wizard Step 3: Bilancia smart -->
<string name="wizard_step3_title">Bilancia Smart (Opzionale)</string>
<string name="wizard_step3_description">Per usare una bilancia da cucina Bluetooth, devi installare l\'app EverShelf Scale Gateway separatamente.</string>
<string name="wizard_step3_question">Hai una bilancia smart Bluetooth?</string>
<string name="wizard_step3_yes">✅ Sì, ho una bilancia</string>
<string name="wizard_step3_no">➡️ No, salta questo passaggio</string>
<!-- Messaggi stato gateway -->
<string name="wizard_gateway_installed">Scale Gateway installato ✅</string>
<string name="wizard_gateway_installed_detail">Verrà avviato in background quando procedi.</string>
<string name="wizard_gateway_not_installed">Scale Gateway non installato</string>
<string name="wizard_gateway_not_installed_detail">Installa l\'app Scale Gateway per usare una bilancia Bluetooth.</string>
<string name="wizard_gateway_checking">Controllo aggiornamenti…</string>
<string name="wizard_gateway_up_to_date">Scale Gateway è aggiornato.</string>
<string name="wizard_gateway_update_available">Aggiornamento disponibile per Scale Gateway</string>
<string name="wizard_gateway_update_detail">Tocca il pulsante qui sotto per aggiornarlo ora.</string>
<!-- Pulsanti -->
<string name="btn_back">Indietro</string>
<string name="btn_launch">🚀 Avvia EverShelf</string>
<string name="btn_launch_no_scale">🚀 Avvia senza bilancia</string>
<string name="btn_download_gateway">📥 Installa Scale Gateway</string>
<string name="btn_update_gateway">📥 Aggiorna Scale Gateway</string>
</resources>
@@ -1,3 +1,27 @@
<resources> <resources>
<string name="app_name">EverShelf Kiosk</string> <string name="app_name">EverShelf Kiosk</string>
<!-- Wizard Step 3: Smart scale -->
<string name="wizard_step3_title">Smart Scale (Optional)</string>
<string name="wizard_step3_description">To use a Bluetooth kitchen scale, you need the EverShelf Scale Gateway app installed separately.</string>
<string name="wizard_step3_question">Do you have a Bluetooth smart scale?</string>
<string name="wizard_step3_yes">✅ Yes, I have a scale</string>
<string name="wizard_step3_no">➡️ No, skip this step</string>
<!-- Gateway status messages -->
<string name="wizard_gateway_installed">Scale Gateway installed ✅</string>
<string name="wizard_gateway_installed_detail">Will be launched in the background when you proceed.</string>
<string name="wizard_gateway_not_installed">Scale Gateway not installed</string>
<string name="wizard_gateway_not_installed_detail">Install the Scale Gateway app to use a Bluetooth scale.</string>
<string name="wizard_gateway_checking">Checking for updates…</string>
<string name="wizard_gateway_up_to_date">Scale Gateway is up to date.</string>
<string name="wizard_gateway_update_available">Update available for Scale Gateway</string>
<string name="wizard_gateway_update_detail">Tap the button below to update it now.</string>
<!-- Buttons -->
<string name="btn_back">Back</string>
<string name="btn_launch">🚀 Launch EverShelf</string>
<string name="btn_launch_no_scale">🚀 Launch without scale</string>
<string name="btn_download_gateway">📥 Install Scale Gateway</string>
<string name="btn_update_gateway">📥 Update Scale Gateway</string>
</resources> </resources>