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>
|
</intent-filter>
|
||||||
</activity>
|
</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
|
<activity
|
||||||
android:name=".SettingsActivity"
|
android:name=".SettingsActivity"
|
||||||
android:exported="false"
|
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:layout_height="match_parent"
|
||||||
android:background="#0f172a">
|
android:background="#0f172a">
|
||||||
|
|
||||||
<!-- Splash screen (shown briefly on launch) -->
|
<!-- Splash screen (shown briefly on launch if setup is already done) -->
|
||||||
<LinearLayout
|
<LinearLayout
|
||||||
android:id="@+id/splashContainer"
|
android:id="@+id/splashContainer"
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
@@ -32,7 +32,7 @@
|
|||||||
<TextView
|
<TextView
|
||||||
android:layout_width="wrap_content"
|
android:layout_width="wrap_content"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:text="Smart Pantry Manager"
|
android:text="La tua dispensa smart"
|
||||||
android:textColor="#64748b"
|
android:textColor="#64748b"
|
||||||
android:textSize="16sp"
|
android:textSize="16sp"
|
||||||
android:layout_marginBottom="48dp" />
|
android:layout_marginBottom="48dp" />
|
||||||
@@ -43,486 +43,6 @@
|
|||||||
android:indeterminateTint="#7c3aed" />
|
android:indeterminateTint="#7c3aed" />
|
||||||
</LinearLayout>
|
</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 (shown after setup) -->
|
||||||
<WebView
|
<WebView
|
||||||
android:id="@+id/webView"
|
android:id="@+id/webView"
|
||||||
@@ -545,7 +65,7 @@
|
|||||||
android:scaleType="centerInside"
|
android:scaleType="centerInside"
|
||||||
android:visibility="gone" />
|
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
|
<LinearLayout
|
||||||
android:id="@+id/updateBanner"
|
android:id="@+id/updateBanner"
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
@@ -573,8 +93,7 @@
|
|||||||
android:layout_weight="1"
|
android:layout_weight="1"
|
||||||
android:textColor="#fbbf24"
|
android:textColor="#fbbf24"
|
||||||
android:textSize="13sp"
|
android:textSize="13sp"
|
||||||
android:text=""
|
android:text="" />
|
||||||
android:drawablePadding="6dp" />
|
|
||||||
|
|
||||||
<com.google.android.material.button.MaterialButton
|
<com.google.android.material.button.MaterialButton
|
||||||
android:id="@+id/btnInstallUpdate"
|
android:id="@+id/btnInstallUpdate"
|
||||||
@@ -599,7 +118,7 @@
|
|||||||
style="@style/Widget.MaterialComponents.Button.TextButton" />
|
style="@style/Widget.MaterialComponents.Button.TextButton" />
|
||||||
</LinearLayout>
|
</LinearLayout>
|
||||||
|
|
||||||
<!-- Thin progress bar at the bottom of the banner — visible during download/install -->
|
<!-- Thin progress bar at the bottom of the banner -->
|
||||||
<ProgressBar
|
<ProgressBar
|
||||||
android:id="@+id/bannerProgressBar"
|
android:id="@+id/bannerProgressBar"
|
||||||
style="@android:style/Widget.ProgressBar.Horizontal"
|
style="@android:style/Widget.ProgressBar.Horizontal"
|
||||||
@@ -614,4 +133,30 @@
|
|||||||
|
|
||||||
</LinearLayout>
|
</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>
|
</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>
|
<resources>
|
||||||
<string name="app_name">EverShelf Kiosk</string>
|
<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 -->
|
<!-- Wizard Schritt 3: Smart-Waage -->
|
||||||
<string name="wizard_step3_title">Smart-Waage (Optional)</string>
|
<string name="wizard_step3_title">Smart-Waage (Optional)</string>
|
||||||
<string name="wizard_step3_description">Um eine Bluetooth-Küchenwaage zu verwenden, musst du die EverShelf Scale Gateway App separat installieren.</string>
|
<string name="wizard_step3_description">Um eine Bluetooth-Küchenwaage zu verwenden, musst du die EverShelf Scale Gateway App separat installieren.</string>
|
||||||
|
|||||||
@@ -2,6 +2,21 @@
|
|||||||
<resources>
|
<resources>
|
||||||
<string name="app_name">EverShelf Kiosk</string>
|
<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 -->
|
<!-- Wizard Step 3: Bilancia smart -->
|
||||||
<string name="wizard_step3_title">Bilancia Smart (Opzionale)</string>
|
<string name="wizard_step3_title">Bilancia Smart (Opzionale)</string>
|
||||||
<string name="wizard_step3_description">Per usare una bilancia da cucina Bluetooth, devi installare l\'app EverShelf Scale Gateway separatamente.</string>
|
<string name="wizard_step3_description">Per usare una bilancia da cucina Bluetooth, devi installare l\'app EverShelf Scale Gateway separatamente.</string>
|
||||||
|
|||||||
@@ -1,6 +1,21 @@
|
|||||||
<resources>
|
<resources>
|
||||||
<string name="app_name">EverShelf Kiosk</string>
|
<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 -->
|
<!-- Wizard Step 3: Smart scale -->
|
||||||
<string name="wizard_step3_title">Smart Scale (Optional)</string>
|
<string name="wizard_step3_title">Smart Scale (Optional)</string>
|
||||||
<string name="wizard_step3_description">To use a Bluetooth kitchen scale, you need the EverShelf Scale Gateway app installed separately.</string>
|
<string name="wizard_step3_description">To use a Bluetooth kitchen scale, you need the EverShelf Scale Gateway app installed separately.</string>
|
||||||
|
|||||||
Reference in New Issue
Block a user