fixed wrong loading bug, fixed editing bug

This commit is contained in:
oxidiert
2025-09-08 10:27:23 +02:00
parent 45deee664b
commit 650a3bb050
4 changed files with 93 additions and 77 deletions

View File

@ -14,7 +14,9 @@ class EditButtonHandler(
private val questionnaireFiles: Map<Button, String>, private val questionnaireFiles: Map<Button, String>,
private val buttonPoints: MutableMap<String, Int>, private val buttonPoints: MutableMap<String, Int>,
private val updateButtonTexts: () -> Unit, private val updateButtonTexts: () -> Unit,
private val setButtonsEnabled: (List<Button>) -> Unit private val setButtonsEnabled: (List<Button>) -> Unit,
// vor "Bearbeiten" ggf. Laden anstoßen
private val triggerLoad: () -> Unit
) { ) {
fun setup() { fun setup() {
@ -23,18 +25,38 @@ class EditButtonHandler(
} }
private fun handleEditButtonClick() { private fun handleEditButtonClick() {
val clientCode = editText.text.toString().trim() val typed = editText.text.toString().trim()
if (clientCode.isBlank()) { val desiredCode = when {
typed.isNotBlank() -> typed
!GlobalValues.LOADED_CLIENT_CODE.isNullOrBlank() -> GlobalValues.LOADED_CLIENT_CODE!!
else -> ""
}
if (desiredCode.isBlank()) {
val message = LanguageManager.getText(languageIDProvider(), "please_client_code") val message = LanguageManager.getText(languageIDProvider(), "please_client_code")
Toast.makeText(activity, message, Toast.LENGTH_SHORT).show() Toast.makeText(activity, message, Toast.LENGTH_SHORT).show()
return return
} }
GlobalValues.LAST_CLIENT_CODE = clientCode // Nutzerwunsch merken (info)
GlobalValues.LAST_CLIENT_CODE = desiredCode
// Nur laden, wenn noch nicht/anders geladen
val needLoad = GlobalValues.LOADED_CLIENT_CODE?.equals(desiredCode) != true
if (needLoad) triggerLoad()
CoroutineScope(Dispatchers.IO).launch { CoroutineScope(Dispatchers.IO).launch {
val loadedOk = waitUntilClientLoaded(desiredCode, timeoutMs = 2500, stepMs = 50)
if (!loadedOk) {
withContext(Dispatchers.Main) {
Toast.makeText(activity, "Bitte den Klienten über \"Laden\" öffnen.", Toast.LENGTH_LONG).show()
}
return@launch
}
// Ab hier: geladen → Bearbeiten-Logik
val completedEntries: List<CompletedQuestionnaire> = val completedEntries: List<CompletedQuestionnaire> =
MyApp.database.completedQuestionnaireDao().getAllForClient(clientCode) MyApp.database.completedQuestionnaireDao().getAllForClient(desiredCode)
val completedFiles = completedEntries val completedFiles = completedEntries
.filter { it.isDone } .filter { it.isDone }
@ -58,4 +80,17 @@ class EditButtonHandler(
} }
} }
} }
private suspend fun waitUntilClientLoaded(expectedCode: String, timeoutMs: Long, stepMs: Long): Boolean {
// sofort ok, wenn bereits korrekt geladen
if (GlobalValues.LOADED_CLIENT_CODE?.equals(expectedCode) == true) return true
var waited = 0L
while (waited < timeoutMs) {
delay(stepMs)
waited += stepMs
if (GlobalValues.LOADED_CLIENT_CODE?.equals(expectedCode) == true) return true
}
return GlobalValues.LOADED_CLIENT_CODE?.equals(expectedCode) == true
}
} }

View File

