chore: auto-merge develop → main
Triggered by: fa9c52e feat(kiosk): complete setup wizard overhaul
This commit is contained in:
@@ -52,6 +52,13 @@
|
||||
</intent-filter>
|
||||
</activity>
|
||||
|
||||
<activity
|
||||
android:name=".SetupActivity"
|
||||
android:exported="false"
|
||||
android:theme="@style/Theme.MaterialComponents.Light.NoActionBar"
|
||||
android:configChanges="orientation|screenSize|keyboard|keyboardHidden"
|
||||
android:windowSoftInputMode="adjustResize" />
|
||||
|
||||
<activity
|
||||
android:name=".SettingsActivity"
|
||||
android:exported="false"
|
||||
|
||||
+183
-881
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,813 @@
|
||||
package it.dadaloop.evershelf.kiosk
|
||||
|
||||
import android.Manifest
|
||||
import android.app.AlertDialog
|
||||
import android.app.DownloadManager
|
||||
import android.app.PendingIntent
|
||||
import android.content.BroadcastReceiver
|
||||
import android.content.Context
|
||||
import android.content.Intent
|
||||
import android.content.IntentFilter
|
||||
import android.content.SharedPreferences
|
||||
import android.content.pm.PackageManager
|
||||
import android.net.Uri
|
||||
import android.net.wifi.WifiManager
|
||||
import android.os.Build
|
||||
import android.os.Bundle
|
||||
import android.os.Handler
|
||||
import android.os.Looper
|
||||
import android.provider.Settings
|
||||
import android.view.View
|
||||
import android.widget.EditText
|
||||
import android.widget.LinearLayout
|
||||
import android.widget.ProgressBar
|
||||
import android.widget.ScrollView
|
||||
import android.widget.TextView
|
||||
import androidx.appcompat.app.AppCompatActivity
|
||||
import androidx.core.app.ActivityCompat
|
||||
import androidx.core.content.ContextCompat
|
||||
import com.google.android.material.button.MaterialButton
|
||||
import java.net.HttpURLConnection
|
||||
import java.net.URL
|
||||
import java.util.concurrent.Executors
|
||||
import java.util.concurrent.TimeUnit
|
||||
import java.util.concurrent.atomic.AtomicBoolean
|
||||
import javax.net.ssl.HttpsURLConnection
|
||||
import javax.net.ssl.SSLContext
|
||||
import javax.net.ssl.TrustManager
|
||||
import javax.net.ssl.X509TrustManager
|
||||
|
||||
/**
|
||||
* Full setup wizard — runs BEFORE KioskActivity locks the screen.
|
||||
* The user can always exit (finishAffinity) via the ✕ button.
|
||||
*
|
||||
* Steps:
|
||||
* 0 — Welcome / intro / privacy
|
||||
* 1 — Permissions rationale + grant
|
||||
* 2 — Server URL + auto-discovery + connection test
|
||||
* 3 — Smart scale question → gateway info + install
|
||||
* 4 — Done
|
||||
*/
|
||||
class SetupActivity : AppCompatActivity() {
|
||||
|
||||
private lateinit var prefs: SharedPreferences
|
||||
private var currentStep = 0
|
||||
|
||||
// Step containers
|
||||
private lateinit var stepWelcome: LinearLayout
|
||||
private lateinit var stepPermissions: LinearLayout
|
||||
private lateinit var stepServer: LinearLayout
|
||||
private lateinit var stepScale: LinearLayout
|
||||
private lateinit var stepDone: LinearLayout
|
||||
|
||||
// Progress dots
|
||||
private lateinit var progressDots: LinearLayout
|
||||
|
||||
// Server step
|
||||
private lateinit var urlEdit: EditText
|
||||
private lateinit var urlStatus: TextView
|
||||
private lateinit var btnTestUrl: MaterialButton
|
||||
private lateinit var btnDiscover: MaterialButton
|
||||
private lateinit var discoverStatus: TextView
|
||||
|
||||
// Scale step
|
||||
private lateinit var scaleQuestionCard: LinearLayout
|
||||
private lateinit var gatewayInfoCard: LinearLayout
|
||||
private lateinit var gatewayInstallCard: LinearLayout
|
||||
private lateinit var gatewayStatusIcon: TextView
|
||||
private lateinit var gatewayStatusText: TextView
|
||||
private lateinit var gatewayStatusDetail: TextView
|
||||
private lateinit var btnInstallGateway: MaterialButton
|
||||
private lateinit var gatewayProgressBar: ProgressBar
|
||||
private lateinit var gatewayProgressText: TextView
|
||||
private lateinit var step3NextButtons: LinearLayout
|
||||
|
||||
// Done step
|
||||
private lateinit var summaryText: TextView
|
||||
|
||||
// Permissions step
|
||||
private lateinit var permsGrantedCard: LinearLayout
|
||||
|
||||
// APK install state (for gateway)
|
||||
private var pendingApkDownloadUrl = ""
|
||||
private var pendingInstallFile: java.io.File? = null
|
||||
private var pendingInstallPkg = ""
|
||||
private val pollHandler = Handler(Looper.getMainLooper())
|
||||
private var activeDownloadId: Long = -1
|
||||
|
||||
// Auto-discover cancellation flag
|
||||
private val discoverCancelled = AtomicBoolean(false)
|
||||
|
||||
companion object {
|
||||
private const val PREFS_NAME = "evershelf_kiosk"
|
||||
private const val KEY_URL = "evershelf_url"
|
||||
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_DOWNLOAD_URL =
|
||||
"https://github.com/dadaloop82/EverShelf/releases/latest/download/evershelf-scale-gateway.apk"
|
||||
private const val INSTALL_PERM_REQUEST = 2001
|
||||
private const val INSTALL_CONFIRM_REQUEST = 2002
|
||||
private const val UNINSTALL_REQUEST = 2003
|
||||
private const val PERMISSION_REQUEST_CODE = 2004
|
||||
}
|
||||
|
||||
// ── Lifecycle ─────────────────────────────────────────────────────────
|
||||
|
||||
override fun onCreate(savedInstanceState: Bundle?) {
|
||||
super.onCreate(savedInstanceState)
|
||||
setContentView(R.layout.activity_setup)
|
||||
prefs = getSharedPreferences(PREFS_NAME, Context.MODE_PRIVATE)
|
||||
bindViews()
|
||||
showStep(0)
|
||||
}
|
||||
|
||||
override fun onBackPressed() {
|
||||
when (currentStep) {
|
||||
0 -> confirmExit()
|
||||
else -> showStep(currentStep - 1)
|
||||
}
|
||||
}
|
||||
|
||||
override fun onDestroy() {
|
||||
pollHandler.removeCallbacksAndMessages(null)
|
||||
discoverCancelled.set(true)
|
||||
super.onDestroy()
|
||||
}
|
||||
|
||||
// ── Binding ────────────────────────────────────────────────────────────
|
||||
|
||||
private fun bindViews() {
|
||||
progressDots = findViewById(R.id.setupProgressDots)
|
||||
stepWelcome = findViewById(R.id.stepWelcome)
|
||||
stepPermissions = findViewById(R.id.stepPermissions)
|
||||
stepServer = findViewById(R.id.stepServer)
|
||||
stepScale = findViewById(R.id.stepScale)
|
||||
stepDone = findViewById(R.id.stepDone)
|
||||
|
||||
// Server step
|
||||
urlEdit = findViewById(R.id.setupUrlEdit)
|
||||
urlStatus = findViewById(R.id.setupUrlStatus)
|
||||
btnTestUrl = findViewById(R.id.btnSetupTestUrl)
|
||||
btnDiscover = findViewById(R.id.btnDiscover)
|
||||
discoverStatus = findViewById(R.id.discoverStatus)
|
||||
|
||||
// Scale step
|
||||
scaleQuestionCard = findViewById(R.id.scaleQuestionCard)
|
||||
gatewayInfoCard = findViewById(R.id.gatewayInfoCard)
|
||||
gatewayInstallCard = findViewById(R.id.gatewayInstallCard)
|
||||
gatewayStatusIcon = findViewById(R.id.gatewayStatusIcon)
|
||||
gatewayStatusText = findViewById(R.id.gatewayStatusText)
|
||||
gatewayStatusDetail = findViewById(R.id.gatewayStatusDetail)
|
||||
btnInstallGateway = findViewById(R.id.btnInstallGateway)
|
||||
gatewayProgressBar = findViewById(R.id.gatewayProgressBar)
|
||||
gatewayProgressText = findViewById(R.id.gatewayProgressText)
|
||||
step3NextButtons = findViewById(R.id.step3NextButtons)
|
||||
|
||||
// Done step
|
||||
summaryText = findViewById(R.id.setupSummaryText)
|
||||
|
||||
// Permissions step
|
||||
permsGrantedCard = findViewById(R.id.permsGrantedCard)
|
||||
|
||||
// Pre-fill saved URL
|
||||
val savedUrl = prefs.getString(KEY_URL, "") ?: ""
|
||||
if (savedUrl.isNotEmpty()) urlEdit.setText(savedUrl)
|
||||
|
||||
// ── Welcome ──────────────────────────────────────────────────────
|
||||
findViewById<MaterialButton>(R.id.btnSetupExit).setOnClickListener { confirmExit() }
|
||||
findViewById<MaterialButton>(R.id.btnWelcomeStart).setOnClickListener { showStep(1) }
|
||||
|
||||
// ── Permissions ──────────────────────────────────────────────────
|
||||
findViewById<MaterialButton>(R.id.btnGrantPerms).setOnClickListener { requestPermissions() }
|
||||
findViewById<MaterialButton>(R.id.btnPermsBack).setOnClickListener { showStep(0) }
|
||||
findViewById<MaterialButton>(R.id.btnPermsNext).setOnClickListener { showStep(2) }
|
||||
|
||||
// ── Server ───────────────────────────────────────────────────────
|
||||
btnDiscover.setOnClickListener { autoDiscover() }
|
||||
btnTestUrl.setOnClickListener { testConnection() }
|
||||
findViewById<MaterialButton>(R.id.btnServerBack).setOnClickListener { showStep(1) }
|
||||
findViewById<MaterialButton>(R.id.btnServerNext).setOnClickListener {
|
||||
val url = urlEdit.text.toString().trim()
|
||||
if (url.isEmpty()) {
|
||||
showUrlStatus(getString(R.string.setup_enter_url), false)
|
||||
return@setOnClickListener
|
||||
}
|
||||
prefs.edit().putString(KEY_URL, url).apply()
|
||||
ErrorReporter.init(this, url)
|
||||
showStep(3)
|
||||
}
|
||||
|
||||
// ── Scale ─────────────────────────────────────────────────────────
|
||||
findViewById<MaterialButton>(R.id.btnScaleYes).setOnClickListener {
|
||||
scaleQuestionCard.visibility = View.GONE
|
||||
gatewayInfoCard.visibility = View.VISIBLE
|
||||
gatewayInstallCard.visibility = View.VISIBLE
|
||||
step3NextButtons.visibility = View.VISIBLE
|
||||
checkGatewayStatus()
|
||||
}
|
||||
findViewById<MaterialButton>(R.id.btnScaleNo).setOnClickListener {
|
||||
prefs.edit().putBoolean(KEY_HAS_SCALE, false).apply()
|
||||
showStep(4)
|
||||
}
|
||||
btnInstallGateway.setOnClickListener {
|
||||
pendingApkDownloadUrl = GATEWAY_DOWNLOAD_URL
|
||||
triggerApkDownload(GATEWAY_DOWNLOAD_URL)
|
||||
}
|
||||
findViewById<MaterialButton>(R.id.btnScaleBack).setOnClickListener { showStep(2) }
|
||||
findViewById<MaterialButton>(R.id.btnScaleNext).setOnClickListener {
|
||||
prefs.edit().putBoolean(KEY_HAS_SCALE, true).apply()
|
||||
showStep(4)
|
||||
}
|
||||
|
||||
// ── Done ──────────────────────────────────────────────────────────
|
||||
findViewById<MaterialButton>(R.id.btnLaunch).setOnClickListener { finishSetup() }
|
||||
}
|
||||
|
||||
// ── Step navigation ───────────────────────────────────────────────────
|
||||
|
||||
private fun showStep(step: Int) {
|
||||
currentStep = step
|
||||
stepWelcome.visibility = if (step == 0) View.VISIBLE else View.GONE
|
||||
stepPermissions.visibility = if (step == 1) View.VISIBLE else View.GONE
|
||||
stepServer.visibility = if (step == 2) View.VISIBLE else View.GONE
|
||||
stepScale.visibility = if (step == 3) View.VISIBLE else View.GONE
|
||||
stepDone.visibility = if (step == 4) View.VISIBLE else View.GONE
|
||||
|
||||
updateProgressDots()
|
||||
|
||||
// Reset scale step when entering it
|
||||
if (step == 3) {
|
||||
scaleQuestionCard.visibility = View.VISIBLE
|
||||
gatewayInfoCard.visibility = View.GONE
|
||||
gatewayInstallCard.visibility = View.GONE
|
||||
step3NextButtons.visibility = View.GONE
|
||||
}
|
||||
|
||||
// Build summary when entering done step
|
||||
if (step == 4) buildSummary()
|
||||
|
||||
// Cancel auto-discover when leaving server step
|
||||
if (step != 2) discoverCancelled.set(true)
|
||||
|
||||
// Scroll to top
|
||||
try { findViewById<ScrollView>(R.id.setupScrollView).scrollTo(0, 0) } catch (_: Exception) {}
|
||||
}
|
||||
|
||||
private fun updateProgressDots() {
|
||||
progressDots.removeAllViews()
|
||||
// 4 dots (steps 1–4); step 0 welcome uses no dots
|
||||
val active = maxOf(currentStep, 1)
|
||||
val density = resources.displayMetrics.density
|
||||
for (i in 1..4) {
|
||||
val dot = View(this)
|
||||
val sizeDp = if (i == active) 10 else 7
|
||||
val px = (sizeDp * density).toInt()
|
||||
val lp = LinearLayout.LayoutParams(px, px)
|
||||
lp.marginStart = (5 * density).toInt()
|
||||
lp.marginEnd = (5 * density).toInt()
|
||||
dot.layoutParams = lp
|
||||
val bg = android.graphics.drawable.GradientDrawable()
|
||||
bg.shape = android.graphics.drawable.GradientDrawable.OVAL
|
||||
bg.setColor(when {
|
||||
i < active -> 0xFF34d399.toInt() // completed
|
||||
i == active -> 0xFF7c3aed.toInt() // current
|
||||
else -> 0xFF334155.toInt() // future
|
||||
})
|
||||
dot.background = bg
|
||||
progressDots.addView(dot)
|
||||
}
|
||||
}
|
||||
|
||||
// ── Exit ──────────────────────────────────────────────────────────────
|
||||
|
||||
private fun confirmExit() {
|
||||
AlertDialog.Builder(this)
|
||||
.setTitle(getString(R.string.setup_exit_title))
|
||||
.setMessage(getString(R.string.setup_exit_message))
|
||||
.setPositiveButton(getString(R.string.setup_exit_confirm)) { _, _ ->
|
||||
pollHandler.removeCallbacksAndMessages(null)
|
||||
discoverCancelled.set(true)
|
||||
finishAffinity()
|
||||
}
|
||||
.setNegativeButton(getString(R.string.setup_exit_cancel), null)
|
||||
.show()
|
||||
}
|
||||
|
||||
// ── Permissions ───────────────────────────────────────────────────────
|
||||
|
||||
private fun allPermissionsGranted(): Boolean {
|
||||
val cam = ContextCompat.checkSelfPermission(this, Manifest.permission.CAMERA) == PackageManager.PERMISSION_GRANTED
|
||||
val mic = ContextCompat.checkSelfPermission(this, Manifest.permission.RECORD_AUDIO) == PackageManager.PERMISSION_GRANTED
|
||||
return cam && mic
|
||||
}
|
||||
|
||||
private fun requestPermissions() {
|
||||
val needed = mutableListOf<String>()
|
||||
if (ContextCompat.checkSelfPermission(this, Manifest.permission.CAMERA) != PackageManager.PERMISSION_GRANTED)
|
||||
needed.add(Manifest.permission.CAMERA)
|
||||
if (ContextCompat.checkSelfPermission(this, Manifest.permission.RECORD_AUDIO) != PackageManager.PERMISSION_GRANTED)
|
||||
needed.add(Manifest.permission.RECORD_AUDIO)
|
||||
if (Build.VERSION.SDK_INT >= 33) {
|
||||
if (ContextCompat.checkSelfPermission(this, Manifest.permission.READ_MEDIA_IMAGES) != PackageManager.PERMISSION_GRANTED)
|
||||
needed.add(Manifest.permission.READ_MEDIA_IMAGES)
|
||||
} else {
|
||||
if (ContextCompat.checkSelfPermission(this, Manifest.permission.READ_EXTERNAL_STORAGE) != PackageManager.PERMISSION_GRANTED)
|
||||
needed.add(Manifest.permission.READ_EXTERNAL_STORAGE)
|
||||
}
|
||||
if (needed.isEmpty()) {
|
||||
// Already granted — show confirmation and allow next
|
||||
permsGrantedCard.visibility = View.VISIBLE
|
||||
} else {
|
||||
ActivityCompat.requestPermissions(this, needed.toTypedArray(), PERMISSION_REQUEST_CODE)
|
||||
}
|
||||
}
|
||||
|
||||
override fun onRequestPermissionsResult(requestCode: Int, permissions: Array<out String>, grantResults: IntArray) {
|
||||
super.onRequestPermissionsResult(requestCode, permissions, grantResults)
|
||||
if (requestCode == PERMISSION_REQUEST_CODE) {
|
||||
permsGrantedCard.visibility = View.VISIBLE
|
||||
// Proceed to next step regardless — user can always grant later
|
||||
Handler(Looper.getMainLooper()).postDelayed({ showStep(2) }, 600)
|
||||
}
|
||||
}
|
||||
|
||||
// ── Connection Test ───────────────────────────────────────────────────
|
||||
|
||||
private fun testConnection() {
|
||||
val url = urlEdit.text.toString().trim()
|
||||
if (url.isEmpty()) { showUrlStatus(getString(R.string.setup_enter_url), false); return }
|
||||
showUrlStatus(getString(R.string.setup_testing), null)
|
||||
|
||||
Thread {
|
||||
val base = url.trimEnd('/')
|
||||
// Try both API path variants
|
||||
val candidates = listOf(
|
||||
"$base/api/index.php?action=get_settings",
|
||||
"$base/api/?action=get_settings"
|
||||
)
|
||||
var found = false
|
||||
for (apiUrl in candidates) {
|
||||
val conn = openConn(apiUrl) ?: continue
|
||||
try {
|
||||
val code = conn.responseCode
|
||||
if (code !in 200..399) { conn.disconnect(); continue }
|
||||
val body = conn.inputStream.bufferedReader().readText()
|
||||
conn.disconnect()
|
||||
if (body.contains("gemini_key_set") || body.contains("\"success\"")) {
|
||||
found = true; break
|
||||
}
|
||||
} catch (_: Exception) { try { conn.disconnect() } catch (_: Exception) {} }
|
||||
}
|
||||
// If API not found, try plain base URL to distinguish unreachable vs wrong path
|
||||
if (!found) {
|
||||
var baseReachable = false
|
||||
try {
|
||||
val conn = openConn(base) ?: openConn("$base/")
|
||||
val code = conn?.responseCode ?: -1
|
||||
conn?.disconnect()
|
||||
baseReachable = code in 200..499
|
||||
} catch (_: Exception) {}
|
||||
runOnUiThread {
|
||||
if (baseReachable) {
|
||||
showUrlStatus("⚠ ${getString(R.string.setup_api_not_found)}", false)
|
||||
} else {
|
||||
showUrlStatus("✗ ${getString(R.string.setup_unreachable)}", false)
|
||||
}
|
||||
}
|
||||
} else {
|
||||
runOnUiThread { showUrlStatus("✅ ${getString(R.string.setup_server_found)}", true) }
|
||||
}
|
||||
}.start()
|
||||
}
|
||||
|
||||
private fun showUrlStatus(text: String, success: Boolean?) {
|
||||
urlStatus.visibility = View.VISIBLE
|
||||
urlStatus.text = text
|
||||
urlStatus.setTextColor(when (success) {
|
||||
true -> 0xFF34d399.toInt()
|
||||
false -> 0xFFf87171.toInt()
|
||||
null -> 0xFF94a3b8.toInt()
|
||||
})
|
||||
}
|
||||
|
||||
private fun openConn(urlStr: String): HttpURLConnection? {
|
||||
return try {
|
||||
val conn = URL(urlStr).openConnection()
|
||||
if (conn is HttpsURLConnection) {
|
||||
val trustAll = arrayOf<TrustManager>(object : X509TrustManager {
|
||||
override fun checkClientTrusted(c: Array<java.security.cert.X509Certificate>?, t: String?) {}
|
||||
override fun checkServerTrusted(c: Array<java.security.cert.X509Certificate>?, t: String?) {}
|
||||
override fun getAcceptedIssuers(): Array<java.security.cert.X509Certificate> = arrayOf()
|
||||
})
|
||||
val sc = SSLContext.getInstance("TLS")
|
||||
sc.init(null, trustAll, java.security.SecureRandom())
|
||||
conn.sslSocketFactory = sc.socketFactory
|
||||
conn.hostnameVerifier = javax.net.ssl.HostnameVerifier { _, _ -> true }
|
||||
}
|
||||
(conn as HttpURLConnection).apply {
|
||||
requestMethod = "GET"
|
||||
connectTimeout = 3000
|
||||
readTimeout = 3000
|
||||
}
|
||||
} catch (_: Exception) { null }
|
||||
}
|
||||
|
||||
// ── Auto-Discover ─────────────────────────────────────────────────────
|
||||
|
||||
@Suppress("DEPRECATION")
|
||||
private fun autoDiscover() {
|
||||
discoverCancelled.set(false)
|
||||
btnDiscover.isEnabled = false
|
||||
btnDiscover.text = getString(R.string.setup_discovering)
|
||||
discoverStatus.visibility = View.VISIBLE
|
||||
discoverStatus.text = getString(R.string.setup_discovering_detail)
|
||||
discoverStatus.setTextColor(0xFF94a3b8.toInt())
|
||||
|
||||
// Determine local subnet
|
||||
val wifiMgr = applicationContext.getSystemService(Context.WIFI_SERVICE) as WifiManager
|
||||
val ipInt = wifiMgr.connectionInfo.ipAddress
|
||||
val subnets = mutableListOf<String>()
|
||||
if (ipInt != 0) {
|
||||
val a = (ipInt shr 0) and 0xFF
|
||||
val b = (ipInt shr 8) and 0xFF
|
||||
val c = (ipInt shr 16) and 0xFF
|
||||
subnets += "$a.$b.$c"
|
||||
}
|
||||
// Always include common subnets as fallback
|
||||
for (s in listOf("192.168.1", "192.168.0", "192.168.2", "10.0.0")) {
|
||||
if (!subnets.contains(s)) subnets += s
|
||||
}
|
||||
|
||||
val ports = listOf(80, 8080)
|
||||
val paths = listOf(
|
||||
"/api/index.php?action=get_settings",
|
||||
"/dispensa/api/index.php?action=get_settings",
|
||||
"/evershelf/api/index.php?action=get_settings"
|
||||
)
|
||||
val executor = Executors.newFixedThreadPool(40)
|
||||
val found = AtomicBoolean(false)
|
||||
|
||||
Thread {
|
||||
val futures = mutableListOf<java.util.concurrent.Future<String?>>()
|
||||
outer@ for (subnet in subnets) {
|
||||
for (i in 1..254) {
|
||||
if (discoverCancelled.get() || found.get()) break@outer
|
||||
val ip = "$subnet.$i"
|
||||
for (port in ports) {
|
||||
if (discoverCancelled.get() || found.get()) break@outer
|
||||
futures += executor.submit<String?> submit@{
|
||||
if (discoverCancelled.get() || found.get()) return@submit null
|
||||
val scheme = if (port == 443 || port == 8443) "https" else "http"
|
||||
for (path in paths) {
|
||||
val urlStr = "$scheme://$ip:$port$path"
|
||||
try {
|
||||
val conn = openConn(urlStr) ?: continue
|
||||
val code = conn.responseCode
|
||||
if (code in 200..399) {
|
||||
val body = conn.inputStream.bufferedReader().readText()
|
||||
conn.disconnect()
|
||||
if (body.contains("gemini_key_set") || body.contains("\"success\"")) {
|
||||
val base = urlStr.substringBefore("/api/")
|
||||
return@submit "$base/"
|
||||
}
|
||||
} else conn.disconnect()
|
||||
} catch (_: Exception) {}
|
||||
}
|
||||
null
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Collect results
|
||||
for (f in futures) {
|
||||
if (discoverCancelled.get()) break
|
||||
val result = try { f.get(4, TimeUnit.SECONDS) } catch (_: Exception) { null }
|
||||
if (result != null && found.compareAndSet(false, true)) {
|
||||
runOnUiThread {
|
||||
urlEdit.setText(result)
|
||||
discoverStatus.text = "✅ ${getString(R.string.setup_server_found)}: $result"
|
||||
discoverStatus.setTextColor(0xFF34d399.toInt())
|
||||
showUrlStatus("✅ ${getString(R.string.setup_server_found)}", true)
|
||||
btnDiscover.isEnabled = true
|
||||
btnDiscover.text = getString(R.string.setup_discover_btn)
|
||||
}
|
||||
break
|
||||
}
|
||||
}
|
||||
executor.shutdown()
|
||||
|
||||
if (!found.get() && !discoverCancelled.get()) {
|
||||
runOnUiThread {
|
||||
discoverStatus.text = getString(R.string.setup_discover_not_found)
|
||||
discoverStatus.setTextColor(0xFFf87171.toInt())
|
||||
btnDiscover.isEnabled = true
|
||||
btnDiscover.text = getString(R.string.setup_discover_btn)
|
||||
}
|
||||
} else if (!found.get()) {
|
||||
runOnUiThread {
|
||||
btnDiscover.isEnabled = true
|
||||
btnDiscover.text = getString(R.string.setup_discover_btn)
|
||||
}
|
||||
}
|
||||
}.start()
|
||||
}
|
||||
|
||||
// ── Gateway ────────────────────────────────────────────────────────────
|
||||
|
||||
private fun isGatewayInstalled() = try {
|
||||
packageManager.getPackageInfo(GATEWAY_PACKAGE, 0); true
|
||||
} catch (_: PackageManager.NameNotFoundException) { false }
|
||||
|
||||
private fun checkGatewayStatus() {
|
||||
if (isGatewayInstalled()) {
|
||||
gatewayStatusIcon.text = "✅"
|
||||
gatewayStatusText.text = getString(R.string.wizard_gateway_installed)
|
||||
gatewayStatusDetail.text = getString(R.string.wizard_gateway_installed_detail)
|
||||
gatewayStatusDetail.setTextColor(0xFF34d399.toInt())
|
||||
btnInstallGateway.visibility = View.GONE
|
||||
gatewayProgressBar.visibility = View.GONE
|
||||
gatewayProgressText.visibility = View.GONE
|
||||
} else {
|
||||
gatewayStatusIcon.text = "📲"
|
||||
gatewayStatusText.text = getString(R.string.wizard_gateway_not_installed)
|
||||
gatewayStatusDetail.text = getString(R.string.wizard_gateway_not_installed_detail)
|
||||
gatewayStatusDetail.setTextColor(0xFFfbbf24.toInt())
|
||||
btnInstallGateway.visibility = View.VISIBLE
|
||||
}
|
||||
}
|
||||
|
||||
private fun setGatewayUI(icon: String, text: String, detail: String, color: Int,
|
||||
btnEnabled: Boolean = true, progress: Int = -2) {
|
||||
runOnUiThread {
|
||||
gatewayStatusIcon.text = icon
|
||||
gatewayStatusText.text = text
|
||||
gatewayStatusDetail.text = detail
|
||||
gatewayStatusDetail.setTextColor(color)
|
||||
btnInstallGateway.isEnabled = btnEnabled
|
||||
when {
|
||||
progress == -2 -> {
|
||||
gatewayProgressBar.visibility = View.GONE
|
||||
gatewayProgressText.visibility = View.GONE
|
||||
}
|
||||
progress == -1 -> {
|
||||
gatewayProgressBar.isIndeterminate = true
|
||||
gatewayProgressBar.visibility = View.VISIBLE
|
||||
gatewayProgressText.visibility = View.GONE
|
||||
}
|
||||
else -> {
|
||||
gatewayProgressBar.isIndeterminate = false
|
||||
gatewayProgressBar.progress = progress
|
||||
gatewayProgressBar.visibility = View.VISIBLE
|
||||
gatewayProgressText.visibility = View.VISIBLE
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun startProgressPoll(downloadId: Long) {
|
||||
activeDownloadId = downloadId
|
||||
pollHandler.removeCallbacksAndMessages(null)
|
||||
fun tick() {
|
||||
if (activeDownloadId != downloadId) return
|
||||
val dm = getSystemService(DOWNLOAD_SERVICE) as DownloadManager
|
||||
val c = dm.query(DownloadManager.Query().setFilterById(downloadId))
|
||||
if (!c.moveToFirst()) { c.close(); return }
|
||||
val status = c.getInt(c.getColumnIndexOrThrow(DownloadManager.COLUMN_STATUS))
|
||||
if (status == DownloadManager.STATUS_RUNNING || status == DownloadManager.STATUS_PENDING) {
|
||||
val dl = c.getLong(c.getColumnIndexOrThrow(DownloadManager.COLUMN_BYTES_DOWNLOADED_SO_FAR))
|
||||
val tot = c.getLong(c.getColumnIndexOrThrow(DownloadManager.COLUMN_TOTAL_SIZE_BYTES))
|
||||
c.close()
|
||||
val pct = if (tot > 0) (dl * 100 / tot).toInt() else 0
|
||||
val txt = if (tot > 0) "%.1f / %.1f MB".format(dl / 1_048_576f, tot / 1_048_576f) else ""
|
||||
setGatewayUI(
|
||||
"⏳",
|
||||
getString(R.string.install_downloading) + if (tot > 0) " ($pct%)" else "",
|
||||
txt, 0xFF94a3b8.toInt(), btnEnabled = false, progress = pct
|
||||
)
|
||||
runOnUiThread { gatewayProgressText.text = txt }
|
||||
pollHandler.postDelayed({ tick() }, 500)
|
||||
} else {
|
||||
c.close()
|
||||
}
|
||||
}
|
||||
pollHandler.post { tick() }
|
||||
}
|
||||
|
||||
private fun triggerApkDownload(apkUrl: String) {
|
||||
pendingApkDownloadUrl = apkUrl
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O && !packageManager.canRequestPackageInstalls()) {
|
||||
@Suppress("DEPRECATION")
|
||||
startActivityForResult(
|
||||
Intent(Settings.ACTION_MANAGE_UNKNOWN_APP_SOURCES, Uri.parse("package:$packageName")),
|
||||
INSTALL_PERM_REQUEST
|
||||
)
|
||||
return
|
||||
}
|
||||
setGatewayUI("⏳", getString(R.string.install_downloading), "", 0xFF94a3b8.toInt(), btnEnabled = false, progress = -1)
|
||||
val destDir = getExternalFilesDir(null) ?: filesDir
|
||||
val destFile = java.io.File(destDir, "evershelf-gateway-setup.apk")
|
||||
val dm = getSystemService(DOWNLOAD_SERVICE) as DownloadManager
|
||||
val req = DownloadManager.Request(Uri.parse(apkUrl)).apply {
|
||||
setTitle("EverShelf Scale Gateway")
|
||||
setDescription(getString(R.string.install_downloading))
|
||||
setNotificationVisibility(DownloadManager.Request.VISIBILITY_VISIBLE_NOTIFY_COMPLETED)
|
||||
setDestinationUri(Uri.fromFile(destFile))
|
||||
setMimeType("application/vnd.android.package-archive")
|
||||
}
|
||||
val downloadId = dm.enqueue(req)
|
||||
startProgressPoll(downloadId)
|
||||
|
||||
val receiver = object : BroadcastReceiver() {
|
||||
override fun onReceive(ctx: Context?, intent: Intent?) {
|
||||
val id = intent?.getLongExtra(DownloadManager.EXTRA_DOWNLOAD_ID, -1)
|
||||
if (id != downloadId) return
|
||||
unregisterReceiver(this)
|
||||
val q = DownloadManager.Query().setFilterById(downloadId)
|
||||
val c = (getSystemService(DOWNLOAD_SERVICE) as DownloadManager).query(q)
|
||||
var ok = false
|
||||
if (c.moveToFirst()) {
|
||||
ok = c.getInt(c.getColumnIndexOrThrow(DownloadManager.COLUMN_STATUS)) ==
|
||||
DownloadManager.STATUS_SUCCESSFUL
|
||||
}
|
||||
c.close()
|
||||
pollHandler.removeCallbacksAndMessages(null)
|
||||
activeDownloadId = -1
|
||||
if (ok) {
|
||||
setGatewayUI("⏳", getString(R.string.install_installing), "", 0xFF94a3b8.toInt(), btnEnabled = false, progress = -1)
|
||||
installApk(destFile)
|
||||
} else {
|
||||
setGatewayUI("❌", getString(R.string.install_error_download), getString(R.string.install_error_download_detail), 0xFFf87171.toInt())
|
||||
}
|
||||
}
|
||||
}
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
|
||||
registerReceiver(receiver, IntentFilter(DownloadManager.ACTION_DOWNLOAD_COMPLETE), RECEIVER_EXPORTED)
|
||||
} else {
|
||||
@Suppress("UnspecifiedRegisterReceiverFlag")
|
||||
registerReceiver(receiver, IntentFilter(DownloadManager.ACTION_DOWNLOAD_COMPLETE))
|
||||
}
|
||||
}
|
||||
|
||||
private fun installApk(file: java.io.File) {
|
||||
if (!file.exists() || file.length() == 0L) {
|
||||
setGatewayUI("❌", getString(R.string.install_error_download), "File APK non trovato sul dispositivo.", 0xFFf87171.toInt())
|
||||
return
|
||||
}
|
||||
// Validate APK magic bytes (ZIP header)
|
||||
val magic = try { file.inputStream().use { s -> val b = ByteArray(4); s.read(b); b } } catch (_: Exception) { null }
|
||||
if (magic == null || magic[0] != 0x50.toByte() || magic[1] != 0x4B.toByte()) {
|
||||
setGatewayUI("❌", getString(R.string.install_error_download), "Il file scaricato non è un APK valido.", 0xFFf87171.toInt())
|
||||
file.delete()
|
||||
return
|
||||
}
|
||||
installWithPackageInstaller(file, GATEWAY_PACKAGE)
|
||||
}
|
||||
|
||||
private fun installWithPackageInstaller(file: java.io.File, targetPkg: String) {
|
||||
try {
|
||||
val pi = packageManager.packageInstaller
|
||||
val params = android.content.pm.PackageInstaller.SessionParams(
|
||||
android.content.pm.PackageInstaller.SessionParams.MODE_FULL_INSTALL
|
||||
)
|
||||
params.setAppPackageName(targetPkg)
|
||||
val sessionId = pi.createSession(params)
|
||||
pi.openSession(sessionId).use { session ->
|
||||
file.inputStream().use { input ->
|
||||
session.openWrite("package", 0, file.length()).use { out ->
|
||||
input.copyTo(out)
|
||||
session.fsync(out)
|
||||
}
|
||||
}
|
||||
val action = "it.dadaloop.evershelf.kiosk.SETUP_INSTALL_$sessionId"
|
||||
val resultReceiver = object : BroadcastReceiver() {
|
||||
override fun onReceive(ctx: Context?, intent: Intent?) {
|
||||
unregisterReceiver(this)
|
||||
val status = intent?.getIntExtra(
|
||||
android.content.pm.PackageInstaller.EXTRA_STATUS,
|
||||
android.content.pm.PackageInstaller.STATUS_FAILURE
|
||||
) ?: android.content.pm.PackageInstaller.STATUS_FAILURE
|
||||
when (status) {
|
||||
android.content.pm.PackageInstaller.STATUS_PENDING_USER_ACTION -> {
|
||||
@Suppress("DEPRECATION")
|
||||
val confirmIntent = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU)
|
||||
intent?.getParcelableExtra(Intent.EXTRA_INTENT, Intent::class.java)
|
||||
else intent?.getParcelableExtra(Intent.EXTRA_INTENT)
|
||||
if (confirmIntent != null) {
|
||||
pendingInstallFile = file
|
||||
pendingInstallPkg = targetPkg
|
||||
@Suppress("DEPRECATION")
|
||||
startActivityForResult(confirmIntent, INSTALL_CONFIRM_REQUEST)
|
||||
}
|
||||
}
|
||||
android.content.pm.PackageInstaller.STATUS_SUCCESS -> {
|
||||
setGatewayUI("✅", getString(R.string.install_success), getString(R.string.install_success_detail), 0xFF34d399.toInt(), btnEnabled = false)
|
||||
Handler(Looper.getMainLooper()).postDelayed({ checkGatewayStatus() }, 1500)
|
||||
}
|
||||
android.content.pm.PackageInstaller.STATUS_FAILURE_INCOMPATIBLE,
|
||||
android.content.pm.PackageInstaller.STATUS_FAILURE_CONFLICT -> {
|
||||
runOnUiThread { offerUninstallAndRetry(file, targetPkg) }
|
||||
}
|
||||
else -> {
|
||||
val msg = intent?.getStringExtra(android.content.pm.PackageInstaller.EXTRA_STATUS_MESSAGE) ?: "status=$status"
|
||||
setGatewayUI("❌", getString(R.string.install_error_install), msg, 0xFFf87171.toInt())
|
||||
val pkgInstalled = try { packageManager.getPackageInfo(targetPkg, 0); true } catch (_: Exception) { false }
|
||||
if (pkgInstalled) runOnUiThread { offerUninstallAndRetry(file, targetPkg) }
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
val flags = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) RECEIVER_NOT_EXPORTED else 0
|
||||
registerReceiver(resultReceiver, IntentFilter(action), flags)
|
||||
val pi2 = PendingIntent.getBroadcast(
|
||||
this, sessionId,
|
||||
Intent(action).setPackage(packageName),
|
||||
PendingIntent.FLAG_UPDATE_CURRENT or PendingIntent.FLAG_IMMUTABLE
|
||||
)
|
||||
session.commit(pi2.intentSender)
|
||||
}
|
||||
setGatewayUI("⏳", getString(R.string.install_installing), "", 0xFF94a3b8.toInt(), btnEnabled = false, progress = -1)
|
||||
} catch (e: Exception) {
|
||||
setGatewayUI("❌", getString(R.string.install_error_install), e.message ?: "", 0xFFf87171.toInt())
|
||||
}
|
||||
}
|
||||
|
||||
private fun offerUninstallAndRetry(file: java.io.File, pkg: String) {
|
||||
pendingInstallFile = file
|
||||
pendingInstallPkg = pkg
|
||||
AlertDialog.Builder(this)
|
||||
.setTitle("⚠️ Conflitto firma APK")
|
||||
.setMessage("L'app installata usa una firma diversa. Devi prima disinstallare la versione precedente.\n\nDisinstalla ora? L'installazione riprenderà automaticamente.")
|
||||
.setPositiveButton("Disinstalla") { _, _ ->
|
||||
@Suppress("DEPRECATION")
|
||||
startActivityForResult(
|
||||
Intent(Intent.ACTION_DELETE, Uri.parse("package:$pkg")),
|
||||
UNINSTALL_REQUEST
|
||||
)
|
||||
}
|
||||
.setNegativeButton("Annulla", null)
|
||||
.show()
|
||||
}
|
||||
|
||||
// ── Summary / Finish ─────────────────────────────────────────────────
|
||||
|
||||
private fun buildSummary() {
|
||||
val url = prefs.getString(KEY_URL, "") ?: ""
|
||||
val hasScale = prefs.getBoolean(KEY_HAS_SCALE, false)
|
||||
val gwOk = hasScale && isGatewayInstalled()
|
||||
val sb = StringBuilder()
|
||||
if (url.isNotEmpty()) sb.appendLine("🌐 Server: $url")
|
||||
sb.appendLine(when {
|
||||
gwOk -> "✅ Scale Gateway: installato"
|
||||
hasScale -> "⚠️ Scale Gateway: non ancora installato"
|
||||
else -> "⏭ Bilancia: non configurata"
|
||||
})
|
||||
summaryText.text = sb.toString().trimEnd()
|
||||
}
|
||||
|
||||
private fun finishSetup() {
|
||||
prefs.edit().putBoolean(KEY_SETUP_COMPLETE, true).apply()
|
||||
setResult(RESULT_OK)
|
||||
finish()
|
||||
}
|
||||
|
||||
// ── Activity Results ─────────────────────────────────────────────────
|
||||
|
||||
@Suppress("DEPRECATION")
|
||||
override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
|
||||
super.onActivityResult(requestCode, resultCode, data)
|
||||
when (requestCode) {
|
||||
INSTALL_PERM_REQUEST -> {
|
||||
if (pendingApkDownloadUrl.isNotEmpty()) triggerApkDownload(pendingApkDownloadUrl)
|
||||
}
|
||||
INSTALL_CONFIRM_REQUEST -> {
|
||||
if (resultCode == RESULT_OK) {
|
||||
setGatewayUI("✅", getString(R.string.install_success), getString(R.string.install_success_detail), 0xFF34d399.toInt(), btnEnabled = false)
|
||||
Handler(Looper.getMainLooper()).postDelayed({ checkGatewayStatus() }, 1500)
|
||||
} else {
|
||||
val f = pendingInstallFile
|
||||
val pkg = pendingInstallPkg
|
||||
if (f != null && f.exists() && pkg.isNotEmpty()) {
|
||||
runOnUiThread {
|
||||
AlertDialog.Builder(this)
|
||||
.setTitle("⚠️ Installazione non riuscita")
|
||||
.setMessage("Se c'è un conflitto di firma, devi disinstallare la versione precedente.\n\nDisinstalla ora?")
|
||||
.setPositiveButton("Disinstalla") { _, _ ->
|
||||
startActivityForResult(Intent(Intent.ACTION_DELETE, Uri.parse("package:$pkg")), UNINSTALL_REQUEST)
|
||||
}
|
||||
.setNegativeButton("Annulla", null).show()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
UNINSTALL_REQUEST -> {
|
||||
val f = pendingInstallFile
|
||||
val pkg = pendingInstallPkg
|
||||
if (f != null && f.exists() && pkg.isNotEmpty()) {
|
||||
Handler(Looper.getMainLooper()).postDelayed({ installWithPackageInstaller(f, pkg) }, 600)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -4,7 +4,7 @@
|
||||
android:layout_height="match_parent"
|
||||
android:background="#0f172a">
|
||||
|
||||
<!-- Splash screen (shown briefly on launch) -->
|
||||
<!-- Splash screen (shown briefly on launch if setup is already done) -->
|
||||
<LinearLayout
|
||||
android:id="@+id/splashContainer"
|
||||
android:layout_width="match_parent"
|
||||
@@ -32,7 +32,7 @@
|
||||
<TextView
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="Smart Pantry Manager"
|
||||
android:text="La tua dispensa smart"
|
||||
android:textColor="#64748b"
|
||||
android:textSize="16sp"
|
||||
android:layout_marginBottom="48dp" />
|
||||
@@ -43,486 +43,6 @@
|
||||
android:indeterminateTint="#7c3aed" />
|
||||
</LinearLayout>
|
||||
|
||||
<!-- Setup wizard container -->
|
||||
<ScrollView
|
||||
android:id="@+id/wizardContainer"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:visibility="gone"
|
||||
android:fillViewport="true">
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="vertical"
|
||||
android:gravity="center_horizontal"
|
||||
android:paddingStart="32dp"
|
||||
android:paddingEnd="32dp"
|
||||
android:paddingTop="48dp"
|
||||
android:paddingBottom="48dp">
|
||||
|
||||
<!-- Step indicator -->
|
||||
<LinearLayout
|
||||
android:id="@+id/stepIndicator"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="horizontal"
|
||||
android:layout_marginBottom="40dp"
|
||||
android:gravity="center" />
|
||||
|
||||
<!-- Step 1: Welcome -->
|
||||
<LinearLayout
|
||||
android:id="@+id/step1"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="vertical"
|
||||
android:gravity="center_horizontal"
|
||||
android:visibility="visible">
|
||||
|
||||
<ImageView
|
||||
android:layout_width="96dp"
|
||||
android:layout_height="96dp"
|
||||
android:src="@drawable/ic_launcher_foreground"
|
||||
android:layout_marginBottom="16dp"
|
||||
android:contentDescription="EverShelf" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/wizardTitle"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="Welcome to\nEverShelf Kiosk"
|
||||
android:textColor="#f1f5f9"
|
||||
android:textSize="28sp"
|
||||
android:textStyle="bold"
|
||||
android:gravity="center"
|
||||
android:lineSpacingExtra="4dp"
|
||||
android:layout_marginBottom="16dp" />
|
||||
|
||||
<TextView
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="Turn this tablet into a dedicated kitchen panel for your smart pantry. Always-on display with built-in smart scale support."
|
||||
android:textColor="#94a3b8"
|
||||
android:textSize="16sp"
|
||||
android:gravity="center"
|
||||
android:lineSpacingExtra="6dp"
|
||||
android:layout_marginBottom="48dp" />
|
||||
|
||||
<!-- Feature highlights -->
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="vertical"
|
||||
android:layout_marginBottom="48dp">
|
||||
|
||||
<TextView
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="📺 Full-screen kiosk mode"
|
||||
android:textColor="#cbd5e1"
|
||||
android:textSize="15sp"
|
||||
android:paddingTop="8dp"
|
||||
android:paddingBottom="8dp" />
|
||||
|
||||
<TextView
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="⚖️ Bluetooth scale via Gateway app"
|
||||
android:textColor="#cbd5e1"
|
||||
android:textSize="15sp"
|
||||
android:paddingTop="8dp"
|
||||
android:paddingBottom="8dp" />
|
||||
|
||||
<TextView
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="🔋 Always-on, screen never sleeps"
|
||||
android:textColor="#cbd5e1"
|
||||
android:textSize="15sp"
|
||||
android:paddingTop="8dp"
|
||||
android:paddingBottom="8dp" />
|
||||
|
||||
<TextView
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="📷 Barcode scanning from WebView"
|
||||
android:textColor="#cbd5e1"
|
||||
android:textSize="15sp"
|
||||
android:paddingTop="8dp"
|
||||
android:paddingBottom="8dp" />
|
||||
</LinearLayout>
|
||||
|
||||
<com.google.android.material.button.MaterialButton
|
||||
android:id="@+id/btnGetStarted"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="56dp"
|
||||
android:text="Get Started"
|
||||
android:textSize="17sp"
|
||||
android:textAllCaps="false"
|
||||
android:backgroundTint="#7c3aed" />
|
||||
</LinearLayout>
|
||||
|
||||
<!-- Step 2: Server URL -->
|
||||
<LinearLayout
|
||||
android:id="@+id/step2"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="vertical"
|
||||
android:gravity="center_horizontal"
|
||||
android:visibility="gone">
|
||||
|
||||
<TextView
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="🌐"
|
||||
android:textSize="48sp"
|
||||
android:layout_marginBottom="16dp" />
|
||||
|
||||
<TextView
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="Connect to EverShelf"
|
||||
android:textColor="#f1f5f9"
|
||||
android:textSize="24sp"
|
||||
android:textStyle="bold"
|
||||
android:layout_marginBottom="8dp" />
|
||||
|
||||
<TextView
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="Enter the URL of your EverShelf server. This is the same address you use in your browser."
|
||||
android:textColor="#94a3b8"
|
||||
android:textSize="15sp"
|
||||
android:gravity="center"
|
||||
android:lineSpacingExtra="4dp"
|
||||
android:layout_marginBottom="32dp" />
|
||||
|
||||
<TextView
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="Server URL"
|
||||
android:textColor="#cbd5e1"
|
||||
android:textSize="13sp"
|
||||
android:textStyle="bold"
|
||||
android:layout_marginBottom="8dp" />
|
||||
|
||||
<EditText
|
||||
android:id="@+id/wizardUrl"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:hint="https://192.168.1.100/dispensa/"
|
||||
android:inputType="textUri"
|
||||
android:textColor="#f1f5f9"
|
||||
android:textColorHint="#475569"
|
||||
android:background="@drawable/input_background"
|
||||
android:padding="16dp"
|
||||
android:textSize="16sp"
|
||||
android:singleLine="true"
|
||||
android:layout_marginBottom="12dp" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/urlStatus"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:text=""
|
||||
android:textSize="13sp"
|
||||
android:visibility="gone"
|
||||
android:layout_marginBottom="12dp" />
|
||||
|
||||
<com.google.android.material.button.MaterialButton
|
||||
android:id="@+id/btnTestUrl"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="48dp"
|
||||
android:text="🔗 Test Connection"
|
||||
android:textSize="15sp"
|
||||
android:textAllCaps="false"
|
||||
style="@style/Widget.MaterialComponents.Button.OutlinedButton"
|
||||
android:strokeColor="#334155"
|
||||
android:textColor="#94a3b8"
|
||||
android:layout_marginBottom="24dp" />
|
||||
|
||||
<TextView
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="💡 Tip: Copy the exact URL from your browser address bar — including the path (e.g. /dispensa/)."
|
||||
android:textColor="#64748b"
|
||||
android:textSize="13sp"
|
||||
android:lineSpacingExtra="2dp"
|
||||
android:background="@drawable/tip_background"
|
||||
android:padding="14dp"
|
||||
android:layout_marginBottom="32dp" />
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="horizontal"
|
||||
android:gravity="center">
|
||||
|
||||
<com.google.android.material.button.MaterialButton
|
||||
android:id="@+id/btnStep2Back"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="48dp"
|
||||
android:layout_weight="1"
|
||||
android:text="Back"
|
||||
android:textSize="15sp"
|
||||
android:textAllCaps="false"
|
||||
style="@style/Widget.MaterialComponents.Button.OutlinedButton"
|
||||
android:strokeColor="#334155"
|
||||
android:textColor="#94a3b8"
|
||||
android:layout_marginEnd="12dp" />
|
||||
|
||||
<com.google.android.material.button.MaterialButton
|
||||
android:id="@+id/btnStep2Next"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="48dp"
|
||||
android:layout_weight="2"
|
||||
android:text="Next →"
|
||||
android:textSize="15sp"
|
||||
android:textAllCaps="false"
|
||||
android:backgroundTint="#7c3aed" />
|
||||
</LinearLayout>
|
||||
</LinearLayout>
|
||||
|
||||
<!-- Step 3: Scale Setup -->
|
||||
<LinearLayout
|
||||
android:id="@+id/step3"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="vertical"
|
||||
android:gravity="center_horizontal"
|
||||
android:visibility="gone">
|
||||
|
||||
<TextView
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="⚖️"
|
||||
android:textSize="48sp"
|
||||
android:layout_marginBottom="16dp" />
|
||||
|
||||
<TextView
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="@string/wizard_step3_title"
|
||||
android:textColor="#f1f5f9"
|
||||
android:textSize="24sp"
|
||||
android:textStyle="bold"
|
||||
android:layout_marginBottom="8dp" />
|
||||
|
||||
<TextView
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="@string/wizard_step3_description"
|
||||
android:textColor="#94a3b8"
|
||||
android:textSize="15sp"
|
||||
android:gravity="center"
|
||||
android:lineSpacingExtra="4dp"
|
||||
android:layout_marginBottom="16dp" />
|
||||
|
||||
<!-- Server reachability check — shown as soon as step 3 is entered -->
|
||||
<LinearLayout
|
||||
android:id="@+id/serverStatusCard"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="horizontal"
|
||||
android:background="@drawable/card_background"
|
||||
android:paddingStart="16dp"
|
||||
android:paddingEnd="16dp"
|
||||
android:paddingTop="12dp"
|
||||
android:paddingBottom="12dp"
|
||||
android:layout_marginBottom="16dp"
|
||||
android:gravity="center_vertical">
|
||||
|
||||
<TextView
|
||||
android:id="@+id/serverCheckIcon"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="⏳"
|
||||
android:textSize="20sp"
|
||||
android:layout_marginEnd="12dp" />
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_weight="1"
|
||||
android:orientation="vertical">
|
||||
|
||||
<TextView
|
||||
android:id="@+id/serverCheckText"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="@string/wizard_server_checking"
|
||||
android:textColor="#94a3b8"
|
||||
android:textSize="13sp" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/serverCheckDetail"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:text=""
|
||||
android:textColor="#64748b"
|
||||
android:textSize="11sp"
|
||||
android:visibility="gone" />
|
||||
</LinearLayout>
|
||||
</LinearLayout>
|
||||
|
||||
<!-- 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
|
||||
android:id="@+id/scaleStatusCard"
|
||||
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:id="@+id/scaleStatusIcon"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="🔍"
|
||||
android:textSize="32sp"
|
||||
android:layout_gravity="center"
|
||||
android:layout_marginBottom="8dp" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/scaleStatusText"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="Checking..."
|
||||
android:textColor="#cbd5e1"
|
||||
android:textSize="16sp"
|
||||
android:gravity="center"
|
||||
android:layout_marginBottom="4dp" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/scaleStatusDetail"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:text=""
|
||||
android:textColor="#64748b"
|
||||
android:textSize="13sp"
|
||||
android:gravity="center" />
|
||||
|
||||
<!-- Download / install progress bar — shown only during active download -->
|
||||
<ProgressBar
|
||||
android:id="@+id/downloadProgressBar"
|
||||
style="@android:style/Widget.ProgressBar.Horizontal"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="8dp"
|
||||
android:layout_marginTop="14dp"
|
||||
android:layout_marginBottom="2dp"
|
||||
android:progressTint="#7c3aed"
|
||||
android:progressBackgroundTint="#334155"
|
||||
android:max="100"
|
||||
android:progress="0"
|
||||
android:indeterminate="false"
|
||||
android:visibility="gone" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/downloadProgressText"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:text=""
|
||||
android:textColor="#94a3b8"
|
||||
android:textSize="12sp"
|
||||
android:gravity="center"
|
||||
android:visibility="gone" />
|
||||
</LinearLayout>
|
||||
|
||||
<!-- Bottom nav (Back / Launch) — hidden until user answers the question -->
|
||||
<LinearLayout
|
||||
android:id="@+id/step3BottomButtons"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="horizontal"
|
||||
android:gravity="center"
|
||||
android:layout_marginTop="16dp"
|
||||
android:visibility="gone">
|
||||
|
||||
<com.google.android.material.button.MaterialButton
|
||||
android:id="@+id/btnStep3Back"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="48dp"
|
||||
android:layout_weight="1"
|
||||
android:text="@string/btn_back"
|
||||
android:textSize="15sp"
|
||||
android:textAllCaps="false"
|
||||
style="@style/Widget.MaterialComponents.Button.OutlinedButton"
|
||||
android:strokeColor="#334155"
|
||||
android:textColor="#94a3b8"
|
||||
android:layout_marginEnd="12dp" />
|
||||
|
||||
<com.google.android.material.button.MaterialButton
|
||||
android:id="@+id/btnFinish"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="56dp"
|
||||
android:layout_weight="2"
|
||||
android:text="@string/btn_launch"
|
||||
android:textSize="16sp"
|
||||
android:textAllCaps="false"
|
||||
android:backgroundTint="#059669" />
|
||||
</LinearLayout>
|
||||
|
||||
<!-- Install/Update gateway button — shown by checkGatewayStatus() as needed -->
|
||||
<com.google.android.material.button.MaterialButton
|
||||
android:id="@+id/btnSkipScale"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:textSize="14sp"
|
||||
android:textAllCaps="false"
|
||||
style="@style/Widget.MaterialComponents.Button.OutlinedButton"
|
||||
android:strokeColor="#7c3aed"
|
||||
android:textColor="#a78bfa"
|
||||
android:layout_marginTop="12dp"
|
||||
android:visibility="gone" />
|
||||
</LinearLayout>
|
||||
|
||||
</LinearLayout>
|
||||
</ScrollView>
|
||||
|
||||
<!-- WebView (shown after setup) -->
|
||||
<WebView
|
||||
android:id="@+id/webView"
|
||||
@@ -545,7 +65,7 @@
|
||||
android:scaleType="centerInside"
|
||||
android:visibility="gone" />
|
||||
|
||||
<!-- ── Update banner (shown at the TOP when a new version is available) ── -->
|
||||
<!-- Update banner (shown at the TOP when a new version is available) -->
|
||||
<LinearLayout
|
||||
android:id="@+id/updateBanner"
|
||||
android:layout_width="match_parent"
|
||||
@@ -573,8 +93,7 @@
|
||||
android:layout_weight="1"
|
||||
android:textColor="#fbbf24"
|
||||
android:textSize="13sp"
|
||||
android:text=""
|
||||
android:drawablePadding="6dp" />
|
||||
android:text="" />
|
||||
|
||||
<com.google.android.material.button.MaterialButton
|
||||
android:id="@+id/btnInstallUpdate"
|
||||
@@ -599,7 +118,7 @@
|
||||
style="@style/Widget.MaterialComponents.Button.TextButton" />
|
||||
</LinearLayout>
|
||||
|
||||
<!-- Thin progress bar at the bottom of the banner — visible during download/install -->
|
||||
<!-- Thin progress bar at the bottom of the banner -->
|
||||
<ProgressBar
|
||||
android:id="@+id/bannerProgressBar"
|
||||
style="@android:style/Widget.ProgressBar.Horizontal"
|
||||
@@ -614,4 +133,30 @@
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
<!-- Download progress (used by download progress poll) -->
|
||||
<ProgressBar
|
||||
android:id="@+id/downloadProgressBar"
|
||||
style="@android:style/Widget.ProgressBar.Horizontal"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="4dp"
|
||||
android:layout_gravity="top"
|
||||
android:layout_marginTop="56dp"
|
||||
android:progressTint="#7c3aed"
|
||||
android:progressBackgroundTint="#334155"
|
||||
android:max="100"
|
||||
android:progress="0"
|
||||
android:indeterminate="false"
|
||||
android:visibility="gone" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/downloadProgressText"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_gravity="top|center_horizontal"
|
||||
android:layout_marginTop="64dp"
|
||||
android:text=""
|
||||
android:textColor="#94a3b8"
|
||||
android:textSize="12sp"
|
||||
android:visibility="gone" />
|
||||
|
||||
</FrameLayout>
|
||||
|
||||
@@ -0,0 +1,961 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:orientation="vertical"
|
||||
android:background="#0f172a">
|
||||
|
||||
<!-- ── Top bar: progress dots + exit button ── -->
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="56dp"
|
||||
android:orientation="horizontal"
|
||||
android:gravity="center_vertical"
|
||||
android:paddingStart="20dp"
|
||||
android:paddingEnd="8dp"
|
||||
android:background="#0f172a">
|
||||
|
||||
<LinearLayout
|
||||
android:id="@+id/setupProgressDots"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_weight="1"
|
||||
android:orientation="horizontal"
|
||||
android:gravity="start|center_vertical" />
|
||||
|
||||
<com.google.android.material.button.MaterialButton
|
||||
android:id="@+id/btnSetupExit"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="✕ Esci"
|
||||
android:textSize="13sp"
|
||||
android:textColor="#64748b"
|
||||
android:textAllCaps="false"
|
||||
style="@style/Widget.MaterialComponents.Button.TextButton" />
|
||||
</LinearLayout>
|
||||
|
||||
<!-- Thin divider -->
|
||||
<View
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="1dp"
|
||||
android:background="#1e293b" />
|
||||
|
||||
<!-- ── Scrollable step content ── -->
|
||||
<ScrollView
|
||||
android:id="@+id/setupScrollView"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="0dp"
|
||||
android:layout_weight="1"
|
||||
android:fillViewport="true">
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="vertical"
|
||||
android:gravity="center_horizontal"
|
||||
android:paddingStart="28dp"
|
||||
android:paddingEnd="28dp"
|
||||
android:paddingTop="32dp"
|
||||
android:paddingBottom="40dp">
|
||||
|
||||
<!-- ════════════════════════════════════════════
|
||||
STEP 0 — Welcome / Intro / Privacy
|
||||
════════════════════════════════════════════ -->
|
||||
<LinearLayout
|
||||
android:id="@+id/stepWelcome"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="vertical"
|
||||
android:gravity="center_horizontal"
|
||||
android:visibility="visible">
|
||||
|
||||
<!-- App logo -->
|
||||
<ImageView
|
||||
android:layout_width="96dp"
|
||||
android:layout_height="96dp"
|
||||
android:src="@drawable/ic_launcher_foreground"
|
||||
android:layout_marginBottom="16dp"
|
||||
android:contentDescription="EverShelf" />
|
||||
|
||||
<TextView
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="EverShelf Kiosk"
|
||||
android:textColor="#f1f5f9"
|
||||
android:textSize="30sp"
|
||||
android:textStyle="bold"
|
||||
android:gravity="center"
|
||||
android:layout_marginBottom="8dp" />
|
||||
|
||||
<TextView
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="La tua dispensa smart in cucina"
|
||||
android:textColor="#7c3aed"
|
||||
android:textSize="16sp"
|
||||
android:gravity="center"
|
||||
android:layout_marginBottom="28dp" />
|
||||
|
||||
<!-- What is EverShelf -->
|
||||
<TextView
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="Trasforma il tuo tablet in un pannello da cucina sempre attivo. Gestisci la dispensa, scansiona i prodotti, ricevi suggerimenti intelligenti — tutto dal tuo server di casa."
|
||||
android:textColor="#94a3b8"
|
||||
android:textSize="15sp"
|
||||
android:gravity="center"
|
||||
android:lineSpacingExtra="4dp"
|
||||
android:layout_marginBottom="28dp" />
|
||||
|
||||
<!-- Privacy card (highlighted) -->
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="vertical"
|
||||
android:background="@drawable/card_background"
|
||||
android:padding="20dp"
|
||||
android:layout_marginBottom="24dp">
|
||||
|
||||
<TextView
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="🔒 Privacy e dati"
|
||||
android:textColor="#f1f5f9"
|
||||
android:textSize="16sp"
|
||||
android:textStyle="bold"
|
||||
android:layout_marginBottom="12dp" />
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="horizontal"
|
||||
android:gravity="top"
|
||||
android:layout_marginBottom="10dp">
|
||||
|
||||
<TextView
|
||||
android:layout_width="22dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="🏠"
|
||||
android:textSize="14sp" />
|
||||
|
||||
<TextView
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_weight="1"
|
||||
android:text="Funziona completamente offline — i tuoi dati restano sul tuo server, dentro casa tua."
|
||||
android:textColor="#94a3b8"
|
||||
android:textSize="14sp"
|
||||
android:lineSpacingExtra="2dp" />
|
||||
</LinearLayout>
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="horizontal"
|
||||
android:gravity="top"
|
||||
android:layout_marginBottom="10dp">
|
||||
|
||||
<TextView
|
||||
android:layout_width="22dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="🤖"
|
||||
android:textSize="14sp" />
|
||||
|
||||
<TextView
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_weight="1"
|
||||
android:text="Solo l'assistente AI (opzionale) comunica con i server Google Gemini. Puoi usare EverShelf senza AI in modo totalmente offline."
|
||||
android:textColor="#94a3b8"
|
||||
android:textSize="14sp"
|
||||
android:lineSpacingExtra="2dp" />
|
||||
</LinearLayout>
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="horizontal"
|
||||
android:gravity="top">
|
||||
|
||||
<TextView
|
||||
android:layout_width="22dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="🚫"
|
||||
android:textSize="14sp" />
|
||||
|
||||
<TextView
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_weight="1"
|
||||
android:text="Nessun account richiesto. Nessun abbonamento. Nessun tracciamento."
|
||||
android:textColor="#94a3b8"
|
||||
android:textSize="14sp"
|
||||
android:lineSpacingExtra="2dp" />
|
||||
</LinearLayout>
|
||||
</LinearLayout>
|
||||
|
||||
<!-- Feature highlights -->
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="vertical"
|
||||
android:background="@drawable/tip_background"
|
||||
android:padding="16dp"
|
||||
android:layout_marginBottom="32dp">
|
||||
|
||||
<TextView
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="📺 Schermo sempre acceso, modalità kiosk"
|
||||
android:textColor="#cbd5e1"
|
||||
android:textSize="14sp"
|
||||
android:paddingTop="4dp"
|
||||
android:paddingBottom="4dp" />
|
||||
|
||||
<TextView
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="📷 Scansione barcode dal browser"
|
||||
android:textColor="#cbd5e1"
|
||||
android:textSize="14sp"
|
||||
android:paddingTop="4dp"
|
||||
android:paddingBottom="4dp" />
|
||||
|
||||
<TextView
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="⚖️ Bilancia Bluetooth via Gateway app"
|
||||
android:textColor="#cbd5e1"
|
||||
android:textSize="14sp"
|
||||
android:paddingTop="4dp"
|
||||
android:paddingBottom="4dp" />
|
||||
|
||||
<TextView
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="🔄 Aggiornamenti automatici"
|
||||
android:textColor="#cbd5e1"
|
||||
android:textSize="14sp"
|
||||
android:paddingTop="4dp"
|
||||
android:paddingBottom="4dp" />
|
||||
</LinearLayout>
|
||||
|
||||
<com.google.android.material.button.MaterialButton
|
||||
android:id="@+id/btnWelcomeStart"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="56dp"
|
||||
android:text="Inizia la configurazione →"
|
||||
android:textSize="16sp"
|
||||
android:textAllCaps="false"
|
||||
android:backgroundTint="#7c3aed" />
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
<!-- ════════════════════════════════════════════
|
||||
STEP 1 — Permissions rationale
|
||||
════════════════════════════════════════════ -->
|
||||
<LinearLayout
|
||||
android:id="@+id/stepPermissions"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="vertical"
|
||||
android:gravity="center_horizontal"
|
||||
android:visibility="gone">
|
||||
|
||||
<TextView
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="🔐"
|
||||
android:textSize="52sp"
|
||||
android:layout_marginBottom="12dp" />
|
||||
|
||||
<TextView
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="Permessi necessari"
|
||||
android:textColor="#f1f5f9"
|
||||
android:textSize="24sp"
|
||||
android:textStyle="bold"
|
||||
android:layout_marginBottom="6dp" />
|
||||
|
||||
<TextView
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="EverShelf Kiosk ha bisogno di questi permessi per funzionare. Ecco perché:"
|
||||
android:textColor="#94a3b8"
|
||||
android:textSize="15sp"
|
||||
android:gravity="center"
|
||||
android:lineSpacingExtra="4dp"
|
||||
android:layout_marginBottom="28dp" />
|
||||
|
||||
<!-- Camera permission card -->
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="horizontal"
|
||||
android:background="@drawable/card_background"
|
||||
android:padding="16dp"
|
||||
android:gravity="top"
|
||||
android:layout_marginBottom="12dp">
|
||||
|
||||
<TextView
|
||||
android:layout_width="44dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="📷"
|
||||
android:textSize="24sp"
|
||||
android:gravity="center" />
|
||||
|
||||
<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="Fotocamera"
|
||||
android:textColor="#f1f5f9"
|
||||
android:textSize="16sp"
|
||||
android:textStyle="bold"
|
||||
android:layout_marginBottom="4dp" />
|
||||
|
||||
<TextView
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="Per scansionare i codici a barre dei prodotti e fotografare ciò che aggiungi alla dispensa."
|
||||
android:textColor="#94a3b8"
|
||||
android:textSize="14sp"
|
||||
android:lineSpacingExtra="2dp" />
|
||||
</LinearLayout>
|
||||
</LinearLayout>
|
||||
|
||||
<!-- Microphone permission card -->
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="horizontal"
|
||||
android:background="@drawable/card_background"
|
||||
android:padding="16dp"
|
||||
android:gravity="top"
|
||||
android:layout_marginBottom="12dp">
|
||||
|
||||
<TextView
|
||||
android:layout_width="44dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="🎤"
|
||||
android:textSize="24sp"
|
||||
android:gravity="center" />
|
||||
|
||||
<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="Microfono"
|
||||
android:textColor="#f1f5f9"
|
||||
android:textSize="16sp"
|
||||
android:textStyle="bold"
|
||||
android:layout_marginBottom="4dp" />
|
||||
|
||||
<TextView
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="Per l'assistente vocale integrato: legge le scadenze e risponde ai comandi vocali."
|
||||
android:textColor="#94a3b8"
|
||||
android:textSize="14sp"
|
||||
android:lineSpacingExtra="2dp" />
|
||||
</LinearLayout>
|
||||
</LinearLayout>
|
||||
|
||||
<!-- Install packages permission card -->
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="horizontal"
|
||||
android:background="@drawable/card_background"
|
||||
android:padding="16dp"
|
||||
android:gravity="top"
|
||||
android:layout_marginBottom="28dp">
|
||||
|
||||
<TextView
|
||||
android:layout_width="44dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="📦"
|
||||
android:textSize="24sp"
|
||||
android:gravity="center" />
|
||||
|
||||
<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="Installa applicazioni"
|
||||
android:textColor="#f1f5f9"
|
||||
android:textSize="16sp"
|
||||
android:textStyle="bold"
|
||||
android:layout_marginBottom="4dp" />
|
||||
|
||||
<TextView
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="Per installare gli aggiornamenti automatici dell'app kiosk e dell'app Scale Gateway direttamente da GitHub."
|
||||
android:textColor="#94a3b8"
|
||||
android:textSize="14sp"
|
||||
android:lineSpacingExtra="2dp" />
|
||||
</LinearLayout>
|
||||
</LinearLayout>
|
||||
|
||||
<!-- Permissions granted confirmation (hidden until granted) -->
|
||||
<LinearLayout
|
||||
android:id="@+id/permsGrantedCard"
|
||||
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="20dp"
|
||||
android:visibility="gone">
|
||||
|
||||
<TextView
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="✅"
|
||||
android:textSize="20sp"
|
||||
android:layout_marginEnd="10dp" />
|
||||
|
||||
<TextView
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_weight="1"
|
||||
android:text="Permessi concessi! Puoi procedere."
|
||||
android:textColor="#34d399"
|
||||
android:textSize="14sp" />
|
||||
</LinearLayout>
|
||||
|
||||
<!-- Grant button -->
|
||||
<com.google.android.material.button.MaterialButton
|
||||
android:id="@+id/btnGrantPerms"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="52dp"
|
||||
android:text="🔓 Concedi i permessi"
|
||||
android:textSize="15sp"
|
||||
android:textAllCaps="false"
|
||||
android:backgroundTint="#7c3aed"
|
||||
android:layout_marginBottom="12dp" />
|
||||
|
||||
<!-- Navigation -->
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="horizontal">
|
||||
|
||||
<com.google.android.material.button.MaterialButton
|
||||
android:id="@+id/btnPermsBack"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="48dp"
|
||||
android:layout_weight="1"
|
||||
android:text="← Indietro"
|
||||
android:textSize="14sp"
|
||||
android:textAllCaps="false"
|
||||
style="@style/Widget.MaterialComponents.Button.OutlinedButton"
|
||||
android:strokeColor="#334155"
|
||||
android:textColor="#64748b"
|
||||
android:layout_marginEnd="10dp" />
|
||||
|
||||
<com.google.android.material.button.MaterialButton
|
||||
android:id="@+id/btnPermsNext"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="48dp"
|
||||
android:layout_weight="2"
|
||||
android:text="Avanti →"
|
||||
android:textSize="14sp"
|
||||
android:textAllCaps="false"
|
||||
style="@style/Widget.MaterialComponents.Button.OutlinedButton"
|
||||
android:strokeColor="#7c3aed"
|
||||
android:textColor="#a78bfa" />
|
||||
</LinearLayout>
|
||||
</LinearLayout>
|
||||
|
||||
<!-- ════════════════════════════════════════════
|
||||
STEP 2 — Server URL + auto-discovery
|
||||
════════════════════════════════════════════ -->
|
||||
<LinearLayout
|
||||
android:id="@+id/stepServer"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="vertical"
|
||||
android:gravity="center_horizontal"
|
||||
android:visibility="gone">
|
||||
|
||||
<TextView
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="🌐"
|
||||
android:textSize="52sp"
|
||||
android:layout_marginBottom="12dp" />
|
||||
|
||||
<TextView
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="Trova il server"
|
||||
android:textColor="#f1f5f9"
|
||||
android:textSize="24sp"
|
||||
android:textStyle="bold"
|
||||
android:layout_marginBottom="8dp" />
|
||||
|
||||
<TextView
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="EverShelf gira sul tuo server di casa (Raspberry Pi, NAS, PC...). Cerca automaticamente nella rete locale o inserisci l'indirizzo manualmente."
|
||||
android:textColor="#94a3b8"
|
||||
android:textSize="15sp"
|
||||
android:gravity="center"
|
||||
android:lineSpacingExtra="4dp"
|
||||
android:layout_marginBottom="28dp" />
|
||||
|
||||
<!-- Auto-discover button -->
|
||||
<com.google.android.material.button.MaterialButton
|
||||
android:id="@+id/btnDiscover"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="52dp"
|
||||
android:text="🔍 Cerca nella rete locale"
|
||||
android:textSize="15sp"
|
||||
android:textAllCaps="false"
|
||||
android:backgroundTint="#0f4c8c"
|
||||
android:layout_marginBottom="8dp" />
|
||||
|
||||
<!-- Discover status text -->
|
||||
<TextView
|
||||
android:id="@+id/discoverStatus"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:text=""
|
||||
android:textSize="13sp"
|
||||
android:visibility="gone"
|
||||
android:lineSpacingExtra="2dp"
|
||||
android:layout_marginBottom="16dp" />
|
||||
|
||||
<!-- Divider with OR -->
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="horizontal"
|
||||
android:gravity="center_vertical"
|
||||
android:layout_marginBottom="16dp">
|
||||
|
||||
<View
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="1dp"
|
||||
android:layout_weight="1"
|
||||
android:background="#334155" />
|
||||
|
||||
<TextView
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:text=" oppure inserisci manualmente "
|
||||
android:textColor="#475569"
|
||||
android:textSize="12sp" />
|
||||
|
||||
<View
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="1dp"
|
||||
android:layout_weight="1"
|
||||
android:background="#334155" />
|
||||
</LinearLayout>
|
||||
|
||||
<!-- URL input label -->
|
||||
<TextView
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="URL del server"
|
||||
android:textColor="#cbd5e1"
|
||||
android:textSize="13sp"
|
||||
android:textStyle="bold"
|
||||
android:layout_marginBottom="6dp" />
|
||||
|
||||
<EditText
|
||||
android:id="@+id/setupUrlEdit"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:hint="http://192.168.1.100/dispensa/"
|
||||
android:inputType="textUri"
|
||||
android:textColor="#f1f5f9"
|
||||
android:textColorHint="#475569"
|
||||
android:background="@drawable/input_background"
|
||||
android:padding="16dp"
|
||||
android:textSize="15sp"
|
||||
android:singleLine="true"
|
||||
android:layout_marginBottom="10dp" />
|
||||
|
||||
<!-- Test connection button -->
|
||||
<com.google.android.material.button.MaterialButton
|
||||
android:id="@+id/btnSetupTestUrl"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="48dp"
|
||||
android:text="🔗 Testa connessione"
|
||||
android:textSize="14sp"
|
||||
android:textAllCaps="false"
|
||||
style="@style/Widget.MaterialComponents.Button.OutlinedButton"
|
||||
android:strokeColor="#334155"
|
||||
android:textColor="#94a3b8"
|
||||
android:layout_marginBottom="8dp" />
|
||||
|
||||
<!-- URL status -->
|
||||
<TextView
|
||||
android:id="@+id/setupUrlStatus"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:text=""
|
||||
android:textSize="13sp"
|
||||
android:visibility="gone"
|
||||
android:lineSpacingExtra="2dp"
|
||||
android:layout_marginBottom="12dp" />
|
||||
|
||||
<!-- Tip -->
|
||||
<TextView
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="💡 Suggerimento: copia l'URL esatto dal browser (es. http://192.168.1.50/dispensa/ ) incluso il percorso finale."
|
||||
android:textColor="#64748b"
|
||||
android:textSize="12sp"
|
||||
android:background="@drawable/tip_background"
|
||||
android:padding="12dp"
|
||||
android:lineSpacingExtra="2dp"
|
||||
android:layout_marginBottom="28dp" />
|
||||
|
||||
<!-- Navigation -->
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="horizontal">
|
||||
|
||||
<com.google.android.material.button.MaterialButton
|
||||
android:id="@+id/btnServerBack"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="48dp"
|
||||
android:layout_weight="1"
|
||||
android:text="← Indietro"
|
||||
android:textSize="14sp"
|
||||
android:textAllCaps="false"
|
||||
style="@style/Widget.MaterialComponents.Button.OutlinedButton"
|
||||
android:strokeColor="#334155"
|
||||
android:textColor="#64748b"
|
||||
android:layout_marginEnd="10dp" />
|
||||
|
||||
<com.google.android.material.button.MaterialButton
|
||||
android:id="@+id/btnServerNext"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="48dp"
|
||||
android:layout_weight="2"
|
||||
android:text="Avanti →"
|
||||
android:textSize="15sp"
|
||||
android:textAllCaps="false"
|
||||
android:backgroundTint="#7c3aed" />
|
||||
</LinearLayout>
|
||||
</LinearLayout>
|
||||
|
||||
<!-- ════════════════════════════════════════════
|
||||
STEP 3 — Smart scale
|
||||
════════════════════════════════════════════ -->
|
||||
<LinearLayout
|
||||
android:id="@+id/stepScale"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="vertical"
|
||||
android:gravity="center_horizontal"
|
||||
android:visibility="gone">
|
||||
|
||||
<TextView
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="⚖️"
|
||||
android:textSize="52sp"
|
||||
android:layout_marginBottom="12dp" />
|
||||
|
||||
<TextView
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="Bilancia smart"
|
||||
android:textColor="#f1f5f9"
|
||||
android:textSize="24sp"
|
||||
android:textStyle="bold"
|
||||
android:layout_marginBottom="8dp" />
|
||||
|
||||
<TextView
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="EverShelf può integrarsi con le bilance da cucina Bluetooth per pesare automaticamente gli ingredienti."
|
||||
android:textColor="#94a3b8"
|
||||
android:textSize="15sp"
|
||||
android:gravity="center"
|
||||
android:lineSpacingExtra="4dp"
|
||||
android:layout_marginBottom="24dp" />
|
||||
|
||||
<!-- Question card -->
|
||||
<LinearLayout
|
||||
android:id="@+id/scaleQuestionCard"
|
||||
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="Hai una bilancia Bluetooth compatibile?"
|
||||
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="✓ Sì, ho una bilancia"
|
||||
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="→ No, salta questo passaggio"
|
||||
android:textSize="15sp"
|
||||
android:textAllCaps="false"
|
||||
style="@style/Widget.MaterialComponents.Button.OutlinedButton"
|
||||
android:strokeColor="#334155"
|
||||
android:textColor="#64748b" />
|
||||
</LinearLayout>
|
||||
|
||||
<!-- Gateway info card (shown after YES) -->
|
||||
<LinearLayout
|
||||
android:id="@+id/gatewayInfoCard"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="vertical"
|
||||
android:background="@drawable/tip_background"
|
||||
android:padding="16dp"
|
||||
android:layout_marginBottom="12dp"
|
||||
android:visibility="gone">
|
||||
|
||||
<TextView
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="📱 EverShelf Scale Gateway"
|
||||
android:textColor="#f1f5f9"
|
||||
android:textSize="15sp"
|
||||
android:textStyle="bold"
|
||||
android:layout_marginBottom="8dp" />
|
||||
|
||||
<TextView
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="Un'app separata che fa da ponte: legge i dati della bilancia Bluetooth e li trasmette al pannello kiosk via rete locale. Rimane in esecuzione in background e si avvia automaticamente insieme a EverShelf."
|
||||
android:textColor="#94a3b8"
|
||||
android:textSize="13sp"
|
||||
android:lineSpacingExtra="3dp" />
|
||||
</LinearLayout>
|
||||
|
||||
<!-- Gateway install status card (shown after YES) -->
|
||||
<LinearLayout
|
||||
android:id="@+id/gatewayInstallCard"
|
||||
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:id="@+id/gatewayStatusIcon"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="📲"
|
||||
android:textSize="32sp"
|
||||
android:layout_gravity="center"
|
||||
android:layout_marginBottom="8dp" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/gatewayStatusText"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="Scale Gateway non installato"
|
||||
android:textColor="#cbd5e1"
|
||||
android:textSize="16sp"
|
||||
android:gravity="center"
|
||||
android:layout_marginBottom="4dp" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/gatewayStatusDetail"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:text=""
|
||||
android:textColor="#94a3b8"
|
||||
android:textSize="13sp"
|
||||
android:gravity="center"
|
||||
android:layout_marginBottom="14dp" />
|
||||
|
||||
<ProgressBar
|
||||
android:id="@+id/gatewayProgressBar"
|
||||
style="@android:style/Widget.ProgressBar.Horizontal"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="8dp"
|
||||
android:layout_marginBottom="4dp"
|
||||
android:progressTint="#7c3aed"
|
||||
android:progressBackgroundTint="#334155"
|
||||
android:max="100"
|
||||
android:progress="0"
|
||||
android:indeterminate="false"
|
||||
android:visibility="gone" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/gatewayProgressText"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:text=""
|
||||
android:textColor="#94a3b8"
|
||||
android:textSize="12sp"
|
||||
android:gravity="center"
|
||||
android:layout_marginBottom="10dp"
|
||||
android:visibility="gone" />
|
||||
|
||||
<com.google.android.material.button.MaterialButton
|
||||
android:id="@+id/btnInstallGateway"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="48dp"
|
||||
android:text="📥 Installa Scale Gateway"
|
||||
android:textSize="14sp"
|
||||
android:textAllCaps="false"
|
||||
android:backgroundTint="#7c3aed" />
|
||||
</LinearLayout>
|
||||
|
||||
<!-- Step 3 navigation (shown after YES) -->
|
||||
<LinearLayout
|
||||
android:id="@+id/step3NextButtons"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="horizontal"
|
||||
android:visibility="gone">
|
||||
|
||||
<com.google.android.material.button.MaterialButton
|
||||
android:id="@+id/btnScaleBack"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="48dp"
|
||||
android:layout_weight="1"
|
||||
android:text="← Indietro"
|
||||
android:textSize="14sp"
|
||||
android:textAllCaps="false"
|
||||
style="@style/Widget.MaterialComponents.Button.OutlinedButton"
|
||||
android:strokeColor="#334155"
|
||||
android:textColor="#64748b"
|
||||
android:layout_marginEnd="10dp" />
|
||||
|
||||
<com.google.android.material.button.MaterialButton
|
||||
android:id="@+id/btnScaleNext"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="48dp"
|
||||
android:layout_weight="2"
|
||||
android:text="Avanti →"
|
||||
android:textSize="15sp"
|
||||
android:textAllCaps="false"
|
||||
android:backgroundTint="#7c3aed" />
|
||||
</LinearLayout>
|
||||
</LinearLayout>
|
||||
|
||||
<!-- ════════════════════════════════════════════
|
||||
STEP 4 — Done
|
||||
════════════════════════════════════════════ -->
|
||||
<LinearLayout
|
||||
android:id="@+id/stepDone"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="vertical"
|
||||
android:gravity="center_horizontal"
|
||||
android:visibility="gone">
|
||||
|
||||
<TextView
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="🎉"
|
||||
android:textSize="64sp"
|
||||
android:layout_marginBottom="16dp" />
|
||||
|
||||
<TextView
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="Tutto pronto!"
|
||||
android:textColor="#f1f5f9"
|
||||
android:textSize="28sp"
|
||||
android:textStyle="bold"
|
||||
android:layout_marginBottom="8dp" />
|
||||
|
||||
<TextView
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="La configurazione è completa. Premi il pulsante per avviare EverShelf in modalità kiosk."
|
||||
android:textColor="#94a3b8"
|
||||
android:textSize="15sp"
|
||||
android:gravity="center"
|
||||
android:lineSpacingExtra="4dp"
|
||||
android:layout_marginBottom="28dp" />
|
||||
|
||||
<!-- Configuration summary -->
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="vertical"
|
||||
android:background="@drawable/card_background"
|
||||
android:padding="20dp"
|
||||
android:layout_marginBottom="32dp">
|
||||
|
||||
<TextView
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="Riepilogo configurazione"
|
||||
android:textColor="#94a3b8"
|
||||
android:textSize="13sp"
|
||||
android:textAllCaps="true"
|
||||
android:letterSpacing="0.08"
|
||||
android:layout_marginBottom="12dp" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/setupSummaryText"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:text=""
|
||||
android:textColor="#cbd5e1"
|
||||
android:textSize="14sp"
|
||||
android:lineSpacingExtra="4dp" />
|
||||
</LinearLayout>
|
||||
|
||||
<com.google.android.material.button.MaterialButton
|
||||
android:id="@+id/btnLaunch"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="60dp"
|
||||
android:text="🚀 Avvia EverShelf"
|
||||
android:textSize="18sp"
|
||||
android:textAllCaps="false"
|
||||
android:backgroundTint="#059669" />
|
||||
</LinearLayout>
|
||||
|
||||
</LinearLayout>
|
||||
</ScrollView>
|
||||
|
||||
</LinearLayout>
|
||||
@@ -2,6 +2,21 @@
|
||||
<resources>
|
||||
<string name="app_name">EverShelf Kiosk</string>
|
||||
|
||||
<!-- Setup-Assistent Zeichenfolgen -->
|
||||
<string name="setup_enter_url">Bitte zuerst eine URL eingeben</string>
|
||||
<string name="setup_testing">Verbindung wird getestet…</string>
|
||||
<string name="setup_server_found">EverShelf-Server gefunden und API aktiv!</string>
|
||||
<string name="setup_api_not_found">Server erreichbar, aber EverShelf-API nicht gefunden. Pfad prüfen.</string>
|
||||
<string name="setup_unreachable">Server nicht erreichbar</string>
|
||||
<string name="setup_discover_btn">🔍 Lokales Netzwerk durchsuchen</string>
|
||||
<string name="setup_discovering">Suche läuft…</string>
|
||||
<string name="setup_discovering_detail">Suche nach EverShelf-Servern im lokalen Netzwerk (kann bis zu 30 s dauern)…</string>
|
||||
<string name="setup_discover_not_found">Kein EverShelf-Server automatisch gefunden. URL manuell eingeben.</string>
|
||||
<string name="setup_exit_title">Setup beenden?</string>
|
||||
<string name="setup_exit_message">Die Einrichtung kann später beim erneuten Öffnen der App abgeschlossen werden.</string>
|
||||
<string name="setup_exit_confirm">Beenden</string>
|
||||
<string name="setup_exit_cancel">Weiter</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>
|
||||
|
||||
@@ -2,6 +2,21 @@
|
||||
<resources>
|
||||
<string name="app_name">EverShelf Kiosk</string>
|
||||
|
||||
<!-- Stringhe setup wizard -->
|
||||
<string name="setup_enter_url">Inserisci prima un URL</string>
|
||||
<string name="setup_testing">Verifica connessione…</string>
|
||||
<string name="setup_server_found">Server EverShelf trovato e API attiva!</string>
|
||||
<string name="setup_api_not_found">Server raggiungibile ma API EverShelf non trovata. Verifica il percorso.</string>
|
||||
<string name="setup_unreachable">Impossibile raggiungere il server</string>
|
||||
<string name="setup_discover_btn">🔍 Cerca nella rete locale</string>
|
||||
<string name="setup_discovering">Scansione in corso…</string>
|
||||
<string name="setup_discovering_detail">Ricerca server EverShelf nella rete locale (potrebbe richiedere fino a 30 s)…</string>
|
||||
<string name="setup_discover_not_found">Nessun server EverShelf trovato automaticamente. Inserisci l\'URL manualmente.</string>
|
||||
<string name="setup_exit_title">Uscire dalla configurazione?</string>
|
||||
<string name="setup_exit_message">Puoi completare la configurazione più tardi riaprendo l\'app.</string>
|
||||
<string name="setup_exit_confirm">Esci</string>
|
||||
<string name="setup_exit_cancel">Continua</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>
|
||||
|
||||
@@ -1,6 +1,21 @@
|
||||
<resources>
|
||||
<string name="app_name">EverShelf Kiosk</string>
|
||||
|
||||
<!-- Setup wizard strings -->
|
||||
<string name="setup_enter_url">Please enter a URL first</string>
|
||||
<string name="setup_testing">Testing connection…</string>
|
||||
<string name="setup_server_found">EverShelf server found and API active!</string>
|
||||
<string name="setup_api_not_found">Server reachable but EverShelf API not found. Check the path.</string>
|
||||
<string name="setup_unreachable">Cannot reach server</string>
|
||||
<string name="setup_discover_btn">🔍 Search local network</string>
|
||||
<string name="setup_discovering">Scanning…</string>
|
||||
<string name="setup_discovering_detail">Scanning local network for EverShelf servers (this may take up to 30 s)…</string>
|
||||
<string name="setup_discover_not_found">No EverShelf server found automatically. Enter the URL manually.</string>
|
||||
<string name="setup_exit_title">Exit setup?</string>
|
||||
<string name="setup_exit_message">You can complete setup later when you reopen the app.</string>
|
||||
<string name="setup_exit_confirm">Exit</string>
|
||||
<string name="setup_exit_cancel">Continue</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>
|
||||
|
||||
Reference in New Issue
Block a user