added edit mode for every important question layout

This commit is contained in:
oxidiert
2025-08-21 11:54:59 +02:00
parent a803be05d5
commit 5f568f4c0e
5 changed files with 229 additions and 11 deletions

View File

@ -3,6 +3,7 @@ package com.dano.test1
import android.content.Context
import android.view.View
import android.widget.*
import kotlinx.coroutines.*
import java.text.SimpleDateFormat
import java.util.*
@ -12,7 +13,8 @@ class HandlerDateSpinner(
private val languageID: String,
private val goToNextQuestion: () -> Unit,
private val goToPreviousQuestion: () -> Unit,
private val showToast: (String) -> Unit
private val showToast: (String) -> Unit,
private val questionnaireMeta: String // neu für DB-Abfrage
) : QuestionHandler {
private lateinit var question: QuestionItem.DateSpinnerQuestion
@ -36,6 +38,7 @@ class HandlerDateSpinner(
questionTextView.text = question.question?.let { LanguageManager.getText(languageID, it) } ?: ""
textView.text = question.textKey?.let { LanguageManager.getText(languageID, it) } ?: ""
// --- gespeicherte Antwort aus answers oder null ---
val (savedYear, savedMonthIndex, savedDay) = question.question?.let {
parseSavedDate(answers[it] as? String)
} ?: Triple(null, null, null)
@ -54,6 +57,49 @@ class HandlerDateSpinner(
setupSpinner(spinnerMonth, months, defaultMonth)
setupSpinner(spinnerYear, years, defaultYear)
// --- DB-Abfrage falls noch nichts in answers gespeichert ---
val answerMapKey = question.question ?: (question.id ?: "")
if (answerMapKey.isNotBlank() && !answers.containsKey(answerMapKey)) {
CoroutineScope(Dispatchers.IO).launch {
try {
val clientCode = GlobalValues.LAST_CLIENT_CODE
if (clientCode.isNullOrBlank()) return@launch
val allAnswersForClient = MyApp.database.answerDao().getAnswersForClient(clientCode)
val myQuestionId = questionnaireMeta + "-" + question.question
val dbAnswer = allAnswersForClient.find { it.questionId == myQuestionId }?.answerValue
if (!dbAnswer.isNullOrBlank()) {
withContext(Dispatchers.Main) {
answers[answerMapKey] = dbAnswer
val (dbYear, dbMonthIndex, dbDay) = parseSavedDate(dbAnswer)
dbYear?.let { year ->
val index = years.indexOf(year)
if (index >= 0) spinnerYear.setSelection(index)
}
dbMonthIndex?.let { monthIndex ->
if (monthIndex in months.indices) {
val monthObj = months[monthIndex]
val idx = months.indexOf(monthObj)
if (idx >= 0) spinnerMonth.setSelection(idx)
}
}
dbDay?.let { day ->
val idx = days.indexOf(day)
if (idx >= 0) spinnerDay.setSelection(idx)
}
}
}
} catch (e: Exception) {
e.printStackTrace()
}
}
}
layout.findViewById<Button>(R.id.Qnext).setOnClickListener {
if (validate()) {
saveAnswer()

View File

@ -4,6 +4,7 @@ import android.content.Context
import android.view.Gravity
import android.view.View
import android.widget.*
import kotlinx.coroutines.*
class HandlerGlassScaleQuestion(
private val context: Context,
@ -12,7 +13,8 @@ class HandlerGlassScaleQuestion(
private val languageID: String,
private val goToNextQuestion: () -> Unit,
private val goToPreviousQuestion: () -> Unit,
private val showToast: (String) -> Unit
private val showToast: (String) -> Unit,
private val questionnaireMeta: String // neu: für DB-Abfrage wie bei anderen Handlern
) : QuestionHandler {
private lateinit var layout: View
@ -71,6 +73,63 @@ class HandlerGlassScaleQuestion(
addSymptomRows(tableLayout)
// --- DB-Abfrage: falls für eine Symptom-Antwort noch nichts im answers-Map steht ---
// Wir holen alle Antworten für den Client und setzen dort, wo answers noch keinen Eintrag hat,
// die gespeicherte DB-Antwort (und aktualisieren UI + points).
val anySymptomNeedsRestore = question.symptoms.any { sym ->
val key = sym
!answers.containsKey(key)
}
if (anySymptomNeedsRestore) {
CoroutineScope(Dispatchers.IO).launch {
try {
val clientCode = GlobalValues.LAST_CLIENT_CODE
if (clientCode.isNullOrBlank()) return@launch
val allAnswersForClient = MyApp.database.answerDao().getAnswersForClient(clientCode)
// Bereite ein Map von questionId -> answerValue vor für schnellen Zugriff
val answerMap = allAnswersForClient.associateBy({ it.questionId }, { it.answerValue })
withContext(Dispatchers.Main) {
// tableLayout: header ist bei index 0, symptom-rows ab index 1 in der gleichen Reihenfolge wie question.symptoms
for ((index, symptomKey) in question.symptoms.withIndex()) {
val answerMapKey = questionnaireMeta + "-" + symptomKey
val dbAnswerRaw = answerMap[answerMapKey]
val dbAnswer = dbAnswerRaw?.takeIf { it.isNotBlank() }?.trim()
// nur wenn noch nichts im answers-Map steht und DB-Antwort vorhanden
if (!answers.containsKey(symptomKey) && !dbAnswer.isNullOrBlank()) {
// finde die entsprechende TableRow (header = 0, erste symptom row = 1)
val rowIndex = index + 1
if (rowIndex < tableLayout.childCount) {
val row = tableLayout.getChildAt(rowIndex) as? TableRow ?: continue
val radioGroup = row.getChildAt(1) as? RadioGroup ?: continue
// Suche RadioButton mit tag == dbAnswer und markiere ihn
for (i in 0 until radioGroup.childCount) {
val rb = radioGroup.getChildAt(i) as? RadioButton ?: continue
if ((rb.tag as? String)?.trim() == dbAnswer) {
rb.isChecked = true
break
}
}
// answers und points aktualisieren
answers[symptomKey] = dbAnswer
val point = pointsMap[dbAnswer] ?: 0
points.add(point)
}
}
}
}
} catch (e: Exception) {
e.printStackTrace()
}
}
}
layout.findViewById<Button>(R.id.Qnext).setOnClickListener {
if (validate()) {
saveAnswer()
@ -138,10 +197,10 @@ class HandlerGlassScaleQuestion(
}
override fun saveAnswer() {
// Vorherige Punkte dieser Frage entfernen
// Vorherige Punkte dieser Frage entfernen (falls vorhanden)
question.symptoms.forEach {
val previousLabel = answers[it] as? String
val previousPoint = pointsMap[previousLabel]
val previousPoint = previousLabel?.let { lbl -> pointsMap[lbl] }
if (previousPoint != null) {
points.remove(previousPoint)
}

View File

@ -3,6 +3,7 @@ package com.dano.test1
import android.content.Context
import android.view.View
import android.widget.*
import kotlinx.coroutines.*
class HandlerMultiCheckboxQuestion(
private val context: Context,
@ -11,7 +12,8 @@ class HandlerMultiCheckboxQuestion(
private val languageID: String,
private val goToNextQuestion: () -> Unit,
private val goToPreviousQuestion: () -> Unit,
private val showToast: (String) -> Unit
private val showToast: (String) -> Unit,
private val questionnaireMeta: String // neu: für DB-ID wie bei den anderen Handlern
) : QuestionHandler {
private lateinit var layout: View
@ -25,12 +27,12 @@ class HandlerMultiCheckboxQuestion(
val questionTitle = layout.findViewById<TextView>(R.id.question)
val questionTextView = layout.findViewById<TextView>(R.id.textView)
// Hier jetzt identisch zur RadioQuestion:
questionTextView.text = this.question.textKey?.let { LanguageManager.getText(languageID, it) } ?: ""
questionTitle.text = this.question.question?.let { LanguageManager.getText(languageID, it) } ?: ""
container.removeAllViews()
// bestehende Auswahl aus answers (falls vorhanden) als Set
val selectedKeys = this.question.question?.let {
(answers[it] as? List<*>)?.map { it.toString() }?.toSet()
} ?: emptySet()
@ -52,6 +54,44 @@ class HandlerMultiCheckboxQuestion(
container.addView(checkBox)
}
// --- DB-Abfrage falls noch kein Eintrag im answers-Map existiert ---
val answerMapKey = question.question ?: (question.id ?: "")
if (answerMapKey.isNotBlank() && !answers.containsKey(answerMapKey)) {
CoroutineScope(Dispatchers.IO).launch {
try {
val clientCode = GlobalValues.LAST_CLIENT_CODE
if (clientCode.isNullOrBlank()) return@launch
val allAnswersForClient = MyApp.database.answerDao().getAnswersForClient(clientCode)
val myQuestionId = questionnaireMeta + "-" + question.question
val dbAnswer = allAnswersForClient.find { it.questionId == myQuestionId }?.answerValue
if (!dbAnswer.isNullOrBlank()) {
val parsed = parseMultiAnswer(dbAnswer)
withContext(Dispatchers.Main) {
// UI: Checkboxen setzen
for (i in 0 until container.childCount) {
val cb = container.getChildAt(i) as? CheckBox ?: continue
cb.isChecked = parsed.contains(cb.tag.toString())
}
// answers-Map aktualisieren (Liste)
answers[answerMapKey] = parsed.toList()
// Punkte berechnen und hinzufügen
val totalPoints = parsed.sumOf { key ->
question.pointsMap?.get(key) ?: 0
}
points.add(totalPoints)
}
}
} catch (e: Exception) {
e.printStackTrace()
}
}
}
layout.findViewById<Button>(R.id.Qnext).setOnClickListener {
if (validate()) {
saveAnswer()
@ -97,8 +137,54 @@ class HandlerMultiCheckboxQuestion(
}
question.question?.let { questionKey ->
// entferne ggf. vorherige Punkte-Einträge für diese Frage, falls die answers-Map vorher schon einen Wert hatte.
val oldList = answers[questionKey] as? List<*>
if (oldList != null) {
val oldTotal = oldList.mapNotNull { it?.toString() }.sumOf { oldKey ->
question.pointsMap?.get(oldKey) ?: 0
}
// Entfernen eines einzelnen Int-Wertes aus points (falls vorhanden)
points.remove(oldTotal)
}
answers[questionKey] = selectedKeys
points.add(totalPoints)
}
}
/**
* Parsen der DB-Antwort in ein Set von Keys. Unterstützt:
* - JSON-Array: ["a","b"]
* - kommasepariert: a,b
* - semikolon-separiert: a;b
* - einzelner Wert: a
*/
private fun parseMultiAnswer(dbAnswer: String): Set<String> {
val trimmed = dbAnswer.trim()
// JSON-Array-like
if (trimmed.startsWith("[") && trimmed.endsWith("]")) {
val inner = trimmed.substring(1, trimmed.length - 1)
if (inner.isBlank()) return emptySet()
return inner.split(",")
.map { it.trim().trim('"', '\'') }
.filter { it.isNotEmpty() }
.toSet()
}
// If contains comma or semicolon
val separator = when {
trimmed.contains(",") -> ","
trimmed.contains(";") -> ";"
else -> null
}
return if (separator != null) {
trimmed.split(separator)
.map { it.trim().trim('"', '\'') }
.filter { it.isNotEmpty() }
.toSet()
} else {
setOf(trimmed.trim().trim('"', '\''))
}
}
}

View File

@ -3,6 +3,7 @@ package com.dano.test1
import android.content.Context
import android.view.View
import android.widget.*
import kotlinx.coroutines.*
class HandlerValueSpinner(
private val context: Context,
@ -11,7 +12,8 @@ class HandlerValueSpinner(
private val goToNextQuestion: () -> Unit,
private val goToPreviousQuestion: () -> Unit,
private val goToQuestionById: (String) -> Unit,
private val showToast: (String) -> Unit
private val showToast: (String) -> Unit,
private val questionnaireMeta: String // neu: für die DB-Abfrage
) : QuestionHandler {
private lateinit var layout: View
@ -40,6 +42,31 @@ class HandlerValueSpinner(
val savedValue = question.question?.let { answers[it] as? String }
setupSpinner(spinner, spinnerItems, savedValue)
// --- DB-Abfrage falls noch keine Antwort im Map existiert ---
val answerMapKey = question.question ?: (question.id ?: "")
if (answerMapKey.isNotBlank() && !answers.containsKey(answerMapKey)) {
CoroutineScope(Dispatchers.IO).launch {
try {
val clientCode = GlobalValues.LAST_CLIENT_CODE
if (clientCode.isNullOrBlank()) return@launch
val allAnswersForClient = MyApp.database.answerDao().getAnswersForClient(clientCode)
val myQuestionId = questionnaireMeta + "-" + question.question
val dbAnswer = allAnswersForClient.find { it.questionId == myQuestionId }?.answerValue
if (!dbAnswer.isNullOrBlank()) {
withContext(Dispatchers.Main) {
answers[answerMapKey] = dbAnswer
val index = spinnerItems.indexOf(dbAnswer)
if (index >= 0) spinner.setSelection(index)
}
}
} catch (e: Exception) {
e.printStackTrace()
}
}
}
layout.findViewById<Button>(R.id.Qnext).setOnClickListener {
if (validate()) {
saveAnswer()

View File

@ -157,12 +157,12 @@ abstract class QuestionnaireBase<T> {
return when (question) {
is QuestionItem.RadioQuestion -> HandlerRadioQuestion(context, answers, points, languageID, ::goToNextQuestion, ::goToPreviousQuestion, ::goToQuestionById, ::showToast, questionnaireMeta.id)
is QuestionItem.ClientCoachCodeQuestion -> HandlerClientCoachCode(answers, languageID, ::goToNextQuestion, ::goToPreviousQuestion, ::showToast)
is QuestionItem.DateSpinnerQuestion -> HandlerDateSpinner(context, answers, languageID, ::goToNextQuestion, ::goToPreviousQuestion, ::showToast)
is QuestionItem.ValueSpinnerQuestion -> HandlerValueSpinner(context, answers, languageID, ::goToNextQuestion, ::goToPreviousQuestion, ::goToQuestionById, ::showToast)
is QuestionItem.GlassScaleQuestion -> HandlerGlassScaleQuestion(context, answers, points, languageID, ::goToNextQuestion, ::goToPreviousQuestion, ::showToast)
is QuestionItem.DateSpinnerQuestion -> HandlerDateSpinner(context, answers, languageID, ::goToNextQuestion, ::goToPreviousQuestion, ::showToast, questionnaireMeta.id)
is QuestionItem.ValueSpinnerQuestion -> HandlerValueSpinner(context, answers, languageID, ::goToNextQuestion, ::goToPreviousQuestion, ::goToQuestionById, ::showToast, questionnaireMeta.id)
is QuestionItem.GlassScaleQuestion -> HandlerGlassScaleQuestion(context, answers, points, languageID, ::goToNextQuestion, ::goToPreviousQuestion, ::showToast, questionnaireMeta.id)
is QuestionItem.ClientNotSigned -> HandlerClientNotSigned(answers, languageID, ::goToNextQuestion, ::goToPreviousQuestion, ::showToast)
is QuestionItem.StringSpinnerQuestion -> HandlerStringSpinner(context, answers, languageID, ::goToNextQuestion, ::goToPreviousQuestion, ::showToast, questionnaireMeta.id)
is QuestionItem.MultiCheckboxQuestion -> HandlerMultiCheckboxQuestion(context, answers, points, languageID, ::goToNextQuestion, ::goToPreviousQuestion, ::showToast)
is QuestionItem.MultiCheckboxQuestion -> HandlerMultiCheckboxQuestion(context, answers, points, languageID, ::goToNextQuestion, ::goToPreviousQuestion, ::showToast, questionnaireMeta.id)
is QuestionItem.LastPage -> HandlerLastPage(
answers, languageID, ::goToNextQuestion, ::goToPreviousQuestion
) { CoroutineScope(Dispatchers.IO).launch { saveAnswersToDatabase(answers, questionnaireMeta.id) } }