added session time stamp and online/offline state

This commit is contained in:
oxidiert
2025-09-26 12:28:11 +02:00
parent bf33501b69
commit cfcb689ffc
7 changed files with 255 additions and 182 deletions

View File

@ -2,6 +2,8 @@ package com.dano.test1
import android.content.res.ColorStateList
import android.graphics.Color
import android.os.Handler
import android.os.Looper
import android.util.TypedValue
import android.view.Gravity
import android.view.View
@ -10,6 +12,7 @@ import com.google.android.material.button.MaterialButton
import org.json.JSONArray
import org.json.JSONObject
import java.io.File
import java.util.concurrent.TimeUnit
var RHS_POINTS: Int? = null
@ -27,6 +30,10 @@ class HandlerOpeningScreen(private val activity: MainActivity) {
private lateinit var downloadButton: Button
private lateinit var databaseButton: Button
// NEU: Status-Leiste
private lateinit var statusSession: TextView
private lateinit var statusOnline: TextView
private val dynamicButtons = mutableListOf<Button>()
private val questionnaireFiles = mutableMapOf<Button, String>()
@ -42,6 +49,15 @@ class HandlerOpeningScreen(private val activity: MainActivity) {
private var uiFreeze: Boolean = false
// Status-Updater (jede Minute)
private val uiHandler = Handler(Looper.getMainLooper())
private val statusTicker = object : Runnable {
override fun run() {
updateStatusStrip()
uiHandler.postDelayed(this, 60_000) // jede Minute
}
}
// Feste Standard-Randfarben
private val STROKE_ENABLED = Color.parseColor("#8C79F2") // wenn anklickbar
private val STROKE_DISABLED = Color.parseColor("#D8D3F5") // wenn nicht anklickbar
@ -62,6 +78,11 @@ class HandlerOpeningScreen(private val activity: MainActivity) {
setupDownloadButton()
setupDatabaseButtonHandler()
// Statusleiste initial & Ticker starten
uiHandler.removeCallbacks(statusTicker)
updateStatusStrip()
uiHandler.post(statusTicker)
val pathExists = File("/data/data/com.dano.test1/databases/questionnaire_database").exists()
updateMainButtonsState(pathExists)
if (pathExists && !editText.text.isNullOrBlank()) buttonLoad.performClick()
@ -79,6 +100,10 @@ class HandlerOpeningScreen(private val activity: MainActivity) {
downloadButton = activity.findViewById(R.id.downloadButton)
databaseButton = activity.findViewById(R.id.databaseButton)
// NEU:
statusSession = activity.findViewById(R.id.statusSession)
statusOnline = activity.findViewById(R.id.statusOnline)
val tag = editText.tag as? String ?: ""
editText.hint = t(tag)
textView.text = t("example_text")
@ -278,6 +303,9 @@ class HandlerOpeningScreen(private val activity: MainActivity) {
private fun applyUpdateButtonTexts(force: Boolean) {
if (uiFreeze && !force) return
// Statuszeile bei jedem Refresh aktualisieren
updateStatusStrip()
questionnaireFiles.forEach { (button, fileName) ->
val entry = questionnaireEntries.firstOrNull { it.file == fileName }
val key = fileName.substringAfter("questionnaire_").substringAfter("_").removeSuffix(".json")
@ -291,7 +319,6 @@ class HandlerOpeningScreen(private val activity: MainActivity) {
val enabled = button.isEnabled
val locked = !enabled && !completed
// Rahmenbreite & -farbe je nach Klickbarkeit
setClickableStroke(button, enabled)
if (locked) {
@ -302,7 +329,6 @@ class HandlerOpeningScreen(private val activity: MainActivity) {
setLockedAppearance(button, false)
parts.title.setTextColor(Color.parseColor("#2F2A49"))
parts.subtitle.setTextColor(Color.parseColor("#7B7794"))
// Tönung nur wenn showPoints == true
applyTintForButton(button, points, emphasize = enabled)
}
@ -357,7 +383,6 @@ class HandlerOpeningScreen(private val activity: MainActivity) {
button.isEnabled = shouldEnable
button.alpha = if (completed || shouldEnable) 1.0f else 0.6f
// Rahmenbreite & -farbe je nach Klickbarkeit
setClickableStroke(button, shouldEnable)
cardParts[button]?.let { parts ->
@ -369,7 +394,6 @@ class HandlerOpeningScreen(private val activity: MainActivity) {
setLockedAppearance(button, false)
parts.title.setTextColor(Color.parseColor("#2F2A49"))
parts.subtitle.setTextColor(Color.parseColor("#7B7794"))
// Tönung nur wenn showPoints == true
applyTintForButton(button, getPointsForButton(button), emphasize = shouldEnable)
}
@ -394,11 +418,6 @@ class HandlerOpeningScreen(private val activity: MainActivity) {
}
}
// öffentliche Wrapper
private fun updateButtonTexts() = applyUpdateButtonTexts(force = false)
private fun setButtonsEnabled(enabledButtons: List<Button>, allowCompleted: Boolean = false) =
applySetButtonsEnabled(enabledButtons, allowCompleted, force = false)
private fun startQuestionnaire(questionnaire: QuestionnaireBase<*>) {
activity.startQuestionnaire(questionnaire, languageID)
}
@ -434,88 +453,23 @@ class HandlerOpeningScreen(private val activity: MainActivity) {
private fun setupUploadButton() {
uploadButton.text = t("upload")
uploadButton.setOnClickListener {
// Token aus dem Login (MainActivity speichert es in TokenStore)
val token = TokenStore.getToken(activity)
if (token.isNullOrBlank()) {
Toast.makeText(
activity,
t("login_required") ?: "Bitte zuerst einloggen",
Toast.LENGTH_LONG
).show()
Toast.makeText(activity, t("login_required") ?: "Bitte zuerst einloggen", Toast.LENGTH_LONG).show()
return@setOnClickListener
}
// wie gewohnt: Client-Code merken, dann direkt hochladen
GlobalValues.LAST_CLIENT_CODE = editText.text.toString().trim()
DatabaseUploader.uploadDatabaseWithToken(activity, token)
// Hinweis: Nach erfolgreichem Upload schließt sich die App (exitProcess(0)) wie bisher.
}
}
private fun setupDownloadButton() {
downloadButton.text = t("download")
downloadButton.setOnClickListener {
GlobalValues.LAST_CLIENT_CODE = editText.text.toString().trim()
promptCredentials(
title = t("login_required") ?: "Login erforderlich",
onOk = { user, pass ->
// Login -> Token -> Download
LoginManager.loginUserWithCredentials(
context = activity,
username = user,
password = pass,
onSuccess = { token ->
Toast.makeText(activity, t("login_ok") ?: "Login OK", Toast.LENGTH_SHORT).show()
DatabaseDownloader.downloadAndReplaceDatabase(activity, token)
updateMainButtonsState(true)
},
onError = { error ->
Toast.makeText(activity, error, Toast.LENGTH_LONG).show()
}
)
}
)
Toast.makeText(activity, t("login_required") ?: "Bitte zuerst einloggen", Toast.LENGTH_SHORT).show()
}
}
private fun promptCredentials(
title: String,
onOk: (username: String, password: String) -> Unit
) {
val wrapper = LinearLayout(activity).apply {
orientation = LinearLayout.VERTICAL
setPadding(dp(20), dp(8), dp(20), 0)
}
val etUser = EditText(activity).apply {
hint = "Username"
setSingleLine()
}
val etPass = EditText(activity).apply {
hint = "Passwort"
setSingleLine()
inputType = android.text.InputType.TYPE_CLASS_TEXT or
android.text.InputType.TYPE_TEXT_VARIATION_PASSWORD
}
wrapper.addView(etUser)
wrapper.addView(etPass)
android.app.AlertDialog.Builder(activity)
.setTitle(title)
.setView(wrapper)
.setPositiveButton("OK") { _, _ ->
val u = etUser.text.toString().trim()
val p = etPass.text.toString()
if (u.isNotEmpty() && p.isNotEmpty()) {
onOk(u, p)
} else {
Toast.makeText(activity, t("enter_password") ?: "Bitte Username & Passwort eingeben", Toast.LENGTH_SHORT).show()
}
}
.setNegativeButton(t("cancel") ?: "Abbrechen", null)
.show()
}
private fun setupDatabaseButtonHandler() {
DatabaseButtonHandler(
activity = activity,
@ -532,8 +486,7 @@ class HandlerOpeningScreen(private val activity: MainActivity) {
}
}
private fun dp(v: Int): Int =
(v * activity.resources.displayMetrics.density).toInt()
private fun dp(v: Int): Int = (v * activity.resources.displayMetrics.density).toInt()
private fun isCompleted(button: Button): Boolean {
val fileName = questionnaireFiles[button] ?: return false
@ -549,7 +502,6 @@ class HandlerOpeningScreen(private val activity: MainActivity) {
}?.value
}
/** Locked-Optik: schwarzer Hintergrund, fester grauer Rand */
private fun setLockedAppearance(button: Button, locked: Boolean) {
val mb = button as? MaterialButton ?: return
if (locked) {
@ -561,14 +513,12 @@ class HandlerOpeningScreen(private val activity: MainActivity) {
}
}
/** Standardfarben wiederherstellen (wenn showPoints == false) hier nicht mehr für locked zuständig */
private fun resetTint(button: Button) {
val mb = button as? MaterialButton ?: return
mb.backgroundTintList = ColorStateList.valueOf(Color.WHITE)
mb.strokeColor = ColorStateList.valueOf(STROKE_DISABLED)
}
/** Tönung nur anwenden, wenn showPoints == true (und Button nicht locked) */
private fun applyTintForButton(button: Button, points: Int?, emphasize: Boolean) {
val file = questionnaireFiles[button] ?: return
val entry = questionnaireEntries.firstOrNull { it.file == file }
@ -579,21 +529,17 @@ class HandlerOpeningScreen(private val activity: MainActivity) {
mb.strokeColor = ColorStateList.valueOf(if (emphasize) STROKE_ENABLED else STROKE_DISABLED)
return
}
setScoreTint(button, points, emphasize)
}
/** Dickere Kontur, wenn anklickbar; Farbe bleibt fix */
private fun setClickableStroke(button: Button, enabled: Boolean) {
val mb = button as? MaterialButton ?: return
mb.strokeWidth = if (enabled) dp(2) else dp(1)
mb.strokeColor = ColorStateList.valueOf(if (enabled) STROKE_ENABLED else STROKE_DISABLED)
}
/** Hintergrund nach Score; Rand bleibt fix */
private fun setScoreTint(button: Button, points: Int?, emphasize: Boolean) {
val mb = button as? MaterialButton ?: return
val bg = when {
points == null && emphasize -> Color.parseColor("#F1EEFF")
points == null -> Color.parseColor("#FFFFFF")
@ -604,8 +550,27 @@ class HandlerOpeningScreen(private val activity: MainActivity) {
points >= 37 && emphasize -> Color.parseColor("#FFCDD2")
else -> Color.parseColor("#FFEBEE")
}
mb.backgroundTintList = ColorStateList.valueOf(bg)
mb.strokeColor = ColorStateList.valueOf(if (emphasize) STROKE_ENABLED else STROKE_DISABLED)
}
// === NEU: Status-Logik ===
private fun updateStatusStrip() {
// Session-Alter
val ts = TokenStore.getLoginTimestamp(activity)
val ageMs = if (ts > 0L) (System.currentTimeMillis() - ts) else 0L
val h = TimeUnit.MILLISECONDS.toHours(ageMs)
val m = TimeUnit.MILLISECONDS.toMinutes(ageMs) - h * 60
statusSession.text = if (ts > 0L) "Session: ${h}h ${m}m" else "Session: —"
// Online/Offline
val online = NetworkUtils.isOnline(activity)
statusOnline.text = if (online) "Online" else "Offline"
statusOnline.setTextColor(if (online) Color.parseColor("#2E7D32") else Color.parseColor("#C62828"))
}
fun refreshHeaderStatusLive() {
// nutzt deine bestehende Update-Logik und erzwingt ein Neuzeichnen
applyUpdateButtonTexts(force = true)
}
}