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
This commit is contained in:
dadaloop82
2026-05-05 18:11:51 +00:00
parent 7ea5505a0d
commit 754f13111f
4 changed files with 315 additions and 21 deletions
@@ -87,18 +87,27 @@ class SetupActivity : AppCompatActivity() {
// Scale step (BLE)
private lateinit var scaleQuestionCard: LinearLayout
private lateinit var scalePowerOnCard: LinearLayout
private lateinit var bleSetupCard: LinearLayout
private lateinit var scaleTestCard: LinearLayout
private lateinit var tvScanStatus: TextView
private lateinit var btnScanBle: MaterialButton
private lateinit var tvSelectedScale: TextView
private lateinit var rvScaleDevices: RecyclerView
private lateinit var step3NextButtons: LinearLayout
private lateinit var tvTestStatus: TextView
private lateinit var tvTestWeight: TextView
private lateinit var testWeightBox: android.widget.LinearLayout
private var bleManager: BleScaleManager? = null
private val discoveredDevices = mutableListOf<BleDeviceInfo>()
private var selectedDevice: BleDeviceInfo? = null
private var deviceAdapter: DeviceAdapter? = null
// Test-mode state
private var isInTestMode = false
private var testHasWeight = false
// Screensaver step
private lateinit var setupSwitchScreensaver: SwitchMaterial
@@ -176,14 +185,15 @@ class SetupActivity : AppCompatActivity() {
override fun onDestroy() {
bleManager?.stopScan()
bleManager?.disconnect()
isInTestMode = false
discoverCancelled.set(true)
super.onDestroy()
}
override fun onResume() {
super.onResume()
// If we're on step 4 with a saved device, reflect it in the UI
if (currentStep == 4) {
// If we're on step 4 with a saved device and not in test mode, reflect it in the UI
if (currentStep == 4 && !isInTestMode) {
val savedName = bleManager?.getSavedDeviceName()
if (savedName != null) {
tvSelectedScale.text = "$savedName"
@@ -214,12 +224,17 @@ class SetupActivity : AppCompatActivity() {
// Scale step
scaleQuestionCard = findViewById(R.id.scaleQuestionCard)
scalePowerOnCard = findViewById(R.id.scalePowerOnCard)
bleSetupCard = findViewById(R.id.bleSetupCard)
scaleTestCard = findViewById(R.id.scaleTestCard)
tvScanStatus = findViewById(R.id.tvScanStatus)
btnScanBle = findViewById(R.id.btnScanBle)
tvSelectedScale = findViewById(R.id.tvSelectedScale)
rvScaleDevices = findViewById(R.id.rvScaleDevices)
step3NextButtons = findViewById(R.id.step3NextButtons)
tvTestStatus = findViewById(R.id.tvTestStatus)
tvTestWeight = findViewById(R.id.tvTestWeight)
testWeightBox = findViewById(R.id.testWeightBox)
// Screensaver step
setupSwitchScreensaver = findViewById(R.id.setupSwitchScreensaver)
@@ -279,9 +294,16 @@ class SetupActivity : AppCompatActivity() {
findViewById<MaterialButton>(R.id.btnScaleYes).setOnClickListener {
prefs.edit().putBoolean(KEY_HAS_SCALE, true).apply()
scaleQuestionCard.visibility = View.GONE
// Show power-on instruction first
scalePowerOnCard.visibility = View.VISIBLE
}
// "Bilancia accesa — Cerca" button
findViewById<MaterialButton>(R.id.btnPowerOnReady).setOnClickListener {
scalePowerOnCard.visibility = View.GONE
bleSetupCard.visibility = View.VISIBLE
step3NextButtons.visibility = View.VISIBLE
// Disable Next until device selected
// Disable Next until device confirmed
val savedName = bleManager?.getSavedDeviceName()
if (savedName != null) {
tvSelectedScale.text = "$savedName"
@@ -300,13 +322,63 @@ class SetupActivity : AppCompatActivity() {
btnScanBle.setOnClickListener { startBleScan() }
findViewById<MaterialButton>(R.id.btnScaleBack).setOnClickListener {
bleManager?.stopScan()
bleManager?.disconnect()
isInTestMode = false
showStep(3)
}
findViewById<MaterialButton>(R.id.btnScaleNext).setOnClickListener {
bleManager?.stopScan()
bleManager?.disconnect()
showStep(5)
}
// Test card buttons
findViewById<MaterialButton>(R.id.btnTestConfirm).setOnClickListener {
// User confirms weight matches → scale is good, proceed
isInTestMode = false
bleManager?.stopScan()
bleManager?.disconnect()
scaleTestCard.visibility = View.GONE
bleSetupCard.visibility = View.GONE
step3NextButtons.visibility = View.VISIBLE
val name = selectedDevice?.name ?: bleManager?.getSavedDeviceName() ?: "Bilancia"
tvSelectedScale.text = "$name"
tvSelectedScale.visibility = View.VISIBLE
findViewById<MaterialButton>(R.id.btnScaleNext).isEnabled = true
// Put scan card back (hidden) and show only nav buttons
bleSetupCard.visibility = View.GONE
}
findViewById<MaterialButton>(R.id.btnTestRetry).setOnClickListener {
// User says weight doesn't match → disconnect, go back to scan
isInTestMode = false
testHasWeight = false
bleManager?.disconnect()
selectedDevice = null
bleManager?.clearSavedDevice()
scaleTestCard.visibility = View.GONE
testWeightBox.visibility = View.GONE
bleSetupCard.visibility = View.VISIBLE
tvSelectedScale.text = ""
tvSelectedScale.visibility = View.GONE
tvScanStatus.text = "Bilancia non confermata. Riprova la scansione."
tvScanStatus.setTextColor(0xFFfbbf24.toInt())
btnScanBle.isEnabled = true
btnScanBle.text = "🔍 Cerca bilancia"
findViewById<MaterialButton>(R.id.btnScaleNext).isEnabled = false
}
findViewById<MaterialButton>(R.id.btnTestSkip).setOnClickListener {
// User skips test — trust the selection
isInTestMode = false
bleManager?.disconnect()
scaleTestCard.visibility = View.GONE
bleSetupCard.visibility = View.GONE
step3NextButtons.visibility = View.VISIBLE
val name = selectedDevice?.name ?: bleManager?.getSavedDeviceName() ?: "Bilancia"
tvSelectedScale.text = "$name"
tvSelectedScale.visibility = View.VISIBLE
findViewById<MaterialButton>(R.id.btnScaleNext).isEnabled = true
}
// ── Screensaver ───────────────────────────────────────────────────
findViewById<MaterialButton>(R.id.btnScreensaverBack).setOnClickListener { showStep(4) }
findViewById<MaterialButton>(R.id.btnScreensaverNext).setOnClickListener {
@@ -356,10 +428,15 @@ class SetupActivity : AppCompatActivity() {
// Reset scale step when entering it
if (step == 4) {
isInTestMode = false
testHasWeight = false
scaleTestCard.visibility = View.GONE
testWeightBox.visibility = View.GONE
val hasScaleYes = prefs.contains(KEY_HAS_SCALE) && prefs.getBoolean(KEY_HAS_SCALE, false)
if (hasScaleYes) {
// Already said YES — go straight to BLE scan card
// Already said YES — skip power-on card, go straight to BLE scan
scaleQuestionCard.visibility = View.GONE
scalePowerOnCard.visibility = View.GONE
bleSetupCard.visibility = View.VISIBLE
step3NextButtons.visibility = View.VISIBLE
val savedName = bleManager?.getSavedDeviceName()
@@ -374,6 +451,7 @@ class SetupActivity : AppCompatActivity() {
}
} else {
scaleQuestionCard.visibility = View.VISIBLE
scalePowerOnCard.visibility = View.GONE
bleSetupCard.visibility = View.GONE
step3NextButtons.visibility = View.GONE
}
@@ -751,15 +829,33 @@ class SetupActivity : AppCompatActivity() {
bleManager?.saveDevice(info.device.address, info.name)
tvSelectedScale.text = "${info.name}"
tvSelectedScale.visibility = View.VISIBLE
tvScanStatus.text = "Bilancia selezionata. Premi Avanti per continuare."
tvScanStatus.setTextColor(0xFF34d399.toInt())
btnScanBle.isEnabled = true
btnScanBle.text = "🔄 Scansiona di nuovo"
findViewById<MaterialButton>(R.id.btnScaleNext).isEnabled = true
// Start connection test
startScaleTest(info)
}
private fun startScaleTest(info: BleDeviceInfo) {
isInTestMode = true
testHasWeight = false
// Show test card, hide scan card
bleSetupCard.visibility = View.GONE
scaleTestCard.visibility = View.VISIBLE
testWeightBox.visibility = View.GONE
step3NextButtons.visibility = View.GONE
tvTestStatus.text = "🔗 Connessione a ${info.name}"
tvTestStatus.setTextColor(0xFF94a3b8.toInt())
tvTestWeight.text = "— g"
// Disable confirm/retry until we have data
findViewById<MaterialButton>(R.id.btnTestConfirm).isEnabled = false
findViewById<MaterialButton>(R.id.btnTestRetry).isEnabled = false
bleManager?.connect(info.device)
}
private fun makeBleListener() = object : BleScaleListener {
override fun onDeviceFound(info: BleDeviceInfo) {
// Only show devices that look like scales (positive score)
if (info.scaleScore <= 0) return
val existing = discoveredDevices.indexOfFirst { it.device.address == info.device.address }
if (existing >= 0) {
discoveredDevices[existing] = info
@@ -769,12 +865,50 @@ class SetupActivity : AppCompatActivity() {
deviceAdapter?.notifyItemInserted(discoveredDevices.size - 1)
}
}
override fun onConnecting(device: BluetoothDevice) {}
override fun onConnected(deviceName: String) {}
override fun onDisconnected() {}
override fun onWeightReceived(reading: WeightReading) {}
override fun onConnecting(device: BluetoothDevice) {
if (!isInTestMode) return
tvTestStatus.text = "🔗 Connessione in corso…"
tvTestStatus.setTextColor(0xFF94a3b8.toInt())
}
override fun onConnected(deviceName: String) {
if (!isInTestMode) return
tvTestStatus.text = "⚖️ Connesso! Posiziona un oggetto sulla bilancia…"
tvTestStatus.setTextColor(0xFF34d399.toInt())
testWeightBox.visibility = View.VISIBLE
findViewById<MaterialButton>(R.id.btnTestRetry).isEnabled = true
}
override fun onDisconnected() {
if (!isInTestMode) return
tvTestStatus.text = "⚠️ Connessione persa. Riprova."
tvTestStatus.setTextColor(0xFFfbbf24.toInt())
testWeightBox.visibility = View.GONE
testHasWeight = false
findViewById<MaterialButton>(R.id.btnTestConfirm).isEnabled = false
}
override fun onWeightReceived(reading: WeightReading) {
if (!isInTestMode) return
testHasWeight = true
val display = if (reading.unit == "kg")
"%.3f kg".format(reading.value)
else
"%g ${reading.unit}".format(reading.value)
tvTestWeight.text = display
testWeightBox.visibility = View.VISIBLE
tvTestStatus.text = "Peso ricevuto — coincide con quello sulla bilancia?"
tvTestStatus.setTextColor(0xFF94a3b8.toInt())
findViewById<MaterialButton>(R.id.btnTestConfirm).isEnabled = true
findViewById<MaterialButton>(R.id.btnTestRetry).isEnabled = true
}
override fun onBatteryReceived(level: Int) {}
override fun onError(message: String) {
if (isInTestMode) {
tvTestStatus.text = "⚠️ $message"
tvTestStatus.setTextColor(0xFFf87171.toInt())
testWeightBox.visibility = View.GONE
testHasWeight = false
findViewById<MaterialButton>(R.id.btnTestRetry).isEnabled = true
return
}
tvScanStatus.text = "⚠️ $message"
tvScanStatus.setTextColor(0xFFf87171.toInt())
btnScanBle.isEnabled = true
@@ -782,7 +916,7 @@ class SetupActivity : AppCompatActivity() {
override fun onScanStopped() {
btnScanBle.isEnabled = true
if (discoveredDevices.isEmpty()) {
tvScanStatus.text = "Nessuna bilancia trovata. Assicurati che sia accesa e vicina."
tvScanStatus.text = "Nessuna bilancia trovata. Assicurati che sia accesa e vicina, poi riprova."
tvScanStatus.setTextColor(0xFFfbbf24.toInt())
} else {
tvScanStatus.text = "Seleziona la tua bilancia dall'elenco."
@@ -807,6 +807,56 @@
android:textColor="#64748b" />
</LinearLayout>
<!-- Power-on instruction card (shown after YES, before scan) -->
<LinearLayout
android:id="@+id/scalePowerOnCard"
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"
android:visibility="gone">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="🔋"
android:textSize="40sp"
android:layout_gravity="center_horizontal"
android:layout_marginBottom="12dp" />
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="Accendi la bilancia"
android:textColor="#f1f5f9"
android:textSize="18sp"
android:textStyle="bold"
android:gravity="center"
android:layout_marginBottom="10dp" />
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="Assicurati che la bilancia sia accesa e vicina al tablet, poi avvia la scansione."
android:textColor="#94a3b8"
android:textSize="14sp"
android:gravity="center"
android:lineSpacingExtra="3dp"
android:layout_marginBottom="20dp" />
<com.google.android.material.button.MaterialButton
android:id="@+id/btnPowerOnReady"
android:layout_width="match_parent"
android:layout_height="52dp"
android:text="✅ Bilancia accesa — Cerca"
android:textSize="15sp"
android:textAllCaps="false"
android:backgroundTint="#059669" />
</LinearLayout>
<!-- BLE scan card (shown after YES) -->
<LinearLayout
android:id="@+id/bleSetupCard"
@@ -858,6 +908,114 @@
</LinearLayout>
<!-- Scale test card (shown after device is selected) -->
<LinearLayout
android:id="@+id/scaleTestCard"
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"
android:visibility="gone">
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="🔗 Test connessione"
android:textColor="#f1f5f9"
android:textSize="17sp"
android:textStyle="bold"
android:gravity="center"
android:layout_marginBottom="12dp" />
<TextView
android:id="@+id/tvTestStatus"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="Connessione in corso…"
android:textColor="#94a3b8"
android:textSize="14sp"
android:gravity="center"
android:layout_marginBottom="16dp" />
<!-- Weight display -->
<LinearLayout
android:id="@+id/testWeightBox"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical"
android:gravity="center"
android:background="#1a7c3aed"
android:padding="16dp"
android:layout_marginBottom="16dp"
android:visibility="gone">
<TextView
android:id="@+id/tvTestWeight"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="— g"
android:textColor="#f1f5f9"
android:textSize="36sp"
android:textStyle="bold"
android:layout_marginBottom="4dp" />
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Il peso che vedi coincide con quello sulla bilancia?"
android:textColor="#94a3b8"
android:textSize="13sp"
android:gravity="center" />
</LinearLayout>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="horizontal"
android:layout_marginBottom="10dp">
<com.google.android.material.button.MaterialButton
android:id="@+id/btnTestRetry"
android:layout_width="0dp"
android:layout_height="48dp"
android:layout_weight="1"
android:text="❌ No, riprova"
android:textSize="13sp"
android:textAllCaps="false"
style="@style/Widget.MaterialComponents.Button.OutlinedButton"
android:strokeColor="#ef4444"
android:textColor="#ef4444"
android:enabled="false"
android:layout_marginEnd="8dp" />
<com.google.android.material.button.MaterialButton
android:id="@+id/btnTestConfirm"
android:layout_width="0dp"
android:layout_height="48dp"
android:layout_weight="1"
android:text="✅ Sì, coincide"
android:textSize="13sp"
android:textAllCaps="false"
android:backgroundTint="#059669"
android:enabled="false" />
</LinearLayout>
<com.google.android.material.button.MaterialButton
android:id="@+id/btnTestSkip"
android:layout_width="match_parent"
android:layout_height="40dp"
android:text="Salta il test"
android:textSize="13sp"
android:textAllCaps="false"
style="@style/Widget.MaterialComponents.Button.TextButton"
android:textColor="#475569" />
</LinearLayout>
<!-- Step 4 navigation (shown after YES) -->
<LinearLayout
android:id="@+id/step3NextButtons"