Compare commits
6 Commits
v1.7.22
...
kiosk-1.7.16
| Author | SHA1 | Date | |
|---|---|---|---|
| ea2dae2be9 | |||
| 8360f5a0a0 | |||
| f5b1913ffa | |||
| d26dce283d | |||
| e67e490162 | |||
| 92048c9eba |
@@ -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 = [
|
||||||
@@ -8434,6 +8436,32 @@ function _calcEstimatedTotal(float $pricePerUnit, string $priceUnitLabel, float
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ── conf/pz with known package weight vs weight-labeled AI price ──────────
|
||||||
|
// E.g. unit='conf', defQty=170g, AI priced 'pacco 500g' @ €3.20
|
||||||
|
// → need ceil(7×170 / 500) = 3 packs × €3.20 = €9.60, not 7×€3.20 = €22.40
|
||||||
|
if (in_array(strtolower($unit), ['conf', 'pz']) && $defQty > 0 && !empty($pkgUnit)) {
|
||||||
|
$pkgL = strtolower($pkgUnit);
|
||||||
|
$isWt = in_array($pkgL, ['g', 'kg']);
|
||||||
|
$isVol = in_array($pkgL, ['ml', 'l', 'lt']);
|
||||||
|
if (($isWt || $isVol) &&
|
||||||
|
preg_match('/\b(\d+(?:[.,]\d+)?)\s*(g|kg|ml|l|lt)\b/i', $priceUnitLabel, $m)) {
|
||||||
|
$rawVal = (float) str_replace(',', '.', $m[1]);
|
||||||
|
$rawUnit = strtolower($m[2]);
|
||||||
|
$labelIsWt = in_array($rawUnit, ['g', 'kg']);
|
||||||
|
$labelIsVol = in_array($rawUnit, ['ml', 'l', 'lt']);
|
||||||
|
if (($isWt && $labelIsWt) || ($isVol && $labelIsVol)) {
|
||||||
|
// Convert to base units (g or ml)
|
||||||
|
$defBase = $pkgL === 'kg' ? $defQty * 1000.0 : $defQty;
|
||||||
|
$labelBase = match($rawUnit) { 'kg','l','lt' => $rawVal * 1000.0, default => $rawVal };
|
||||||
|
if ($labelBase > 0) {
|
||||||
|
$totalBase = $qty * $defBase;
|
||||||
|
$packs = (int) max(1, ceil($totalBase / $labelBase));
|
||||||
|
return round($pricePerUnit * $packs, 2);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
$buyQty = max(1.0, $qty);
|
$buyQty = max(1.0, $qty);
|
||||||
return round($pricePerUnit * $buyQty, 2);
|
return round($pricePerUnit * $buyQty, 2);
|
||||||
}
|
}
|
||||||
|
|||||||
+2
-1
@@ -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] !== '') {
|
||||||
|
|||||||
@@ -11,8 +11,8 @@ android {
|
|||||||
applicationId = "it.dadaloop.evershelf.kiosk"
|
applicationId = "it.dadaloop.evershelf.kiosk"
|
||||||
minSdk = 24
|
minSdk = 24
|
||||||
targetSdk = 34
|
targetSdk = 34
|
||||||
versionCode = 16
|
versionCode = 17
|
||||||
versionName = "1.7.15"
|
versionName = "1.7.16"
|
||||||
}
|
}
|
||||||
|
|
||||||
signingConfigs {
|
signingConfigs {
|
||||||
|
|||||||
@@ -113,7 +113,9 @@ class KioskActivity : AppCompatActivity() {
|
|||||||
private const val KEY_SCREENSAVER = "screensaver_enabled"
|
private const val KEY_SCREENSAVER = "screensaver_enabled"
|
||||||
private const val KIOSK_DOWNLOAD_URL = "https://github.com/dadaloop82/EverShelf/releases/download/kiosk-latest/evershelf-kiosk.apk"
|
private const val KIOSK_DOWNLOAD_URL = "https://github.com/dadaloop82/EverShelf/releases/download/kiosk-latest/evershelf-kiosk.apk"
|
||||||
private const val SPLASH_DURATION = 1500L
|
private const val SPLASH_DURATION = 1500L
|
||||||
private const val GITHUB_RELEASES_API = "https://api.github.com/repos/dadaloop82/EverShelf/releases/latest"
|
// Use the kiosk-specific rolling release tag so version comparison is always
|
||||||
|
// against the KIOSK version, not the webapp version (they diverge).
|
||||||
|
private const val GITHUB_RELEASES_API = "https://api.github.com/repos/dadaloop82/EverShelf/releases/tags/kiosk-latest"
|
||||||
// Keys for persisting a pending update across restarts
|
// Keys for persisting a pending update across restarts
|
||||||
private const val KEY_PENDING_UPDATE_VERSION = "pending_update_version"
|
private const val KEY_PENDING_UPDATE_VERSION = "pending_update_version"
|
||||||
private const val KEY_PENDING_UPDATE_URL = "pending_update_url"
|
private const val KEY_PENDING_UPDATE_URL = "pending_update_url"
|
||||||
@@ -627,10 +629,16 @@ class KioskActivity : AppCompatActivity() {
|
|||||||
packageManager.getPackageInfo(packageName, 0).versionName ?: ""
|
packageManager.getPackageInfo(packageName, 0).versionName ?: ""
|
||||||
} catch (_: Exception) { "" }
|
} catch (_: Exception) { "" }
|
||||||
|
|
||||||
// Strip any non-numeric prefix so "kiosk-1.7.0", "v1.7.0", "kiosk-v1.7.1"
|
// The kiosk-latest release uses a non-semver tag ("kiosk-latest").
|
||||||
// all normalise to "1.7.0" / "1.7.1" for comparison.
|
// Extract the actual kiosk version from the release body text.
|
||||||
|
// Body format: "Alias automatico → kiosk-X.Y.Z" or just "kiosk-X.Y.Z".
|
||||||
|
// Fall back to stripping the tag prefix if body parsing fails.
|
||||||
|
val bodyText = json.optString("body", "")
|
||||||
val norm = { v: String -> v.replace(Regex("^[^0-9]*"), "") }
|
val norm = { v: String -> v.replace(Regex("^[^0-9]*"), "") }
|
||||||
val isSemver = norm(latestTag).matches(Regex("\\d+\\.\\d+.*"))
|
val remoteKioskVersion = Regex("""kiosk-v?(\d+\.\d+(?:\.\d+)?)""")
|
||||||
|
.find(bodyText)?.groupValues?.get(1)
|
||||||
|
?.takeIf { it.isNotEmpty() }
|
||||||
|
?: norm(latestTag)
|
||||||
|
|
||||||
// Compare semver: returns true if `remote` is strictly greater than `local`
|
// Compare semver: returns true if `remote` is strictly greater than `local`
|
||||||
fun semverNewer(remote: String, local: String): Boolean {
|
fun semverNewer(remote: String, local: String): Boolean {
|
||||||
@@ -645,29 +653,31 @@ class KioskActivity : AppCompatActivity() {
|
|||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
|
val isSemver = remoteKioskVersion.matches(Regex("\\d+\\.\\d+.*"))
|
||||||
|
|
||||||
|
// Get APK URL from assets; fall back to the hardcoded KIOSK_DOWNLOAD_URL
|
||||||
val assets = json.optJSONArray("assets")
|
val assets = json.optJSONArray("assets")
|
||||||
var kioskApkUrl = ""
|
var kioskApkUrl = ""
|
||||||
if (assets != null) {
|
if (assets != null) {
|
||||||
for (i in 0 until assets.length()) {
|
for (i in 0 until assets.length()) {
|
||||||
val a = assets.getJSONObject(i)
|
val a = assets.getJSONObject(i)
|
||||||
val name = a.optString("name", "").lowercase()
|
val url = a.optString("browser_download_url", "")
|
||||||
val url = a.optString("browser_download_url", "")
|
if (url.endsWith(".apk", ignoreCase = true) && url.isNotEmpty()) {
|
||||||
if (name.contains("kiosk") && url.isNotEmpty()) kioskApkUrl = url
|
kioskApkUrl = url; break
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (kioskApkUrl.isEmpty()) kioskApkUrl = KIOSK_DOWNLOAD_URL
|
if (kioskApkUrl.isEmpty()) kioskApkUrl = KIOSK_DOWNLOAD_URL
|
||||||
|
|
||||||
// Only flag an update when the remote tag is parseable as semver AND
|
// Only flag an update when the remote version is parseable as semver AND
|
||||||
// the remote version is strictly greater than the installed version.
|
// strictly greater than the installed version.
|
||||||
// Non-semver tags (e.g. "kiosk-latest", "rolling") cannot be compared
|
val kioskNeedsUpdate = currentKiosk.isNotEmpty() && isSemver &&
|
||||||
// numerically → treat as "no update" to avoid false positives.
|
semverNewer(remoteKioskVersion, currentKiosk)
|
||||||
val kioskNeedsUpdate = currentKiosk.isNotEmpty() &&
|
|
||||||
isSemver && semverNewer(norm(latestTag), norm(currentKiosk))
|
|
||||||
|
|
||||||
val result = JSONObject()
|
val result = JSONObject()
|
||||||
.put("has_update", kioskNeedsUpdate)
|
.put("has_update", kioskNeedsUpdate)
|
||||||
.put("current", currentKiosk)
|
.put("current", currentKiosk)
|
||||||
.put("latest", latestTag)
|
.put("latest", remoteKioskVersion)
|
||||||
.put("apk_url", kioskApkUrl)
|
.put("apk_url", kioskApkUrl)
|
||||||
|
|
||||||
notifyJs(result)
|
notifyJs(result)
|
||||||
@@ -680,12 +690,11 @@ class KioskActivity : AppCompatActivity() {
|
|||||||
|
|
||||||
// Persist the pending update so the banner reappears after a crash/restart
|
// Persist the pending update so the banner reappears after a crash/restart
|
||||||
prefs.edit()
|
prefs.edit()
|
||||||
.putString(KEY_PENDING_UPDATE_VERSION, latestTag)
|
.putString(KEY_PENDING_UPDATE_VERSION, remoteKioskVersion)
|
||||||
.putString(KEY_PENDING_UPDATE_URL, kioskApkUrl)
|
.putString(KEY_PENDING_UPDATE_URL, kioskApkUrl)
|
||||||
.apply()
|
.apply()
|
||||||
|
|
||||||
val label = if (isSemver) "$currentKiosk → $latestTag" else latestTag
|
runOnUiThread { showNativeUpdateBanner("🔄 Kiosk $currentKiosk → $remoteKioskVersion", kioskApkUrl) }
|
||||||
runOnUiThread { showNativeUpdateBanner("🔄 Kiosk $label", kioskApkUrl) }
|
|
||||||
} catch (e: Exception) {
|
} catch (e: Exception) {
|
||||||
notifyJs(JSONObject().put("has_update", false).put("error", e.message ?: "network error"))
|
notifyJs(JSONObject().put("has_update", false).put("error", e.message ?: "network error"))
|
||||||
}
|
}
|
||||||
@@ -802,6 +811,52 @@ class KioskActivity : AppCompatActivity() {
|
|||||||
file.delete()
|
file.delete()
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
// ── Pre-install validation via PackageManager ──────────────────────
|
||||||
|
// This catches version-downgrade or same-version attempts before PackageInstaller
|
||||||
|
// gets them (which would silently fail with STATUS_FAILURE=1 on many OEMs).
|
||||||
|
@Suppress("DEPRECATION")
|
||||||
|
val apkInfo = try { packageManager.getPackageArchiveInfo(file.absolutePath, 0) } catch (_: Exception) { null }
|
||||||
|
if (apkInfo != null) {
|
||||||
|
// Wrong package: would always fail with STATUS_FAILURE=1
|
||||||
|
if (apkInfo.packageName != packageName) {
|
||||||
|
val detail = "APK package=${apkInfo.packageName}, expected=$packageName"
|
||||||
|
setInstallUI("\u274C", "APK non valido", detail, 0xFFf87171.toInt(), btnEnabled = true, progress = -2)
|
||||||
|
runOnUiThread { activeInstallBtn?.text = getString(R.string.install_btn_retry) }
|
||||||
|
ErrorReporter.reportMessage("install_wrong_package", detail, mapOf("apk_pkg" to apkInfo.packageName, "expected" to packageName), forceReport = true)
|
||||||
|
file.delete()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
// Version downgrade or same versionCode: Android rejects it
|
||||||
|
@Suppress("DEPRECATION")
|
||||||
|
val apkVc: Long = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P)
|
||||||
|
apkInfo.longVersionCode
|
||||||
|
else
|
||||||
|
apkInfo.versionCode.toLong()
|
||||||
|
val installedVc: Long = try {
|
||||||
|
@Suppress("DEPRECATION")
|
||||||
|
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P)
|
||||||
|
packageManager.getPackageInfo(packageName, 0).longVersionCode
|
||||||
|
else
|
||||||
|
packageManager.getPackageInfo(packageName, 0).versionCode.toLong()
|
||||||
|
} catch (_: Exception) { -1L }
|
||||||
|
|
||||||
|
if (installedVc >= 0 && apkVc <= installedVc) {
|
||||||
|
// Same or older version — no real update, dismiss banner silently
|
||||||
|
runOnUiThread {
|
||||||
|
updateBanner.visibility = View.GONE
|
||||||
|
bannerProgressBar.visibility = View.GONE
|
||||||
|
prefs.edit().remove(KEY_PENDING_UPDATE_VERSION).remove(KEY_PENDING_UPDATE_URL).apply()
|
||||||
|
}
|
||||||
|
ErrorReporter.reportMessage(
|
||||||
|
"install_no_upgrade",
|
||||||
|
"APK versionCode=$apkVc (${apkInfo.versionName}) ≤ installed=$installedVc — not an upgrade",
|
||||||
|
mapOf("apk_vc" to apkVc, "apk_ver" to (apkInfo.versionName ?: ""), "installed_vc" to installedVc),
|
||||||
|
forceReport = true
|
||||||
|
)
|
||||||
|
file.delete()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
// Only kiosk self-update is handled; gateway is now integrated
|
// Only kiosk self-update is handled; gateway is now integrated
|
||||||
val targetPkg = packageName
|
val targetPkg = packageName
|
||||||
installWithPackageInstaller(file, targetPkg)
|
installWithPackageInstaller(file, targetPkg)
|
||||||
|
|||||||
@@ -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'orologio e informazioni utili (statistiche, piano pasti). Lo schermo rimane SEMPRE acceso — questa opzione riguarda solo l'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>
|
||||||
|
|||||||
Reference in New Issue
Block a user