feat(kiosk-wizard): step Features con screensaver, prezzi, piano pasti, zero-waste tips

Step 5 del wizard ora mostra 4 toggle (pre-compilati se già configurati):
  - Salvaschermo orologio (screensaver_enabled)
  - Prezzi lista spesa (price_enabled)
  - Piano pasti (meal_plan_enabled)
  - Suggerimenti zero-waste durante cottura (zerowaste_tips_enabled)

Solo i toggle NON ancora impostati in prefs partono da false (fresh install).
Tutti e 4 vengono salvati in SharedPreferences e inviati al server via
save_settings al completamento del wizard.

PHP/JS: zerowaste_tips_enabled aggiunto come impostazione server-side
(ZEROWASTE_TIPS_ENABLED in .env), sincronizzata nel WebView via
_applySyncedSettings() al caricamento.
This commit is contained in:
dadaloop82
2026-05-17 15:40:01 +00:00
parent d26dce283d
commit 8360f5a0a0
5 changed files with 184 additions and 42 deletions
+2
View File
@@ -2652,6 +2652,7 @@ function getServerSettings(): void {
'meal_plan_enabled' => env('MEAL_PLAN_ENABLED', 'false') === 'true', 'meal_plan_enabled' => env('MEAL_PLAN_ENABLED', 'false') === 'true',
'screensaver_enabled' => env('SCREENSAVER_ENABLED', 'false') === 'true', 'screensaver_enabled' => env('SCREENSAVER_ENABLED', 'false') === 'true',
'screensaver_timeout' => (int)env('SCREENSAVER_TIMEOUT', '5'), 'screensaver_timeout' => (int)env('SCREENSAVER_TIMEOUT', '5'),
'zerowaste_tips_enabled' => env('ZEROWASTE_TIPS_ENABLED', 'false') === 'true',
'price_enabled' => env('PRICE_ENABLED', 'false') === 'true', 'price_enabled' => env('PRICE_ENABLED', 'false') === 'true',
'price_country' => env('PRICE_COUNTRY', 'Italia'), 'price_country' => env('PRICE_COUNTRY', 'Italia'),
'price_currency' => env('PRICE_CURRENCY', 'EUR'), 'price_currency' => env('PRICE_CURRENCY', 'EUR'),
@@ -2709,6 +2710,7 @@ function saveSettings(): void {
'meal_plan_enabled' => 'MEAL_PLAN_ENABLED', 'meal_plan_enabled' => 'MEAL_PLAN_ENABLED',
'screensaver_enabled' => 'SCREENSAVER_ENABLED', 'screensaver_enabled' => 'SCREENSAVER_ENABLED',
'price_enabled' => 'PRICE_ENABLED', 'price_enabled' => 'PRICE_ENABLED',
'zerowaste_tips_enabled' => 'ZEROWASTE_TIPS_ENABLED',
]; ];
// Integer keys // Integer keys
$intMap = [ $intMap = [
+2 -1
View File
@@ -2185,7 +2185,8 @@ function _applySyncedSettings(serverSettings) {
'tts_method','tts_auth_type','tts_content_type','tts_payload_key', 'tts_method','tts_auth_type','tts_content_type','tts_payload_key',
'tts_engine','tts_rate','tts_pitch','tts_auth_header_name','tts_auth_header_value','tts_extra_fields', 'tts_engine','tts_rate','tts_pitch','tts_auth_header_name','tts_auth_header_value','tts_extra_fields',
'screensaver_enabled','screensaver_timeout', 'screensaver_enabled','screensaver_timeout',
'price_enabled','price_country','price_currency','price_update_months']; 'price_enabled','price_country','price_currency','price_update_months',
'zerowaste_tips_enabled'];
let changed = false; let changed = false;
for (const key of serverKeys) { for (const key of serverKeys) {
if (serverSettings[key] !== undefined && serverSettings[key] !== null && serverSettings[key] !== '') { if (serverSettings[key] !== undefined && serverSettings[key] !== null && serverSettings[key] !== '') {
@@ -110,6 +110,9 @@ class SetupActivity : AppCompatActivity() {
// Screensaver step // Screensaver step
private lateinit var setupSwitchScreensaver: SwitchMaterial private lateinit var setupSwitchScreensaver: SwitchMaterial
private lateinit var setupSwitchPrices: SwitchMaterial
private lateinit var setupSwitchMealPlan: SwitchMaterial
private lateinit var setupSwitchZeroWaste: SwitchMaterial
// Done step // Done step
private lateinit var summaryText: TextView private lateinit var summaryText: TextView
@@ -128,6 +131,9 @@ class SetupActivity : AppCompatActivity() {
private const val KEY_HAS_SCALE = "has_scale" private const val KEY_HAS_SCALE = "has_scale"
private const val KEY_LANGUAGE = "kiosk_language" private const val KEY_LANGUAGE = "kiosk_language"
private const val KEY_SCREENSAVER = "screensaver_enabled" private const val KEY_SCREENSAVER = "screensaver_enabled"
private const val KEY_PRICE_ENABLED = "price_enabled"
private const val KEY_MEAL_PLAN = "meal_plan_enabled"
private const val KEY_ZEROWASTE_TIPS = "zerowaste_tips_enabled"
private const val PERMISSION_REQUEST_CODE = 2004 private const val PERMISSION_REQUEST_CODE = 2004
private const val BLE_PERMISSION_REQUEST = 2006 private const val BLE_PERMISSION_REQUEST = 2006
@@ -238,10 +244,17 @@ class SetupActivity : AppCompatActivity() {
tvTestWeight = findViewById(R.id.tvTestWeight) tvTestWeight = findViewById(R.id.tvTestWeight)
testWeightBox = findViewById(R.id.testWeightBox) testWeightBox = findViewById(R.id.testWeightBox)
// Screensaver step // Features step — bind all four toggles
setupSwitchScreensaver = findViewById(R.id.setupSwitchScreensaver) setupSwitchScreensaver = findViewById(R.id.setupSwitchScreensaver)
// Pre-fill saved screensaver pref setupSwitchPrices = findViewById(R.id.setupSwitchPrices)
setupSwitchScreensaver.isChecked = prefs.getBoolean(KEY_SCREENSAVER, false) setupSwitchMealPlan = findViewById(R.id.setupSwitchMealPlan)
setupSwitchZeroWaste = findViewById(R.id.setupSwitchZeroWaste)
// Pre-fill from saved prefs only if each key was previously configured
// ("se non sono impostati, chiedi!" — fresh install → all start at false)
setupSwitchScreensaver.isChecked = if (prefs.contains(KEY_SCREENSAVER)) prefs.getBoolean(KEY_SCREENSAVER, false) else false
setupSwitchPrices.isChecked = if (prefs.contains(KEY_PRICE_ENABLED)) prefs.getBoolean(KEY_PRICE_ENABLED, false) else false
setupSwitchMealPlan.isChecked = if (prefs.contains(KEY_MEAL_PLAN)) prefs.getBoolean(KEY_MEAL_PLAN, false) else false
setupSwitchZeroWaste.isChecked = if (prefs.contains(KEY_ZEROWASTE_TIPS)) prefs.getBoolean(KEY_ZEROWASTE_TIPS, false) else false
// Done step // Done step
summaryText = findViewById(R.id.setupSummaryText) summaryText = findViewById(R.id.setupSummaryText)
@@ -381,10 +394,15 @@ class SetupActivity : AppCompatActivity() {
findViewById<MaterialButton>(R.id.btnScaleNext).isEnabled = true findViewById<MaterialButton>(R.id.btnScaleNext).isEnabled = true
} }
// ── Screensaver ─────────────────────────────────────────────────── // ── Features step (screensaver / prices / meal plan / zero-waste) ────
findViewById<MaterialButton>(R.id.btnScreensaverBack).setOnClickListener { showStep(4) } findViewById<MaterialButton>(R.id.btnScreensaverBack).setOnClickListener { showStep(4) }
findViewById<MaterialButton>(R.id.btnScreensaverNext).setOnClickListener { findViewById<MaterialButton>(R.id.btnScreensaverNext).setOnClickListener {
prefs.edit().putBoolean(KEY_SCREENSAVER, setupSwitchScreensaver.isChecked).apply() prefs.edit()
.putBoolean(KEY_SCREENSAVER, setupSwitchScreensaver.isChecked)
.putBoolean(KEY_PRICE_ENABLED, setupSwitchPrices.isChecked)
.putBoolean(KEY_MEAL_PLAN, setupSwitchMealPlan.isChecked)
.putBoolean(KEY_ZEROWASTE_TIPS, setupSwitchZeroWaste.isChecked)
.apply()
showStep(6) showStep(6)
} }
@@ -971,13 +989,16 @@ class SetupActivity : AppCompatActivity() {
// ── Summary / Finish ───────────────────────────────────────────────── // ── Summary / Finish ─────────────────────────────────────────────────
private fun buildSummary() { private fun buildSummary() {
val url = prefs.getString(KEY_URL, "") ?: "" val url = prefs.getString(KEY_URL, "") ?: ""
val hasScale = prefs.getBoolean(KEY_HAS_SCALE, false) val hasScale = prefs.getBoolean(KEY_HAS_SCALE, false)
val screensOn = setupSwitchScreensaver.isChecked val screensOn = setupSwitchScreensaver.isChecked
val scaleName = bleManager?.getSavedDeviceName() val pricesOn = setupSwitchPrices.isChecked
val scaleOk = hasScale && scaleName != null val mealPlanOn = setupSwitchMealPlan.isChecked
val lang = prefs.getString(KEY_LANGUAGE, "it") ?: "it" val zeroWasteOn = setupSwitchZeroWaste.isChecked
val langLabel = when (lang) { "en" -> "English 🇬🇧"; "de" -> "Deutsch 🇩🇪"; else -> "Italiano 🇮🇹" } val scaleName = bleManager?.getSavedDeviceName()
val scaleOk = hasScale && scaleName != null
val lang = prefs.getString(KEY_LANGUAGE, "it") ?: "it"
val langLabel = when (lang) { "en" -> "English 🇬🇧"; "de" -> "Deutsch 🇩🇪"; else -> "Italiano 🇮🇹" }
val sb = StringBuilder() val sb = StringBuilder()
sb.appendLine("🌐 ${getString(R.string.summary_lang)}: $langLabel") sb.appendLine("🌐 ${getString(R.string.summary_lang)}: $langLabel")
if (url.isNotEmpty()) sb.appendLine("🖥️ Server: $url") if (url.isNotEmpty()) sb.appendLine("🖥️ Server: $url")
@@ -986,7 +1007,10 @@ class SetupActivity : AppCompatActivity() {
hasScale -> "⚠️ Bilancia: da configurare" hasScale -> "⚠️ Bilancia: da configurare"
else -> "${getString(R.string.summary_scale_skip)}" else -> "${getString(R.string.summary_scale_skip)}"
}) })
sb.appendLine(if (screensOn) "🌙 ${getString(R.string.summary_screensaver_on)}" else "💡 ${getString(R.string.summary_screensaver_off)}") sb.appendLine(if (screensOn) getString(R.string.summary_screensaver_on) else getString(R.string.summary_screensaver_off))
if (pricesOn) sb.appendLine(getString(R.string.summary_prices_on))
if (mealPlanOn) sb.appendLine(getString(R.string.summary_mealplan_on))
if (zeroWasteOn) sb.appendLine(getString(R.string.summary_zerowaste_on))
summaryText.text = sb.toString().trimEnd() summaryText.text = sb.toString().trimEnd()
} }
@@ -994,16 +1018,20 @@ class SetupActivity : AppCompatActivity() {
prefs.edit().putBoolean(KEY_SETUP_COMPLETE, true).apply() prefs.edit().putBoolean(KEY_SETUP_COMPLETE, true).apply()
val baseUrl = (prefs.getString(KEY_URL, "") ?: "").trimEnd('/') val baseUrl = (prefs.getString(KEY_URL, "") ?: "").trimEnd('/')
if (baseUrl.isNotEmpty()) { if (baseUrl.isNotEmpty()) {
val hasScale = prefs.getBoolean(KEY_HAS_SCALE, false) && (bleManager?.getSavedDeviceAddress() != null) val hasScale = prefs.getBoolean(KEY_HAS_SCALE, false) && (bleManager?.getSavedDeviceAddress() != null)
val screensaver = prefs.getBoolean(KEY_SCREENSAVER, false) val screensaver = prefs.getBoolean(KEY_SCREENSAVER, false)
val priceEnabled = prefs.getBoolean(KEY_PRICE_ENABLED, false)
val mealPlan = prefs.getBoolean(KEY_MEAL_PLAN, false)
val zeroWaste = prefs.getBoolean(KEY_ZEROWASTE_TIPS, false)
Thread { Thread {
try { try {
val url = "$baseUrl/api/index.php?action=save_settings" val url = "$baseUrl/api/index.php?action=save_settings"
val body = buildString { val body = buildString {
append("{\"screensaver_enabled\":$screensaver") append("{\"screensaver_enabled\":$screensaver")
append(",\"price_enabled\":$priceEnabled")
append(",\"meal_plan_enabled\":$mealPlan")
append(",\"zerowaste_tips_enabled\":$zeroWaste")
if (hasScale) { if (hasScale) {
// Use the tablet's actual LAN IP so the EverShelf server
// (potentially on a different machine) can reach the gateway.
val lanIp = getDeviceLanIp() ?: "127.0.0.1" val lanIp = getDeviceLanIp() ?: "127.0.0.1"
append(",\"scale_enabled\":true,\"scale_gateway_url\":\"ws://$lanIp:8765\"") append(",\"scale_enabled\":true,\"scale_gateway_url\":\"ws://$lanIp:8765\"")
} }
@@ -1050,7 +1050,7 @@
</LinearLayout> </LinearLayout>
<!-- ════════════════════════════════════════════ <!-- ════════════════════════════════════════════
STEP 5 — Screensaver STEP 5 — Features
════════════════════════════════════════════ --> ════════════════════════════════════════════ -->
<LinearLayout <LinearLayout
android:id="@+id/stepScreensaver" android:id="@+id/stepScreensaver"
@@ -1063,66 +1063,58 @@
<TextView <TextView
android:layout_width="wrap_content" android:layout_width="wrap_content"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:text="🌙" android:text=""
android:textSize="52sp" android:textSize="52sp"
android:layout_marginBottom="12dp" /> android:layout_marginBottom="12dp" />
<TextView <TextView
android:layout_width="wrap_content" android:layout_width="wrap_content"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:id="@+id/tvScreensaverTitle" android:text="@string/setup_features_title"
android:text="@string/setup_screensaver_title"
android:textColor="#f1f5f9" android:textColor="#f1f5f9"
android:textSize="24sp" android:textSize="24sp"
android:textStyle="bold" android:textStyle="bold"
android:layout_marginBottom="8dp" /> android:layout_marginBottom="8dp" />
<TextView <TextView
android:id="@+id/tvScreensaverDesc"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:text="Dopo 5 minuti di inattività mostra un overlay con l&#39;orologio e informazioni utili (statistiche, piano pasti). Lo schermo rimane SEMPRE acceso — questa opzione riguarda solo l&#39;overlay visivo in-app." android:text="@string/setup_features_desc"
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="28dp" /> android:layout_marginBottom="20dp" />
<!-- Toggle card --> <!-- Toggle: Screensaver -->
<LinearLayout <LinearLayout
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:background="@drawable/card_background" android:background="@drawable/card_background"
android:padding="20dp" android:padding="16dp"
android:gravity="center_vertical" android:gravity="center_vertical"
android:layout_marginBottom="32dp"> android:layout_marginBottom="10dp">
<LinearLayout <LinearLayout
android:layout_width="0dp" android:layout_width="0dp"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_weight="1" android:layout_weight="1"
android:orientation="vertical"> android:orientation="vertical">
<TextView <TextView
android:id="@+id/tvScreensaverToggleLabel"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:text="@string/setup_screensaver_toggle_label" android:text="@string/setup_screensaver_toggle_label"
android:textColor="#f1f5f9" android:textColor="#f1f5f9"
android:textSize="16sp" android:textSize="15sp"
android:textStyle="bold" android:textStyle="bold"
android:layout_marginBottom="4dp" /> android:layout_marginBottom="3dp" />
<TextView <TextView
android:id="@+id/tvScreensaverToggleHint"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:text="@string/setup_screensaver_toggle_hint" android:text="@string/setup_screensaver_toggle_hint"
android:textColor="#64748b" android:textColor="#64748b"
android:textSize="13sp" /> android:textSize="12sp" />
</LinearLayout> </LinearLayout>
<com.google.android.material.switchmaterial.SwitchMaterial <com.google.android.material.switchmaterial.SwitchMaterial
android:id="@+id/setupSwitchScreensaver" android:id="@+id/setupSwitchScreensaver"
android:layout_width="wrap_content" android:layout_width="wrap_content"
@@ -1130,6 +1122,114 @@
android:checked="false" /> android:checked="false" />
</LinearLayout> </LinearLayout>
<!-- Toggle: Prezzi lista spesa -->
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="horizontal"
android:background="@drawable/card_background"
android:padding="16dp"
android:gravity="center_vertical"
android:layout_marginBottom="10dp">
<LinearLayout
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1"
android:orientation="vertical">
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="@string/setup_prices_toggle_label"
android:textColor="#f1f5f9"
android:textSize="15sp"
android:textStyle="bold"
android:layout_marginBottom="3dp" />
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="@string/setup_prices_toggle_hint"
android:textColor="#64748b"
android:textSize="12sp" />
</LinearLayout>
<com.google.android.material.switchmaterial.SwitchMaterial
android:id="@+id/setupSwitchPrices"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:checked="false" />
</LinearLayout>
<!-- Toggle: Piano pasti -->
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="horizontal"
android:background="@drawable/card_background"
android:padding="16dp"
android:gravity="center_vertical"
android:layout_marginBottom="10dp">
<LinearLayout
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1"
android:orientation="vertical">
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="@string/setup_mealplan_toggle_label"
android:textColor="#f1f5f9"
android:textSize="15sp"
android:textStyle="bold"
android:layout_marginBottom="3dp" />
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="@string/setup_mealplan_toggle_hint"
android:textColor="#64748b"
android:textSize="12sp" />
</LinearLayout>
<com.google.android.material.switchmaterial.SwitchMaterial
android:id="@+id/setupSwitchMealPlan"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:checked="false" />
</LinearLayout>
<!-- Toggle: Suggerimenti zero-waste -->
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="horizontal"
android:background="@drawable/card_background"
android:padding="16dp"
android:gravity="center_vertical"
android:layout_marginBottom="24dp">
<LinearLayout
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1"
android:orientation="vertical">
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="@string/setup_zerowaste_toggle_label"
android:textColor="#f1f5f9"
android:textSize="15sp"
android:textStyle="bold"
android:layout_marginBottom="3dp" />
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="@string/setup_zerowaste_toggle_hint"
android:textColor="#64748b"
android:textSize="12sp" />
</LinearLayout>
<com.google.android.material.switchmaterial.SwitchMaterial
android:id="@+id/setupSwitchZeroWaste"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:checked="false" />
</LinearLayout>
<!-- Navigation --> <!-- Navigation -->
<LinearLayout <LinearLayout
android:layout_width="match_parent" android:layout_width="match_parent"
@@ -58,15 +58,26 @@
<string name="wizard_server_ok_detail">Error reporting is active — install failures will be sent to GitHub Issues automatically.</string> <string name="wizard_server_ok_detail">Error reporting is active — install failures will be sent to GitHub Issues automatically.</string>
<string name="wizard_server_error">Server not reachable ⚠️</string> <string name="wizard_server_error">Server not reachable ⚠️</string>
<string name="wizard_server_error_detail">Install errors won\'t reach GitHub Issues. Check the URL entered in step 2.</string> <string name="wizard_server_error_detail">Install errors won\'t reach GitHub Issues. Check the URL entered in step 2.</string>
<!-- Screensaver step --> <!-- Features step (step 5) -->
<string name="setup_features_title">Funzionalità</string>
<string name="setup_features_desc">Attiva le funzioni che vuoi usare. Puoi sempre cambiarle in seguito dalle impostazioni del server.</string>
<string name="setup_screensaver_title">Salvaschermo in-app</string> <string name="setup_screensaver_title">Salvaschermo in-app</string>
<string name="setup_screensaver_desc">Shows a clock with useful facts after 5 minutes of inactivity. Off by default (screen stays always on).</string> <string name="setup_screensaver_desc">Shows a clock with useful facts after 5 minutes of inactivity. Off by default (screen stays always on).</string>
<string name="setup_screensaver_toggle_label">Abilita salvaschermo orologio</string> <string name="setup_screensaver_toggle_label">Salvaschermo orologio</string>
<string name="setup_screensaver_toggle_hint">Mostra l\'overlay orologio dopo 5 min. Lo schermo resta sempre acceso.</string> <string name="setup_screensaver_toggle_hint">Mostra l\'overlay orologio dopo 5 min di inattività.</string>
<string name="setup_prices_toggle_label">Prezzi lista spesa</string>
<string name="setup_prices_toggle_hint">Stima automatica del costo di ogni articolo in lista tramite AI.</string>
<string name="setup_mealplan_toggle_label">Piano pasti</string>
<string name="setup_mealplan_toggle_hint">Pianifica i pasti della settimana suggerendo ricette basate sulla dispensa.</string>
<string name="setup_zerowaste_toggle_label">Suggerimenti zero-waste</string>
<string name="setup_zerowaste_toggle_hint">Durante la cottura mostra consigli per riutilizzare scarti (bucce, acqua di cottura, ecc.).</string>
<!-- Summary --> <!-- Summary -->
<string name="summary_lang">Language</string> <string name="summary_lang">Language</string>
<string name="summary_scale_skip">Scale: not configured</string> <string name="summary_scale_skip">Scale: not configured</string>
<string name="summary_screensaver_on">Screensaver: enabled</string> <string name="summary_screensaver_on">Screensaver: abilitato</string>
<string name="summary_screensaver_off">Screen always on (screensaver disabled)</string> <string name="summary_screensaver_off">Screensaver: disabilitato</string>
<string name="summary_prices_on">Prezzi lista spesa: abilitati</string>
<string name="summary_mealplan_on">Piano pasti: abilitato</string>
<string name="summary_zerowaste_on">Suggerimenti zero-waste: abilitati</string>
</resources> </resources>