@ -26,50 +26,39 @@ class HandlerClientCoachCode(
this.layout = layout this.layout = layout
this.question = question this.question = question
// Bind UI components
val clientCodeField = layout.findViewById<EditText>(R.id.client_code) val clientCodeField = layout.findViewById<EditText>(R.id.client_code)
val coachCodeField = layout.findViewById<EditText>(R.id.coach_code) val coachCodeField = layout.findViewById<EditText>(R.id.coach_code)
val questionTextView = layout.findViewById<TextView>(R.id.question) val questionTextView = layout.findViewById<TextView>(R.id.question)
val titleTextView = layout.findViewById<TextView>(R.id.textView) val titleTextView = layout.findViewById<TextView>(R.id.textView)
// Fill question text using language manager questionTextView.text = question.question?.let { LanguageManager.getText(languageID, it) } ?: ""
questionTextView.text = question.question?.let {
LanguageManager.getText(languageID, it)
} ?: ""
// --- Schriftgrößen prozentual zur Bildschirmhöhe setzen --- setTextSizePercentOfScreenHeight(titleTextView, 0.03f)
// Passe die Prozente bei Bedarf an: setTextSizePercentOfScreenHeight(questionTextView,0.03f)
setTextSizePercentOfScreenHeight(titleTextView, 0.03f) // 5.5% der Bildschirmhöhe setTextSizePercentOfScreenHeight(clientCodeField, 0.025f)
setTextSizePercentOfScreenHeight(questionTextView,0.03f) // 5.0% der Bildschirmhöhe setTextSizePercentOfScreenHeight(coachCodeField, 0.025f)
setTextSizePercentOfScreenHeight(clientCodeField, 0.025f) // 3.5% der Bildschirmhöhe
setTextSizePercentOfScreenHeight(coachCodeField, 0.025f) // anpassen nach Geschmack
// ----------------------------------------------------------
// Load last used client code if available // *** WICHTIG: Nur den ERFOLGREICH GELADENEN Code verwenden ***
val lastClientCode = GlobalValues.LAST_CLIENT_CODE val loadedClientCode = GlobalValues.LOADED_CLIENT_CODE
if (!lastClientCode.isNullOrBlank()) { if (!loadedClientCode.isNullOrBlank()) {
clientCodeField.setText(lastClientCode) clientCodeField.setText(loadedClientCode)
clientCodeField.isEnabled = false clientCodeField.isEnabled = false
} else { } else {
clientCodeField.setText(answers["client_code"] as? String ?: "") // Nichts ist geladen → Feld bleibt leer und editierbar
clientCodeField.setText("")
clientCodeField.isEnabled = true clientCodeField.isEnabled = true
} }
// Load saved coach code
coachCodeField.setText(answers["coach_code"] as? String ?: "") coachCodeField.setText(answers["coach_code"] as? String ?: "")
// Set click listener for Next button
layout.findViewById<Button>(R.id.Qnext).setOnClickListener { layout.findViewById<Button>(R.id.Qnext).setOnClickListener {
onNextClicked(clientCodeField, coachCodeField) onNextClicked(clientCodeField, coachCodeField)
} }
// Set click listener for Previous button
layout.findViewById<Button>(R.id.Qprev).setOnClickListener { layout.findViewById<Button>(R.id.Qprev).setOnClickListener {
onPreviousClicked(clientCodeField, coachCodeField) onPreviousClicked(clientCodeField, coachCodeField)
} }
} }
// Deaktiviert AutoSize und setzt textSize in sp prozentual zur Bildschirmhöhe
private fun setTextSizePercentOfScreenHeight(view: TextView, percentOfHeight: Float) { private fun setTextSizePercentOfScreenHeight(view: TextView, percentOfHeight: Float) {
val dm = layout.resources.displayMetrics val dm = layout.resources.displayMetrics
val sp = (dm.heightPixels * percentOfHeight) / dm.scaledDensity val sp = (dm.heightPixels * percentOfHeight) / dm.scaledDensity
@ -77,8 +66,10 @@ class HandlerClientCoachCode(
view.setTextSize(TypedValue.COMPLEX_UNIT_SP, sp) view.setTextSize(TypedValue.COMPLEX_UNIT_SP, sp)
} }
// Handle Next button click
private fun onNextClicked(clientCodeField: EditText, coachCodeField: EditText) { private fun onNextClicked(clientCodeField: EditText, coachCodeField: EditText) {
// 1) Ohne vorher geladenen Client NICHT weiter
val loadedClientCode = GlobalValues.LOADED_CLIENT_CODE
if (!validate()) { if (!validate()) {
val message = LanguageManager.getText(languageID, "fill_both_fields") val message = LanguageManager.getText(languageID, "fill_both_fields")
showToast(message) showToast(message)
@ -92,21 +83,18 @@ class HandlerClientCoachCode(
val dbPath = layout.context.getDatabasePath("questionnaire_database") val dbPath = layout.context.getDatabasePath("questionnaire_database")
val dbExistedBefore = dbPath.exists() val dbExistedBefore = dbPath.exists()
// Check if client code already exists asynchronously
CoroutineScope(Dispatchers.IO).launch { CoroutineScope(Dispatchers.IO).launch {
val existingClient = MyApp.database.clientDao().getClientByCode(clientCode) val existingClient = MyApp.database.clientDao().getClientByCode(clientCode)
withContext(Dispatchers.Main) { withContext(Dispatchers.Main) {
// Wenn Feld editierbar war und Code bereits existiert → Hinweis
if (existingClient != null && clientCodeField.isEnabled) { if (existingClient != null && clientCodeField.isEnabled) {
// Client code already exists and field was editable
val message = LanguageManager.getText(languageID, "client_code_exists") val message = LanguageManager.getText(languageID, "client_code_exists")
showToast(message) showToast(message)
} else { } else {
// Either no existing client or re-using previous code
saveAnswers(clientCode, coachCode) saveAnswers(clientCode, coachCode)
goToNextQuestion() goToNextQuestion()
// Lösche DB-Dateien nur, wenn sie vorher nicht existierten
if (!dbExistedBefore) { if (!dbExistedBefore) {
MyApp.database.close() MyApp.database.close()
dbPath.delete() dbPath.delete()
@ -118,7 +106,6 @@ class HandlerClientCoachCode(
} }
} }
// Handle Previous button click
private fun onPreviousClicked(clientCodeField: EditText, coachCodeField: EditText) { private fun onPreviousClicked(clientCodeField: EditText, coachCodeField: EditText) {
val clientCode = clientCodeField.text.toString() val clientCode = clientCodeField.text.toString()
val coachCode = coachCodeField.text.toString() val coachCode = coachCodeField.text.toString()
@ -126,21 +113,19 @@ class HandlerClientCoachCode(
goToPreviousQuestion() goToPreviousQuestion()
} }
// Validate that both fields are filled
override fun validate(): Boolean { override fun validate(): Boolean {
val clientCode = layout.findViewById<EditText>(R.id.client_code).text val clientCode = layout.findViewById<EditText>(R.id.client_code).text
val coachCode = layout.findViewById<EditText>(R.id.coach_code).text val coachCode = layout.findViewById<EditText>(R.id.coach_code).text
return clientCode.isNotBlank() && coachCode.isNotBlank() return clientCode.isNotBlank() && coachCode.isNotBlank()
} }
// Save answers to shared state and global value
private fun saveAnswers(clientCode: String, coachCode: String) { private fun saveAnswers(clientCode: String, coachCode: String) {
// Optional: LAST_CLIENT_CODE kann gesetzt bleiben; maßgeblich ist LOADED_CLIENT_CODE
GlobalValues.LAST_CLIENT_CODE = clientCode GlobalValues.LAST_CLIENT_CODE = clientCode
answers["client_code"] = clientCode answers["client_code"] = clientCode
answers["coach_code"] = coachCode answers["coach_code"] = coachCode
} }
// Required override but not used here
override fun saveAnswer() { override fun saveAnswer() {
// Not used // Not used
} }

View File

@ -48,7 +48,7 @@ class HandlerOpeningScreen(private val activity: MainActivity) {
val pathExists = File(dbPath).exists() val pathExists = File(dbPath).exists()
updateMainButtonsState(pathExists) updateMainButtonsState(pathExists)
if (!editText.text.isNullOrBlank()) { if (pathExists && !editText.text.isNullOrBlank()) {
buttonLoad.performClick() buttonLoad.performClick()
} }
} }
@ -154,21 +154,26 @@ class HandlerOpeningScreen(private val activity: MainActivity) {
questionnaireFiles[button] = entry.file questionnaireFiles[button] = entry.file
} }
updateButtonTexts() updateButtonTexts()
val alwaysButtons = questionnaireEntries.mapIndexedNotNull { idx, entry -> val alwaysButtons = questionnaireEntries.mapIndexedNotNull { idx, entry ->
val btn = dynamicButtons.getOrNull(idx) val btn = dynamicButtons.getOrNull(idx)
if (entry.condition is QuestionItem.Condition.AlwaysAvailable) btn else null if (entry.condition is QuestionItem.Condition.AlwaysAvailable) btn else null
} }
setButtonsEnabled(alwaysButtons) setButtonsEnabled(alwaysButtons)
dynamicButtons.forEach { button -> dynamicButtons.forEach { button ->
button.setOnClickListener { button.setOnClickListener {
val clientCode = editText.text.toString().trim()
GlobalValues.LAST_CLIENT_CODE = clientCode // Optional: LAST_CLIENT_CODE synchronisieren (rein informativ)
GlobalValues.LAST_CLIENT_CODE = GlobalValues.LOADED_CLIENT_CODE
startQuestionnaireForButton(button) startQuestionnaireForButton(button)
setButtonsEnabled(dynamicButtons.filter { it == button }) setButtonsEnabled(dynamicButtons.filter { it == button })
} }
} }
} }
private fun restorePreviousClientCode() { private fun restorePreviousClientCode() {
GlobalValues.LAST_CLIENT_CODE?.let { editText.setText(it) } GlobalValues.LAST_CLIENT_CODE?.let { editText.setText(it) }
} }
@ -276,10 +281,13 @@ class HandlerOpeningScreen(private val activity: MainActivity) {
questionnaireFiles = questionnaireFiles, questionnaireFiles = questionnaireFiles,
buttonPoints = buttonPoints, buttonPoints = buttonPoints,
updateButtonTexts = { updateButtonTexts() }, updateButtonTexts = { updateButtonTexts() },
setButtonsEnabled = { setButtonsEnabled(it) } setButtonsEnabled = { setButtonsEnabled(it) },
// Vor "Bearbeiten" ggf. den Load-Button ausführen
triggerLoad = { buttonLoad.performClick() }
).setup() ).setup()
} }
private fun setupUploadButton() { private fun setupUploadButton() {
uploadButton.text = "Upload" uploadButton.text = "Upload"
uploadButton.setOnClickListener { uploadButton.setOnClickListener {

View File

@ -42,10 +42,11 @@ class LoadButtonHandler(
CoroutineScope(Dispatchers.IO).launch { CoroutineScope(Dispatchers.IO).launch {
val client = MyApp.database.clientDao().getClientByCode(clientCode) val client = MyApp.database.clientDao().getClientByCode(clientCode)
if (client == null) { if (client == null) {
// Kein Profil → als NICHT geladen markieren
GlobalValues.LOADED_CLIENT_CODE = null
withContext(Dispatchers.Main) { withContext(Dispatchers.Main) {
val message = LanguageManager.getText(languageIDProvider(), "no_profile") val message = LanguageManager.getText(languageIDProvider(), "no_profile")
Toast.makeText(activity, message, Toast.LENGTH_LONG).show() Toast.makeText(activity, message, Toast.LENGTH_LONG).show()
// enable only alwaysAvailable ones if no client found
val questionnaireEntries = questionnaireEntriesProvider() val questionnaireEntries = questionnaireEntriesProvider()
val alwaysButtons = questionnaireEntries.mapIndexedNotNull { idx, entry -> val alwaysButtons = questionnaireEntries.mapIndexedNotNull { idx, entry ->
val btn = dynamicButtonsProvider().getOrNull(idx) val btn = dynamicButtonsProvider().getOrNull(idx)
@ -56,27 +57,28 @@ class LoadButtonHandler(
return@launch return@launch
} }
// Profil gefunden → als geladen markieren
GlobalValues.LOADED_CLIENT_CODE = clientCode
withContext(Dispatchers.Main) { withContext(Dispatchers.Main) {
updateMainButtonsState(true) // Datenbank vorhanden -> Buttons aktivieren updateMainButtonsState(true)
} }
handleNormalLoad(clientCode) handleNormalLoad(clientCode)
} }
} }
// Evaluierung der Bedingung: suspend, weil DB-Abfragen stattfinden.
private suspend fun evaluateCondition( private suspend fun evaluateCondition(
condition: QuestionItem.Condition?, condition: QuestionItem.Condition?,
clientCode: String, clientCode: String,
completedEntries: List<CompletedQuestionnaire> completedEntries: List<CompletedQuestionnaire>
): Boolean { ): Boolean {
if (condition == null) return false if (condition == null) return false
return when (condition) {
when (condition) { is QuestionItem.Condition.AlwaysAvailable -> true
is QuestionItem.Condition.AlwaysAvailable -> return true
is QuestionItem.Condition.RequiresCompleted -> { is QuestionItem.Condition.RequiresCompleted -> {
val normalizedCompleted = completedEntries.map { normalizeQuestionnaireId(it.questionnaireId) } val normalizedCompleted = completedEntries.map { normalizeQuestionnaireId(it.questionnaireId) }
return condition.required.all { req -> condition.required.all { req ->
val nReq = normalizeQuestionnaireId(req) val nReq = normalizeQuestionnaireId(req)
normalizedCompleted.any { it.contains(nReq) || nReq.contains(it) } normalizedCompleted.any { it.contains(nReq) || nReq.contains(it) }
} }
@ -85,48 +87,37 @@ class LoadButtonHandler(
val answers = MyApp.database.answerDao().getAnswersForClientAndQuestionnaire(clientCode, condition.questionnaire) val answers = MyApp.database.answerDao().getAnswersForClientAndQuestionnaire(clientCode, condition.questionnaire)
val relevant = answers.find { it.questionId.endsWith(condition.questionId, ignoreCase = true) } val relevant = answers.find { it.questionId.endsWith(condition.questionId, ignoreCase = true) }
val answerValue = relevant?.answerValue ?: "" val answerValue = relevant?.answerValue ?: ""
return when (condition.operator) { when (condition.operator) {
"==" -> answerValue == condition.value "==" -> answerValue == condition.value
"!=" -> answerValue != condition.value "!=" -> answerValue != condition.value
else -> false else -> false
} }
} }
is QuestionItem.Condition.Combined -> { is QuestionItem.Condition.Combined -> {
val reqOk = if (condition.requiresCompleted.isNullOrEmpty()) true
else {
val normalizedCompleted = completedEntries.map { normalizeQuestionnaireId(it.questionnaireId) } val normalizedCompleted = completedEntries.map { normalizeQuestionnaireId(it.questionnaireId) }
condition.requiresCompleted.all { req -> val reqOk = condition.requiresCompleted.isNullOrEmpty() || condition.requiresCompleted.all { req ->
val nReq = normalizeQuestionnaireId(req) val nReq = normalizeQuestionnaireId(req)
normalizedCompleted.any { it.contains(nReq) || nReq.contains(it) } normalizedCompleted.any { it.contains(nReq) || nReq.contains(it) }
} }
}
if (!reqOk) return false if (!reqOk) return false
val q = condition.questionCheck val q = condition.questionCheck ?: return true
if (q != null) {
val answers = MyApp.database.answerDao().getAnswersForClientAndQuestionnaire(clientCode, q.questionnaire) val answers = MyApp.database.answerDao().getAnswersForClientAndQuestionnaire(clientCode, q.questionnaire)
val relevant = answers.find { it.questionId.endsWith(q.questionId, ignoreCase = true) } val relevant = answers.find { it.questionId.endsWith(q.questionId, ignoreCase = true) }
val answerValue = relevant?.answerValue ?: "" val answerValue = relevant?.answerValue ?: ""
return when (q.operator) { when (q.operator) {
"==" -> answerValue == q.value "==" -> answerValue == q.value
"!=" -> answerValue != q.value "!=" -> answerValue != q.value
else -> false else -> false
} }
} }
return reqOk
}
is QuestionItem.Condition.AnyOf -> { is QuestionItem.Condition.AnyOf -> {
for (sub in condition.conditions) { condition.conditions.any { evaluateCondition(it, clientCode, completedEntries) }
val subRes = evaluateCondition(sub, clientCode, completedEntries)
if (subRes) return true
}
return false
} }
} }
} }
private fun normalizeQuestionnaireId(name: String): String { private fun normalizeQuestionnaireId(name: String): String =
return name.lowercase().removeSuffix(".json") name.lowercase().removeSuffix(".json")
}
private suspend fun handleNormalLoad(clientCode: String) { private suspend fun handleNormalLoad(clientCode: String) {
val completedEntries = withContext(Dispatchers.IO) { val completedEntries = withContext(Dispatchers.IO) {
@ -147,7 +138,6 @@ class LoadButtonHandler(
updateButtonTexts() updateButtonTexts()
} }
// für jeden Fragebogen prüfen, ob er aktiv sein darf
val enabledButtons = mutableListOf<Button>() val enabledButtons = mutableListOf<Button>()
val questionnaireEntries = questionnaireEntriesProvider() val questionnaireEntries = questionnaireEntriesProvider()
val dynamicButtons = dynamicButtonsProvider() val dynamicButtons = dynamicButtonsProvider()
@ -155,7 +145,6 @@ class LoadButtonHandler(
for ((idx, entry) in questionnaireEntries.withIndex()) { for ((idx, entry) in questionnaireEntries.withIndex()) {
val button = dynamicButtons.getOrNull(idx) ?: continue val button = dynamicButtons.getOrNull(idx) ?: continue
// falls bereits erledigt: nicht anklickbar
val isCompleted = completedEntries.any { completed -> val isCompleted = completedEntries.any { completed ->
normalizeQuestionnaireId(completed.questionnaireId).let { completedNorm -> normalizeQuestionnaireId(completed.questionnaireId).let { completedNorm ->
val targetNorm = normalizeQuestionnaireId(entry.file) val targetNorm = normalizeQuestionnaireId(entry.file)
@ -164,7 +153,6 @@ class LoadButtonHandler(
} }
if (isCompleted) continue if (isCompleted) continue
// auswerten der Bedingung (suspend)
val condMet = evaluateCondition(entry.condition, clientCode, completedEntries) val condMet = evaluateCondition(entry.condition, clientCode, completedEntries)
if (condMet) enabledButtons.add(button) if (condMet) enabledButtons.add(button)
} }