Compare commits
2 Commits
4a22f90269
...
148af18496
| Author | SHA1 | Date | |
|---|---|---|---|
| 148af18496 | |||
| 6e33d61b1e |
@ -1,24 +1,41 @@
|
||||
[
|
||||
{
|
||||
"file": "questionnaire_1_demographic_information.json",
|
||||
"showPoints": false
|
||||
"showPoints": false,
|
||||
"condition": {
|
||||
"alwaysAvailable": true
|
||||
}
|
||||
},
|
||||
{
|
||||
"file": "questionnaire_2_rhs.json",
|
||||
"showPoints": true
|
||||
"showPoints": true,
|
||||
"condition": {
|
||||
"alwaysAvailable": true
|
||||
}
|
||||
},
|
||||
{
|
||||
"file": "questionnaire_3_integration_index.json",
|
||||
"showPoints": true
|
||||
"showPoints": true,
|
||||
"condition": {
|
||||
"alwaysAvailable": true
|
||||
}
|
||||
},
|
||||
{
|
||||
"file": "questionnaire_4_consultation_results.json",
|
||||
"showPoints": false
|
||||
"showPoints": false,
|
||||
"condition": {
|
||||
"requiresCompleted": [
|
||||
"questionnaire_1_demographic_information",
|
||||
"questionnaire_2_rhs",
|
||||
"questionnaire_3_integration_index"
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
"file": "questionnaire_5_final_interview.json",
|
||||
"showPoints": false,
|
||||
"condition": {
|
||||
"requiresCompleted": ["questionnaire_4_consultation_results"],
|
||||
"questionnaire": "questionnaire_4_consultation_results",
|
||||
"questionId": "consultation_decision",
|
||||
"operator": "==",
|
||||
@ -27,6 +44,20 @@
|
||||
},
|
||||
{
|
||||
"file": "questionnaire_6_follow_up_survey.json",
|
||||
"showPoints": false
|
||||
"showPoints": false,
|
||||
"condition": {
|
||||
"anyOf": [
|
||||
{
|
||||
"requiresCompleted": ["questionnaire_5_final_interview"]
|
||||
},
|
||||
{
|
||||
"requiresCompleted": ["questionnaire_4_consultation_results"],
|
||||
"questionnaire": "questionnaire_4_consultation_results",
|
||||
"questionId": "consultation_decision",
|
||||
"operator": "!=",
|
||||
"value": "yellow"
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
]
|
||||
|
||||
@ -14,7 +14,6 @@ import okhttp3.RequestBody.Companion.asRequestBody
|
||||
import org.json.JSONArray
|
||||
import org.json.JSONObject
|
||||
import java.io.File
|
||||
import java.io.FileOutputStream
|
||||
import java.io.IOException
|
||||
import kotlin.system.exitProcess
|
||||
|
||||
@ -22,7 +21,7 @@ object DatabaseUploader {
|
||||
|
||||
private const val DB_NAME = "questionnaire_database"
|
||||
// TODO entferne uploadDeltaTest2.php
|
||||
private const val SERVER_DELTA_URL = "http://49.13.157.44/uploadDeltaTest2.php"
|
||||
private const val SERVER_DELTA_URL = "http://49.13.157.44/uploadDeltaTest3.php"
|
||||
private const val SERVER_CHECK_URL = "http://49.13.157.44/checkDatabaseExists.php"
|
||||
private const val API_TOKEN = "MEIN_SUPER_GEHEIMES_TOKEN_12345"
|
||||
|
||||
|
||||
@ -69,6 +69,11 @@ class HandlerClientCoachCode(
|
||||
val clientCode = clientCodeField.text.toString()
|
||||
val coachCode = coachCodeField.text.toString()
|
||||
|
||||
// Prüfen, ob die Datenbank-Dateien vor dem Klick existieren
|
||||
val dbFile = layout.context.getDatabasePath("questionnaire_database")
|
||||
val dbJournalFile = layout.context.getDatabasePath("questionnaire_database-journal")
|
||||
val dbExisted = dbFile.exists() || dbJournalFile.exists()
|
||||
|
||||
// Check if client code already exists asynchronously
|
||||
CoroutineScope(Dispatchers.IO).launch {
|
||||
val existingClient = MyApp.database.clientDao().getClientByCode(clientCode)
|
||||
@ -81,6 +86,13 @@ class HandlerClientCoachCode(
|
||||
} else {
|
||||
// Either no existing client or re-using previous code
|
||||
saveAnswers(clientCode, coachCode)
|
||||
|
||||
// Datenbank-Dateien löschen, wenn sie vorher NICHT existierten
|
||||
if (!dbExisted) {
|
||||
dbFile.delete()
|
||||
dbJournalFile.delete()
|
||||
}
|
||||
|
||||
goToNextQuestion()
|
||||
}
|
||||
}
|
||||
|
||||
@ -8,10 +8,11 @@ import android.view.View
|
||||
import android.widget.*
|
||||
import kotlinx.coroutines.*
|
||||
import org.json.JSONArray
|
||||
import org.json.JSONObject
|
||||
import android.util.Log
|
||||
import com.dano.test1.data.CompletedQuestionnaire
|
||||
import java.io.File
|
||||
|
||||
|
||||
var INTEGRATION_INDEX_POINTS: Int? = null
|
||||
|
||||
class HandlerOpeningScreen(private val activity: MainActivity) {
|
||||
@ -52,8 +53,7 @@ class HandlerOpeningScreen(private val activity: MainActivity) {
|
||||
val pathExists = File(dbPath).exists()
|
||||
if (pathExists) {
|
||||
updateMainButtonsState(true)
|
||||
}
|
||||
else{
|
||||
} else {
|
||||
updateMainButtonsState(false)
|
||||
}
|
||||
|
||||
@ -90,15 +90,7 @@ class HandlerOpeningScreen(private val activity: MainActivity) {
|
||||
val obj = jsonArray.getJSONObject(i)
|
||||
val file = obj.getString("file")
|
||||
val conditionObj = obj.optJSONObject("condition")
|
||||
val condition = if (conditionObj != null) {
|
||||
QuestionItem.Condition(
|
||||
questionnaire = conditionObj.getString("questionnaire"),
|
||||
questionId = conditionObj.getString("questionId"),
|
||||
operator = conditionObj.getString("operator"),
|
||||
value = conditionObj.getString("value")
|
||||
)
|
||||
} else null
|
||||
|
||||
val condition = parseCondition(conditionObj)
|
||||
val showPoints = obj.optBoolean("showPoints", false)
|
||||
|
||||
QuestionItem.QuestionnaireEntry(file, condition, showPoints)
|
||||
@ -109,6 +101,63 @@ class HandlerOpeningScreen(private val activity: MainActivity) {
|
||||
}
|
||||
}
|
||||
|
||||
// Parser: erzeugt ein QuestionItem.Condition? (sehr robust gegenüber verschiedenen JSON-Formaten)
|
||||
private fun parseCondition(conditionObj: JSONObject?): QuestionItem.Condition? {
|
||||
if (conditionObj == null) return null
|
||||
|
||||
// anyOf
|
||||
if (conditionObj.has("anyOf")) {
|
||||
val arr = conditionObj.optJSONArray("anyOf") ?: JSONArray()
|
||||
val conditions = mutableListOf<QuestionItem.Condition>()
|
||||
for (i in 0 until arr.length()) {
|
||||
val sub = arr.optJSONObject(i)
|
||||
parseCondition(sub)?.let { conditions.add(it) }
|
||||
}
|
||||
return QuestionItem.Condition.AnyOf(conditions)
|
||||
}
|
||||
|
||||
// alwaysAvailable
|
||||
if (conditionObj.has("alwaysAvailable")) {
|
||||
val flag = conditionObj.optBoolean("alwaysAvailable", false)
|
||||
if (flag) return QuestionItem.Condition.AlwaysAvailable
|
||||
}
|
||||
|
||||
// requiresCompleted (array)
|
||||
val requiresList = mutableListOf<String>()
|
||||
if (conditionObj.has("requiresCompleted")) {
|
||||
val reqArr = conditionObj.optJSONArray("requiresCompleted")
|
||||
if (reqArr != null) {
|
||||
for (i in 0 until reqArr.length()) {
|
||||
requiresList.add(reqArr.optString(i))
|
||||
}
|
||||
} else {
|
||||
// sometimes it's a single string
|
||||
conditionObj.optString("requiresCompleted")?.let { if (it.isNotBlank()) requiresList.add(it) }
|
||||
}
|
||||
}
|
||||
|
||||
// question-check fields
|
||||
val questionnaire = conditionObj.optString("questionnaire", null)
|
||||
val questionId = conditionObj.optString("questionId", null)
|
||||
val operator = conditionObj.optString("operator", null)
|
||||
val value = conditionObj.optString("value", null)
|
||||
|
||||
val hasQuestionCheck = !questionnaire.isNullOrBlank() && !questionId.isNullOrBlank() && !operator.isNullOrBlank() && value != null
|
||||
|
||||
return when {
|
||||
requiresList.isNotEmpty() && hasQuestionCheck -> {
|
||||
QuestionItem.Condition.Combined(requiresList, QuestionItem.Condition.QuestionCondition(questionnaire!!, questionId!!, operator!!, value!!))
|
||||
}
|
||||
hasQuestionCheck -> {
|
||||
QuestionItem.Condition.QuestionCondition(questionnaire!!, questionId!!, operator!!, value!!)
|
||||
}
|
||||
requiresList.isNotEmpty() -> {
|
||||
QuestionItem.Condition.RequiresCompleted(requiresList)
|
||||
}
|
||||
else -> null
|
||||
}
|
||||
}
|
||||
|
||||
private fun createQuestionnaireButtons() {
|
||||
buttonContainer.removeAllViews()
|
||||
dynamicButtons.clear()
|
||||
@ -129,12 +178,23 @@ class HandlerOpeningScreen(private val activity: MainActivity) {
|
||||
}
|
||||
|
||||
updateButtonTexts()
|
||||
setButtonsEnabled(listOf(dynamicButtons.firstOrNull()).filterNotNull())
|
||||
|
||||
// Initial: enable those with AlwaysAvailable (falls vorhanden)
|
||||
val alwaysButtons = questionnaireEntries.mapIndexedNotNull { idx, entry ->
|
||||
val btn = dynamicButtons.getOrNull(idx)
|
||||
if (entry.condition is QuestionItem.Condition.AlwaysAvailable) btn else null
|
||||
}
|
||||
setButtonsEnabled(alwaysButtons)
|
||||
|
||||
dynamicButtons.forEach { button ->
|
||||
button.setOnClickListener {
|
||||
// require a client code to start actual questionnaire (sichere Kontrolle)
|
||||
val clientCode = editText.text.toString().trim()
|
||||
|
||||
GlobalValues.LAST_CLIENT_CODE = clientCode
|
||||
startQuestionnaireForButton(button)
|
||||
setButtonsEnabled(dynamicButtons.filter { it != button })
|
||||
// disable other buttons while one questionnaire is open
|
||||
setButtonsEnabled(dynamicButtons.filter { it == button })
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -190,86 +250,143 @@ class HandlerOpeningScreen(private val activity: MainActivity) {
|
||||
withContext(Dispatchers.Main) {
|
||||
val message = LanguageManager.getText(languageID, "no_profile")
|
||||
Toast.makeText(activity, message, Toast.LENGTH_LONG).show()
|
||||
setButtonsEnabled(listOf(dynamicButtons.firstOrNull()).filterNotNull())
|
||||
// enable only alwaysAvailable ones if no client found
|
||||
val alwaysButtons = questionnaireEntries.mapIndexedNotNull { idx, entry ->
|
||||
val btn = dynamicButtons.getOrNull(idx)
|
||||
if (entry.condition is QuestionItem.Condition.AlwaysAvailable) btn else null
|
||||
}
|
||||
setButtonsEnabled(alwaysButtons)
|
||||
}
|
||||
return@launch
|
||||
}
|
||||
|
||||
withContext(Dispatchers.Main) {
|
||||
updateMainButtonsState(true) // Datenbank vorhanden -> Buttons aktivieren
|
||||
handleNormalLoad(clientCode)
|
||||
}
|
||||
|
||||
handleNormalLoad(clientCode)
|
||||
}
|
||||
}
|
||||
|
||||
// Evaluierung der Bedingung: suspend, weil DB-Abfragen stattfinden.
|
||||
private suspend fun evaluateCondition(
|
||||
condition: QuestionItem.Condition?,
|
||||
clientCode: String,
|
||||
completedEntries: List<CompletedQuestionnaire> // Anpassung an deinem DAO-Objekt-Name
|
||||
): Boolean {
|
||||
if (condition == null) return false
|
||||
|
||||
when (condition) {
|
||||
is QuestionItem.Condition.AlwaysAvailable -> return true
|
||||
is QuestionItem.Condition.RequiresCompleted -> {
|
||||
// prüfen, ob alle required items in completedEntries vorhanden und isDone == true sind
|
||||
val normalizedCompleted = completedEntries.map { normalizeQuestionnaireId(it.questionnaireId) }
|
||||
return condition.required.all { req ->
|
||||
val nReq = normalizeQuestionnaireId(req)
|
||||
normalizedCompleted.any { it.contains(nReq) || nReq.contains(it) }
|
||||
}
|
||||
}
|
||||
is QuestionItem.Condition.QuestionCondition -> {
|
||||
// need to fetch the answer for that questionnaire/questionId
|
||||
val answers = MyApp.database.answerDao().getAnswersForClientAndQuestionnaire(clientCode, condition.questionnaire)
|
||||
val relevant = answers.find { it.questionId.endsWith(condition.questionId, ignoreCase = true) }
|
||||
val answerValue = relevant?.answerValue ?: ""
|
||||
return when (condition.operator) {
|
||||
"==" -> answerValue == condition.value
|
||||
"!=" -> answerValue != condition.value
|
||||
else -> false
|
||||
}
|
||||
}
|
||||
is QuestionItem.Condition.Combined -> {
|
||||
// Combined: requiresCompleted (if present) AND questionCheck must match
|
||||
val reqOk = if (condition.requiresCompleted.isNullOrEmpty()) true
|
||||
else {
|
||||
val normalizedCompleted = completedEntries.map { normalizeQuestionnaireId(it.questionnaireId) }
|
||||
condition.requiresCompleted.all { req ->
|
||||
val nReq = normalizeQuestionnaireId(req)
|
||||
normalizedCompleted.any { it.contains(nReq) || nReq.contains(it) }
|
||||
}
|
||||
}
|
||||
if (!reqOk) return false
|
||||
// dann Frage-Check auswerten
|
||||
val q = condition.questionCheck
|
||||
if (q != null) {
|
||||
val answers = MyApp.database.answerDao().getAnswersForClientAndQuestionnaire(clientCode, q.questionnaire)
|
||||
val relevant = answers.find { it.questionId.endsWith(q.questionId, ignoreCase = true) }
|
||||
val answerValue = relevant?.answerValue ?: ""
|
||||
return when (q.operator) {
|
||||
"==" -> answerValue == q.value
|
||||
"!=" -> answerValue != q.value
|
||||
else -> false
|
||||
}
|
||||
}
|
||||
return reqOk
|
||||
}
|
||||
is QuestionItem.Condition.AnyOf -> {
|
||||
// true, wenn irgendeine der Sub-Bedingungen erfüllt ist
|
||||
for (sub in condition.conditions) {
|
||||
val subRes = evaluateCondition(sub, clientCode, completedEntries)
|
||||
if (subRes) return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun normalizeQuestionnaireId(name: String): String {
|
||||
return name.lowercase().removeSuffix(".json")
|
||||
}
|
||||
|
||||
private suspend fun handleNormalLoad(clientCode: String) {
|
||||
val completedIds = withContext(Dispatchers.IO) {
|
||||
MyApp.database.completedQuestionnaireDao().getCompletedQuestionnairesForClient(clientCode)
|
||||
}
|
||||
|
||||
if (completedIds.isEmpty()) {
|
||||
setButtonsEnabled(listOf(dynamicButtons.firstOrNull()).filterNotNull())
|
||||
val message = LanguageManager.getText(languageID, "no_profile")
|
||||
Toast.makeText(activity, message, Toast.LENGTH_LONG).show()
|
||||
return
|
||||
}
|
||||
|
||||
val completedIndexes = completedIds.mapNotNull { id ->
|
||||
questionnaireEntries.indexOfFirst { it.file.contains(id, ignoreCase = true) }.takeIf { it >= 0 }
|
||||
}.sorted()
|
||||
|
||||
val completedEntries = withContext(Dispatchers.IO) {
|
||||
MyApp.database.completedQuestionnaireDao().getAllForClient(clientCode)
|
||||
}
|
||||
|
||||
// fülle buttonPoints & INTEGRATION_INDEX_POINTS
|
||||
buttonPoints.clear()
|
||||
for (entry in completedEntries) {
|
||||
if (entry.isDone) {
|
||||
buttonPoints[entry.questionnaireId] = entry.sumPoints ?: 0
|
||||
|
||||
if (entry.questionnaireId.contains("questionnaire_3_integration_index", ignoreCase = true)) {
|
||||
INTEGRATION_INDEX_POINTS = entry.sumPoints
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
updateButtonTexts()
|
||||
|
||||
var nextIndex = (completedIndexes.lastOrNull() ?: -1) + 1
|
||||
|
||||
while (nextIndex < questionnaireEntries.size) {
|
||||
val entry = questionnaireEntries[nextIndex]
|
||||
val condition = entry.condition
|
||||
|
||||
if (condition != null) {
|
||||
val answers = MyApp.database.answerDao().getAnswersForClientAndQuestionnaire(clientCode, condition.questionnaire)
|
||||
|
||||
val relevantAnswer = answers.find {
|
||||
it.questionId.endsWith(condition.questionId)
|
||||
}
|
||||
|
||||
val answerValue = relevantAnswer?.answerValue ?: ""
|
||||
val conditionMet = when (condition.operator) {
|
||||
"!=" -> answerValue != condition.value
|
||||
"==" -> answerValue == condition.value
|
||||
else -> true
|
||||
}
|
||||
|
||||
if (conditionMet) break
|
||||
else nextIndex++
|
||||
} else {
|
||||
break
|
||||
}
|
||||
withContext(Dispatchers.Main) {
|
||||
updateButtonTexts()
|
||||
}
|
||||
|
||||
if (nextIndex >= questionnaireEntries.size) {
|
||||
setButtonsEnabled(emptyList())
|
||||
val message = LanguageManager.getText(languageID, "questionnaires_finished")
|
||||
Toast.makeText(activity, message, Toast.LENGTH_LONG).show()
|
||||
} else {
|
||||
val nextFileName = questionnaireEntries[nextIndex].file
|
||||
val nextButton = questionnaireFiles.entries.firstOrNull { it.value == nextFileName }?.key
|
||||
setButtonsEnabled(listOfNotNull(nextButton))
|
||||
// für jeden Fragebogen prüfen, ob er aktiv sein darf
|
||||
val enabledButtons = mutableListOf<Button>()
|
||||
for ((idx, entry) in questionnaireEntries.withIndex()) {
|
||||
val button = dynamicButtons.getOrNull(idx) ?: continue
|
||||
|
||||
// falls bereits erledigt: nicht anklickbar
|
||||
val isCompleted = completedEntries.any { completed ->
|
||||
normalizeQuestionnaireId(completed.questionnaireId).let { completedNorm ->
|
||||
val targetNorm = normalizeQuestionnaireId(entry.file)
|
||||
completedNorm.contains(targetNorm) || targetNorm.contains(completedNorm)
|
||||
} && completed.isDone
|
||||
}
|
||||
if (isCompleted) {
|
||||
// ausdrücklich deaktivieren
|
||||
continue
|
||||
}
|
||||
|
||||
// auswerten der Bedingung (suspend)
|
||||
val condMet = evaluateCondition(entry.condition, clientCode, completedEntries)
|
||||
if (condMet) enabledButtons.add(button)
|
||||
}
|
||||
|
||||
withContext(Dispatchers.Main) {
|
||||
if (enabledButtons.isEmpty()) {
|
||||
setButtonsEnabled(emptyList())
|
||||
val message = LanguageManager.getText(languageID, "questionnaires_finished")
|
||||
Toast.makeText(activity, message, Toast.LENGTH_LONG).show()
|
||||
} else {
|
||||
setButtonsEnabled(enabledButtons)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -278,6 +395,7 @@ class HandlerOpeningScreen(private val activity: MainActivity) {
|
||||
|
||||
val entry = questionnaireEntries.firstOrNull { it.file == fileName }
|
||||
|
||||
// key ableiten
|
||||
val key = fileName.substringAfter("questionnaire_").substringAfter("_").removeSuffix(".json")
|
||||
var buttonText = LanguageManager.getText(languageID, key)
|
||||
|
||||
@ -574,15 +692,12 @@ class HandlerOpeningScreen(private val activity: MainActivity) {
|
||||
// --- Füge diese Funktion in deine Klasse ein ---
|
||||
private fun isDatabasePopulated(): Boolean {
|
||||
return try {
|
||||
// Wir prüfen, ob die Datenbank mindestens eine nicht-interne Tabelle enthält.
|
||||
// Das ist robust gegenüber verschiedenen Tabellennamen.
|
||||
val db = MyApp.database.openHelper.readableDatabase
|
||||
val cursor = db.query(
|
||||
"SELECT name FROM sqlite_master WHERE type='table' AND name NOT LIKE 'sqlite_%' AND name != 'room_master_table'"
|
||||
)
|
||||
cursor.use { it.count > 0 }
|
||||
} catch (e: Exception) {
|
||||
// Falls etwas schiefgeht (z.B. DB noch nicht vorhanden), gilt: nicht vorhanden
|
||||
false
|
||||
}
|
||||
}
|
||||
@ -607,4 +722,4 @@ class HandlerOpeningScreen(private val activity: MainActivity) {
|
||||
button.alpha = if (isDatabaseAvailable) 1.0f else 0.5f
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -112,11 +112,22 @@ sealed class QuestionItem {
|
||||
val showPoints: Boolean = false // neu
|
||||
)
|
||||
|
||||
data class Condition(
|
||||
val questionnaire: String,
|
||||
val questionId: String,
|
||||
val operator: String,
|
||||
val value: String
|
||||
)
|
||||
}
|
||||
// flexible Condition-Typen für die questionnaire_order.json
|
||||
sealed class Condition {
|
||||
object AlwaysAvailable : Condition()
|
||||
data class RequiresCompleted(val required: List<String>) : Condition()
|
||||
data class QuestionCondition(
|
||||
val questionnaire: String,
|
||||
val questionId: String,
|
||||
val operator: String,
|
||||
val value: String
|
||||
) : Condition()
|
||||
|
||||
data class Combined(
|
||||
val requiresCompleted: List<String>?,
|
||||
val questionCheck: QuestionCondition?
|
||||
) : Condition()
|
||||
|
||||
data class AnyOf(val conditions: List<Condition>) : Condition()
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user