Initialer Upload neues Unity-Projekt
This commit is contained in:
22
app/src/main/java/com/dano/test1/AppDatabase.kt
Normal file
22
app/src/main/java/com/dano/test1/AppDatabase.kt
Normal file
@ -0,0 +1,22 @@
|
||||
package com.dano.test1.data
|
||||
|
||||
import androidx.room.Database
|
||||
import androidx.room.RoomDatabase
|
||||
|
||||
@Database(
|
||||
entities = [
|
||||
Client::class,
|
||||
Questionnaire::class,
|
||||
Question::class,
|
||||
Answer::class,
|
||||
CompletedQuestionnaire::class
|
||||
],
|
||||
version = 10
|
||||
)
|
||||
abstract class AppDatabase : RoomDatabase() {
|
||||
abstract fun clientDao(): ClientDao
|
||||
abstract fun questionnaireDao(): QuestionnaireDao
|
||||
abstract fun questionDao(): QuestionDao
|
||||
abstract fun answerDao(): AnswerDao
|
||||
abstract fun completedQuestionnaireDao(): CompletedQuestionnaireDao
|
||||
}
|
||||
50
app/src/main/java/com/dano/test1/CountriesSpinner.kt
Normal file
50
app/src/main/java/com/dano/test1/CountriesSpinner.kt
Normal file
@ -0,0 +1,50 @@
|
||||
package com.dano.test1
|
||||
|
||||
object Countries {
|
||||
fun getAllCountries(languageID: String): List<String> {
|
||||
val prompt = LanguageManager.getText(languageID, "choose_answer")
|
||||
val countries = listOf(
|
||||
"Afghanistan", "Albanien", "Algerien", "Andorra", "Angola",
|
||||
"Antigua und Barbuda", "Argentinien", "Armenien", "Australien",
|
||||
"Österreich", "Aserbaidschan", "Bahamas", "Bahrain", "Bangladesch",
|
||||
"Barbados", "Belgien", "Belize", "Benin", "Bhutan", "Bolivien",
|
||||
"Bosnien und Herzegowina", "Botswana", "Brasilien", "Brunei",
|
||||
"Bulgarien", "Burkina Faso", "Burundi", "Cabo Verde", "Cambodia",
|
||||
"Kamerun", "Kanada", "Zentralafrikanische Republik", "Tschad",
|
||||
"Chile", "China", "Kolumbien", "Komoren", "Kongo (Dem. Rep.)",
|
||||
"Kongo (Rep.)", "Costa Rica", "Kroatien", "Kuba", "Zypern",
|
||||
"Tschechien", "Dänemark", "Djibouti", "Dominica",
|
||||
"Dominikanische Republik", "Ecuador", "Ägypten", "El Salvador",
|
||||
"Äquatorialguinea", "Eritrea", "Estland", "Eswatini", "Äthiopien",
|
||||
"Fiji", "Finnland", "Frankreich", "Gabon", "Gambia", "Georgien",
|
||||
"Deutschland", "Ghana", "Griechenland", "Grenada", "Guatemala",
|
||||
"Guinea", "Guinea-Bissau", "Guyana", "Haiti", "Honduras", "Ungarn",
|
||||
"Island", "Indien", "Indonesien", "Irak", "Iran", "Irland",
|
||||
"Israel", "Italien", "Jamaika", "Japan", "Jordanien", "Kasachstan",
|
||||
"Kenia", "Kiribati", "Korea (Nord)", "Korea (Süd)", "Kuwait",
|
||||
"Kyrgyzstan", "Laos", "Lesotho", "Latvia", "Lebanon", "Liberia",
|
||||
"Libyen", "Liechtenstein", "Litauen", "Luxemburg", "Madagaskar",
|
||||
"Malawi", "Malaysia", "Maldiven", "Mali", "Malta", "Marokko",
|
||||
"Marshallinseln", "Mauritius", "Mauritanien", "Mexiko", "Mikronesien",
|
||||
"Moldawien", "Monaco", "Mongolei", "Montenegro", "Mozambique",
|
||||
"Namibia", "Nauru", "Nepal", "Nicaragua", "Niger", "Nigeria",
|
||||
"Nordmazedonien", "Norwegen", "Oman", "Pakistan", "Palau", "Panama",
|
||||
"Papua-Neuguinea", "Paraguay", "Peru", "Philippinen", "Polen",
|
||||
"Portugal", "Ruanda", "Rumänien", "Russland", "Salomonen", "Sambia",
|
||||
"Samoa", "San Marino", "Sao Tome und Principe", "Saudi-Arabien",
|
||||
"Senegal", "Serbien", "Seychellen", "Sierra Leone", "Singapur",
|
||||
"Slowakei", "Slowenien", "Solomonen", "Südafrika", "Südkorea",
|
||||
"Südsudan", "Spanien", "Sri Lanka", "St. Kitts und Nevis",
|
||||
"St. Lucia", "St. Vincent und die Grenadinen", "Sudan", "Suriname",
|
||||
"Syrien", "São Tomé und Príncipe", "Schweden", "Schweiz", "Simbabwe",
|
||||
"Tadschikistan", "Taiwan", "Tansania", "Togo", "Trinidad und Tobago",
|
||||
"Tonga", "Tunisien", "Türkei", "Turkmenistan", "Tuvalu", "Uganda",
|
||||
"Ukraine", "Uruguay", "Usbekistan", "Vanuatu", "Venezuela",
|
||||
"Vereinigte Arabische Emirate", "Vereinigte Staaten",
|
||||
"Vereinigtes Königreich", "Vietnam", "Wallis und Futuna",
|
||||
"Westjordanland", "Westsahara", "Yemen", "Zentralafrika", "Zypern",
|
||||
"Zimbabwe"
|
||||
)
|
||||
return listOf(prompt) + countries
|
||||
}
|
||||
}
|
||||
88
app/src/main/java/com/dano/test1/Daos.kt
Normal file
88
app/src/main/java/com/dano/test1/Daos.kt
Normal file
@ -0,0 +1,88 @@
|
||||
package com.dano.test1.data
|
||||
|
||||
import androidx.room.*
|
||||
|
||||
@Dao
|
||||
interface ClientDao {
|
||||
@Insert(onConflict = OnConflictStrategy.IGNORE)
|
||||
suspend fun insertClient(client: Client)
|
||||
|
||||
@Query("SELECT * FROM clients WHERE clientCode = :code LIMIT 1")
|
||||
suspend fun getClientByCode(code: String): Client?
|
||||
|
||||
//@Query("SELECT * FROM clients")
|
||||
//suspend fun getAllClients(): List<Client>
|
||||
|
||||
@Delete
|
||||
suspend fun deleteClient(client: Client)
|
||||
}
|
||||
|
||||
@Dao
|
||||
interface QuestionnaireDao {
|
||||
@Insert(onConflict = OnConflictStrategy.IGNORE)
|
||||
suspend fun insertQuestionnaire(questionnaire: Questionnaire)
|
||||
|
||||
@Query("SELECT * FROM questionnaires WHERE id = :id LIMIT 1")
|
||||
suspend fun getById(id: String): Questionnaire?
|
||||
|
||||
//@Query("SELECT * FROM questionnaires")
|
||||
//suspend fun getAllQuestionnaires(): List<Questionnaire>
|
||||
}
|
||||
|
||||
|
||||
@Dao
|
||||
interface QuestionDao {
|
||||
@Insert(onConflict = OnConflictStrategy.IGNORE)
|
||||
suspend fun insertQuestions(questions: List<Question>)
|
||||
|
||||
@Insert(onConflict = OnConflictStrategy.IGNORE)
|
||||
suspend fun insertQuestion(question: Question)
|
||||
|
||||
@Query("SELECT * FROM questions WHERE questionId = :id LIMIT 1")
|
||||
suspend fun getById(id: String): Question?
|
||||
|
||||
@Query("SELECT * FROM questions WHERE questionnaireId = :questionnaireId")
|
||||
suspend fun getQuestionsForQuestionnaire(questionnaireId: String): List<Question>
|
||||
|
||||
//@Query("SELECT * FROM questions")
|
||||
//suspend fun getAllQuestions(): List<Question> // <-- HIER NEU
|
||||
}
|
||||
|
||||
|
||||
|
||||
@Dao
|
||||
interface AnswerDao {
|
||||
@Insert(onConflict = OnConflictStrategy.REPLACE)
|
||||
suspend fun insertAnswers(answers: List<Answer>)
|
||||
|
||||
@Query("""
|
||||
SELECT a.* FROM answers a
|
||||
INNER JOIN questions q ON a.questionId = q.questionId
|
||||
WHERE a.clientCode = :clientCode AND q.questionnaireId = :questionnaireId
|
||||
""")
|
||||
suspend fun getAnswersForClientAndQuestionnaire(
|
||||
clientCode: String,
|
||||
questionnaireId: String
|
||||
): List<Answer>
|
||||
|
||||
@Query("SELECT * FROM answers WHERE clientCode = :clientCode")
|
||||
suspend fun getAnswersForClient(clientCode: String): List<Answer>
|
||||
|
||||
}
|
||||
|
||||
@Dao
|
||||
interface CompletedQuestionnaireDao {
|
||||
@Insert(onConflict = OnConflictStrategy.REPLACE)
|
||||
suspend fun insert(entry: CompletedQuestionnaire)
|
||||
|
||||
@Query("SELECT * FROM completed_questionnaires WHERE clientCode = :clientCode AND questionnaireId = :questionnaireId LIMIT 1")
|
||||
suspend fun getStatus(clientCode: String, questionnaireId: String): CompletedQuestionnaire?
|
||||
|
||||
@Query("SELECT * FROM completed_questionnaires WHERE clientCode = :clientCode")
|
||||
suspend fun getAllForClient(clientCode: String): List<CompletedQuestionnaire>
|
||||
|
||||
@Query("SELECT questionnaireId FROM completed_questionnaires WHERE clientCode = :clientCode")
|
||||
suspend fun getCompletedQuestionnairesForClient(clientCode: String): List<String>
|
||||
}
|
||||
|
||||
|
||||
84
app/src/main/java/com/dano/test1/Entities.kt
Normal file
84
app/src/main/java/com/dano/test1/Entities.kt
Normal file
@ -0,0 +1,84 @@
|
||||
package com.dano.test1.data
|
||||
|
||||
import androidx.room.*
|
||||
|
||||
@Entity(tableName = "clients")
|
||||
data class Client(
|
||||
@PrimaryKey val clientCode: String,
|
||||
)
|
||||
|
||||
@Entity(tableName = "questionnaires")
|
||||
data class Questionnaire(
|
||||
@PrimaryKey val id: String,
|
||||
)
|
||||
|
||||
@Entity(
|
||||
tableName = "questions",
|
||||
foreignKeys = [
|
||||
ForeignKey(
|
||||
entity = Questionnaire::class,
|
||||
parentColumns = ["id"],
|
||||
childColumns = ["questionnaireId"],
|
||||
onDelete = ForeignKey.CASCADE
|
||||
)
|
||||
],
|
||||
indices = [Index("questionnaireId")]
|
||||
)
|
||||
data class Question(
|
||||
@PrimaryKey val questionId: String,
|
||||
val questionnaireId: String,
|
||||
val question: String = ""
|
||||
)
|
||||
|
||||
@Entity(
|
||||
tableName = "answers",
|
||||
primaryKeys = ["clientCode", "questionId"],
|
||||
foreignKeys = [
|
||||
ForeignKey(
|
||||
entity = Client::class,
|
||||
parentColumns = ["clientCode"],
|
||||
childColumns = ["clientCode"],
|
||||
onDelete = ForeignKey.CASCADE
|
||||
),
|
||||
ForeignKey(
|
||||
entity = Question::class,
|
||||
parentColumns = ["questionId"],
|
||||
childColumns = ["questionId"],
|
||||
onDelete = ForeignKey.CASCADE
|
||||
)
|
||||
],
|
||||
indices = [Index("clientCode"), Index("questionId")]
|
||||
)
|
||||
data class Answer(
|
||||
val clientCode: String,
|
||||
val questionId: String,
|
||||
val answerValue: String = ""
|
||||
)
|
||||
|
||||
@Entity(
|
||||
tableName = "completed_questionnaires",
|
||||
primaryKeys = ["clientCode", "questionnaireId"],
|
||||
foreignKeys = [
|
||||
ForeignKey(
|
||||
entity = Client::class,
|
||||
parentColumns = ["clientCode"],
|
||||
childColumns = ["clientCode"],
|
||||
onDelete = ForeignKey.CASCADE
|
||||
),
|
||||
ForeignKey(
|
||||
entity = Questionnaire::class,
|
||||
parentColumns = ["id"],
|
||||
childColumns = ["questionnaireId"],
|
||||
onDelete = ForeignKey.CASCADE
|
||||
)
|
||||
],
|
||||
indices = [Index("clientCode"), Index("questionnaireId")]
|
||||
)
|
||||
data class CompletedQuestionnaire(
|
||||
val clientCode: String,
|
||||
val questionnaireId: String,
|
||||
val timestamp: Long = System.currentTimeMillis(),
|
||||
val isDone: Boolean,
|
||||
val sumPoints: Int? = null
|
||||
)
|
||||
|
||||
116
app/src/main/java/com/dano/test1/HandlerClientCoachCode.kt
Normal file
116
app/src/main/java/com/dano/test1/HandlerClientCoachCode.kt
Normal file
@ -0,0 +1,116 @@
|
||||
package com.dano.test1
|
||||
|
||||
import android.view.View
|
||||
import android.widget.*
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.launch
|
||||
import kotlinx.coroutines.withContext
|
||||
|
||||
class HandlerClientCoachCode(
|
||||
private val answers: MutableMap<String, Any>,
|
||||
private val languageID: String,
|
||||
private val goToNextQuestion: () -> Unit,
|
||||
private val goToPreviousQuestion: () -> Unit,
|
||||
private val showToast: (String) -> Unit,
|
||||
) : QuestionHandler {
|
||||
|
||||
private lateinit var question: QuestionItem.ClientCoachCodeQuestion
|
||||
private lateinit var layout: View
|
||||
|
||||
override fun bind(layout: View, question: QuestionItem) {
|
||||
if (question !is QuestionItem.ClientCoachCodeQuestion) return
|
||||
|
||||
this.layout = layout
|
||||
this.question = question
|
||||
|
||||
// Bind UI components
|
||||
val clientCodeField = layout.findViewById<EditText>(R.id.client_code)
|
||||
val coachCodeField = layout.findViewById<EditText>(R.id.coach_code)
|
||||
val questionTextView = layout.findViewById<TextView>(R.id.question)
|
||||
|
||||
// Fill question text using language manager
|
||||
questionTextView.text = question.question?.let {
|
||||
LanguageManager.getText(languageID, it)
|
||||
} ?: ""
|
||||
|
||||
// Load last used client code if available
|
||||
val lastClientCode = GlobalValues.LAST_CLIENT_CODE
|
||||
if (!lastClientCode.isNullOrBlank()) {
|
||||
clientCodeField.setText(lastClientCode)
|
||||
clientCodeField.isEnabled = false
|
||||
} else {
|
||||
clientCodeField.setText(answers["client_code"] as? String ?: "")
|
||||
clientCodeField.isEnabled = true
|
||||
}
|
||||
|
||||
// Load saved coach code
|
||||
coachCodeField.setText(answers["coach_code"] as? String ?: "")
|
||||
|
||||
// Set click listener for Next button
|
||||
layout.findViewById<Button>(R.id.Qnext).setOnClickListener {
|
||||
onNextClicked(clientCodeField, coachCodeField)
|
||||
}
|
||||
|
||||
// Set click listener for Previous button
|
||||
layout.findViewById<Button>(R.id.Qprev).setOnClickListener {
|
||||
onPreviousClicked(clientCodeField, coachCodeField)
|
||||
}
|
||||
}
|
||||
|
||||
// Handle Next button click
|
||||
private fun onNextClicked(clientCodeField: EditText, coachCodeField: EditText) {
|
||||
if (!validate()) {
|
||||
val message = LanguageManager.getText(languageID, "fill_both_fields")
|
||||
showToast(message)
|
||||
return
|
||||
}
|
||||
|
||||
val clientCode = clientCodeField.text.toString()
|
||||
val coachCode = coachCodeField.text.toString()
|
||||
|
||||
// Check if client code already exists asynchronously
|
||||
CoroutineScope(Dispatchers.IO).launch {
|
||||
val existingClient = MyApp.database.clientDao().getClientByCode(clientCode)
|
||||
|
||||
withContext(Dispatchers.Main) {
|
||||
if (existingClient != null && clientCodeField.isEnabled) {
|
||||
// Client code already exists and field was editable
|
||||
val message = LanguageManager.getText(languageID, "client_code_exists")
|
||||
showToast(message)
|
||||
} else {
|
||||
// Either no existing client or re-using previous code
|
||||
saveAnswers(clientCode, coachCode)
|
||||
goToNextQuestion()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Handle Previous button click
|
||||
private fun onPreviousClicked(clientCodeField: EditText, coachCodeField: EditText) {
|
||||
val clientCode = clientCodeField.text.toString()
|
||||
val coachCode = coachCodeField.text.toString()
|
||||
saveAnswers(clientCode, coachCode)
|
||||
goToPreviousQuestion()
|
||||
}
|
||||
|
||||
// Validate that both fields are filled
|
||||
override fun validate(): Boolean {
|
||||
val clientCode = layout.findViewById<EditText>(R.id.client_code).text
|
||||
val coachCode = layout.findViewById<EditText>(R.id.coach_code).text
|
||||
return clientCode.isNotBlank() && coachCode.isNotBlank()
|
||||
}
|
||||
|
||||
// Save answers to shared state and global value
|
||||
private fun saveAnswers(clientCode: String, coachCode: String) {
|
||||
GlobalValues.LAST_CLIENT_CODE = clientCode
|
||||
answers["client_code"] = clientCode
|
||||
answers["coach_code"] = coachCode
|
||||
}
|
||||
|
||||
// Required override but not used here
|
||||
override fun saveAnswer() {
|
||||
// Not used
|
||||
}
|
||||
}
|
||||
80
app/src/main/java/com/dano/test1/HandlerClientNotSigned.kt
Normal file
80
app/src/main/java/com/dano/test1/HandlerClientNotSigned.kt
Normal file
@ -0,0 +1,80 @@
|
||||
package com.dano.test1
|
||||
|
||||
import android.view.View
|
||||
import android.widget.*
|
||||
|
||||
class HandlerClientNotSigned(
|
||||
private val answers: MutableMap<String, Any>,
|
||||
private val languageID: String,
|
||||
private val goToNextQuestion: () -> Unit,
|
||||
private val goToPreviousQuestion: () -> Unit,
|
||||
private val showToast: (String) -> Unit
|
||||
) : QuestionHandler {
|
||||
|
||||
private lateinit var layout: View
|
||||
private lateinit var question: QuestionItem.ClientNotSigned
|
||||
|
||||
// UI components
|
||||
private lateinit var textView1: TextView
|
||||
private lateinit var textView2: TextView
|
||||
private lateinit var questionTextView: TextView
|
||||
private lateinit var coachCodeField: EditText
|
||||
|
||||
override fun bind(layout: View, question: QuestionItem) {
|
||||
if (question !is QuestionItem.ClientNotSigned) return
|
||||
|
||||
this.layout = layout
|
||||
this.question = question
|
||||
|
||||
// Initialize UI components only once
|
||||
initViews()
|
||||
|
||||
// Set localized text values from LanguageManager
|
||||
textView1.text = question.textKey1?.let { LanguageManager.getText(languageID, it) } ?: ""
|
||||
textView2.text = question.textKey2?.let { LanguageManager.getText(languageID, it) } ?: ""
|
||||
questionTextView.text = question.question?.let { LanguageManager.getText(languageID, it) } ?: ""
|
||||
|
||||
// Populate EditText with previous value if exists
|
||||
coachCodeField.setText(answers[question.id] as? String ?: "")
|
||||
|
||||
// Set click listener for Next button
|
||||
layout.findViewById<Button>(R.id.Qnext).setOnClickListener {
|
||||
onNextClicked()
|
||||
}
|
||||
|
||||
// Set click listener for Previous button
|
||||
layout.findViewById<Button>(R.id.Qprev).setOnClickListener {
|
||||
goToPreviousQuestion()
|
||||
}
|
||||
}
|
||||
|
||||
// Initialize all views once to avoid repeated findViewById calls
|
||||
private fun initViews() {
|
||||
textView1 = layout.findViewById(R.id.textView1)
|
||||
textView2 = layout.findViewById(R.id.textView2)
|
||||
questionTextView = layout.findViewById(R.id.question)
|
||||
coachCodeField = layout.findViewById(R.id.coach_code)
|
||||
}
|
||||
|
||||
// Handle Next button click
|
||||
private fun onNextClicked() {
|
||||
if (validate()) {
|
||||
saveAnswer()
|
||||
goToNextQuestion()
|
||||
} else {
|
||||
val message = LanguageManager.getText(languageID, "enter_coach_code")
|
||||
showToast(message)
|
||||
}
|
||||
}
|
||||
|
||||
// Validate that coach code field is not empty
|
||||
override fun validate(): Boolean {
|
||||
val coachCode = coachCodeField.text
|
||||
return coachCode.isNotBlank()
|
||||
}
|
||||
|
||||
// Save entered coach code to answers map
|
||||
override fun saveAnswer() {
|
||||
answers[question.id] = coachCodeField.text.toString()
|
||||
}
|
||||
}
|
||||
154
app/src/main/java/com/dano/test1/HandlerDateSpinner.kt
Normal file
154
app/src/main/java/com/dano/test1/HandlerDateSpinner.kt
Normal file
@ -0,0 +1,154 @@
|
||||
package com.dano.test1
|
||||
|
||||
import android.content.Context
|
||||
import android.view.View
|
||||
import android.widget.*
|
||||
import java.text.SimpleDateFormat
|
||||
import java.util.*
|
||||
|
||||
class HandlerDateSpinner(
|
||||
private val context: Context,
|
||||
private val answers: MutableMap<String, Any>,
|
||||
private val languageID: String,
|
||||
private val goToNextQuestion: () -> Unit,
|
||||
private val goToPreviousQuestion: () -> Unit,
|
||||
private val showToast: (String) -> Unit
|
||||
) : QuestionHandler {
|
||||
|
||||
private lateinit var question: QuestionItem.DateSpinnerQuestion
|
||||
private lateinit var layout: View
|
||||
|
||||
private lateinit var spinnerDay: Spinner
|
||||
private lateinit var spinnerMonth: Spinner
|
||||
private lateinit var spinnerYear: Spinner
|
||||
|
||||
override fun bind(layout: View, question: QuestionItem) {
|
||||
if (question !is QuestionItem.DateSpinnerQuestion) return
|
||||
|
||||
this.layout = layout
|
||||
this.question = question
|
||||
|
||||
initViews()
|
||||
|
||||
val questionTextView = layout.findViewById<TextView>(R.id.question)
|
||||
val textView = layout.findViewById<TextView>(R.id.textView)
|
||||
|
||||
questionTextView.text = question.question?.let { LanguageManager.getText(languageID, it) } ?: ""
|
||||
textView.text = question.textKey?.let { LanguageManager.getText(languageID, it) } ?: ""
|
||||
|
||||
val (savedYear, savedMonthIndex, savedDay) = question.question?.let {
|
||||
parseSavedDate(answers[it] as? String)
|
||||
} ?: Triple(null, null, null)
|
||||
|
||||
val days = (1..31).toList()
|
||||
val months = Months.getAllMonths(languageID)
|
||||
val years = (1900..MAX_VALUE_YEAR + 1).toList().reversed()
|
||||
|
||||
val today = Calendar.getInstance()
|
||||
val defaultDay = savedDay ?: today.get(Calendar.DAY_OF_MONTH)
|
||||
val defaultMonth = savedMonthIndex?.takeIf { it in months.indices }?.let { months[it] }
|
||||
?: months[today.get(Calendar.MONTH)]
|
||||
val defaultYear = savedYear ?: today.get(Calendar.YEAR)
|
||||
|
||||
setupSpinner(spinnerDay, days, defaultDay)
|
||||
setupSpinner(spinnerMonth, months, defaultMonth)
|
||||
setupSpinner(spinnerYear, years, defaultYear)
|
||||
|
||||
layout.findViewById<Button>(R.id.Qnext).setOnClickListener {
|
||||
if (validate()) {
|
||||
saveAnswer()
|
||||
goToNextQuestion()
|
||||
}
|
||||
}
|
||||
|
||||
layout.findViewById<Button>(R.id.Qprev).setOnClickListener {
|
||||
goToPreviousQuestion()
|
||||
}
|
||||
}
|
||||
|
||||
private fun initViews() {
|
||||
spinnerDay = layout.findViewById(R.id.spinner_value_day)
|
||||
spinnerMonth = layout.findViewById(R.id.spinner_value_month)
|
||||
spinnerYear = layout.findViewById(R.id.spinner_value_year)
|
||||
}
|
||||
|
||||
override fun validate(): Boolean {
|
||||
val day = spinnerDay.selectedItem.toString().padStart(2, '0')
|
||||
val month = spinnerMonth.selectedItem as Month
|
||||
val year = spinnerYear.selectedItem.toString()
|
||||
|
||||
val allMonths = Months.getAllMonths(languageID)
|
||||
val monthNumber = (allMonths.indexOf(month) + 1).toString().padStart(2, '0')
|
||||
val selectedDateString = "$year-$monthNumber-$day"
|
||||
val selectedDate = parseDate(selectedDateString)
|
||||
|
||||
question.constraints?.notBefore?.let { key ->
|
||||
val referenceDateString = answers[key] as? String
|
||||
val referenceDate = referenceDateString?.let { parseDate(it) }
|
||||
if (referenceDate != null && selectedDate.before(referenceDate)) {
|
||||
val message1 = LanguageManager.getText(languageID, "date_after")
|
||||
val message2 = LanguageManager.getText(languageID, "lay")
|
||||
showToast(message1 + " " + referenceDateString + " " + message2)
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
question.constraints?.notAfter?.let { key ->
|
||||
val referenceDate = when (key) {
|
||||
"today" -> Date()
|
||||
else -> (answers[key] as? String)?.let { parseDate(it) }
|
||||
}
|
||||
if (referenceDate != null && selectedDate.after(referenceDate)) {
|
||||
val message1 = LanguageManager.getText(languageID, "date_before")
|
||||
val message2 = LanguageManager.getText(languageID, "lay")
|
||||
showToast(message1 + " " + referenceDate + " " + message2)
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
override fun saveAnswer() {
|
||||
val day = spinnerDay.selectedItem.toString().padStart(2, '0')
|
||||
val month = spinnerMonth.selectedItem as Month
|
||||
val year = spinnerYear.selectedItem.toString()
|
||||
|
||||
val allMonths = Months.getAllMonths(languageID)
|
||||
val monthNumber = (allMonths.indexOf(month) + 1).toString().padStart(2, '0')
|
||||
|
||||
question.question?.let { key ->
|
||||
answers[key] = "$year-$monthNumber-$day"
|
||||
}
|
||||
}
|
||||
|
||||
private fun parseSavedDate(dateString: String?): Triple<Int?, Int?, Int?> {
|
||||
if (dateString.isNullOrBlank()) return Triple(null, null, null)
|
||||
val parts = dateString.split("-")
|
||||
if (parts.size != 3) return Triple(null, null, null)
|
||||
|
||||
val year = parts[0].toIntOrNull()
|
||||
val monthIndex = parts[1].toIntOrNull()?.minus(1)
|
||||
val day = parts[2].toIntOrNull()
|
||||
|
||||
return Triple(year, monthIndex, day)
|
||||
}
|
||||
|
||||
private fun parseDate(dateString: String): Date {
|
||||
val sdf = SimpleDateFormat("yyyy-MM-dd", Locale.getDefault())
|
||||
return sdf.parse(dateString)
|
||||
}
|
||||
|
||||
private fun <T> setupSpinner(spinner: Spinner, items: List<T>, defaultSelection: T?) {
|
||||
val adapter = ArrayAdapter(context, android.R.layout.simple_spinner_item, items)
|
||||
adapter.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item)
|
||||
spinner.adapter = adapter
|
||||
|
||||
defaultSelection?.let {
|
||||
val index = items.indexOf(it)
|
||||
if (index >= 0) {
|
||||
spinner.setSelection(index)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
167
app/src/main/java/com/dano/test1/HandlerGlassScaleQuestion.kt
Normal file
167
app/src/main/java/com/dano/test1/HandlerGlassScaleQuestion.kt
Normal file
@ -0,0 +1,167 @@
|
||||
package com.dano.test1
|
||||
|
||||
import android.content.Context
|
||||
import android.view.Gravity
|
||||
import android.view.View
|
||||
import android.widget.*
|
||||
|
||||
class HandlerGlassScaleQuestion(
|
||||
private val context: Context,
|
||||
private val answers: MutableMap<String, Any>,
|
||||
private val points: MutableList<Int>,
|
||||
private val languageID: String,
|
||||
private val goToNextQuestion: () -> Unit,
|
||||
private val goToPreviousQuestion: () -> Unit,
|
||||
private val showToast: (String) -> Unit
|
||||
) : QuestionHandler {
|
||||
|
||||
private lateinit var layout: View
|
||||
private lateinit var question: QuestionItem.GlassScaleQuestion
|
||||
|
||||
private val scaleLabels = listOf(
|
||||
"never_glass", "little_glass", "moderate_glass", "much_glass", "extreme_glass"
|
||||
)
|
||||
|
||||
private val pointsMap = mapOf(
|
||||
"never_glass" to 0,
|
||||
"little_glass" to 1,
|
||||
"moderate_glass" to 2,
|
||||
"much_glass" to 3,
|
||||
"extreme_glass" to 4
|
||||
)
|
||||
|
||||
override fun bind(layout: View, question: QuestionItem) {
|
||||
if (question !is QuestionItem.GlassScaleQuestion) return
|
||||
|
||||
this.layout = layout
|
||||
this.question = question
|
||||
|
||||
layout.findViewById<TextView>(R.id.textView).text =
|
||||
question.textKey?.let { LanguageManager.getText(languageID, it) } ?: ""
|
||||
|
||||
layout.findViewById<TextView>(R.id.question).text =
|
||||
question.question?.let { LanguageManager.getText(languageID, it) } ?: ""
|
||||
|
||||
val tableLayout = layout.findViewById<TableLayout>(R.id.glass_table)
|
||||
tableLayout.removeAllViews()
|
||||
|
||||
val headerRow = TableRow(context).apply {
|
||||
layoutParams = TableLayout.LayoutParams(
|
||||
TableLayout.LayoutParams.MATCH_PARENT,
|
||||
TableLayout.LayoutParams.WRAP_CONTENT
|
||||
)
|
||||
gravity = Gravity.CENTER
|
||||
}
|
||||
|
||||
val emptyCell = TextView(context).apply {
|
||||
layoutParams = TableRow.LayoutParams(0, TableRow.LayoutParams.WRAP_CONTENT, 4f)
|
||||
}
|
||||
headerRow.addView(emptyCell)
|
||||
|
||||
scaleLabels.forEach { labelKey ->
|
||||
val labelText = LanguageManager.getText(languageID, labelKey)
|
||||
val labelView = TextView(context).apply {
|
||||
text = labelText
|
||||
gravity = Gravity.START
|
||||
layoutParams = TableRow.LayoutParams(0, TableRow.LayoutParams.WRAP_CONTENT, 1f)
|
||||
}
|
||||
headerRow.addView(labelView)
|
||||
}
|
||||
tableLayout.addView(headerRow)
|
||||
|
||||
addSymptomRows(tableLayout)
|
||||
|
||||
layout.findViewById<Button>(R.id.Qnext).setOnClickListener {
|
||||
if (validate()) {
|
||||
saveAnswer()
|
||||
goToNextQuestion()
|
||||
} else {
|
||||
val message = LanguageManager.getText(languageID, "select_one_answer_per_row")
|
||||
showToast(message)
|
||||
}
|
||||
}
|
||||
|
||||
layout.findViewById<Button>(R.id.Qprev).setOnClickListener {
|
||||
goToPreviousQuestion()
|
||||
}
|
||||
}
|
||||
|
||||
private fun addSymptomRows(table: TableLayout) {
|
||||
question.symptoms.forEach { symptomKey ->
|
||||
val savedLabel = answers[symptomKey] as? String
|
||||
|
||||
val row = TableRow(context).apply {
|
||||
layoutParams = TableRow.LayoutParams(
|
||||
TableRow.LayoutParams.MATCH_PARENT,
|
||||
TableRow.LayoutParams.WRAP_CONTENT
|
||||
)
|
||||
}
|
||||
|
||||
val symptomText = TextView(context).apply {
|
||||
text = LanguageManager.getText(languageID, symptomKey)
|
||||
layoutParams = TableRow.LayoutParams(0, TableRow.LayoutParams.WRAP_CONTENT, 4f)
|
||||
setPadding(4, 16, 4, 16)
|
||||
}
|
||||
row.addView(symptomText)
|
||||
|
||||
val radioGroup = RadioGroup(context).apply {
|
||||
orientation = RadioGroup.HORIZONTAL
|
||||
layoutParams = TableRow.LayoutParams(0, TableRow.LayoutParams.WRAP_CONTENT, 5f)
|
||||
}
|
||||
|
||||
scaleLabels.forEach { labelKey ->
|
||||
val radioButton = RadioButton(context).apply {
|
||||
tag = labelKey
|
||||
id = View.generateViewId()
|
||||
isChecked = savedLabel == labelKey
|
||||
layoutParams =
|
||||
RadioGroup.LayoutParams(0, RadioGroup.LayoutParams.WRAP_CONTENT, 1f)
|
||||
gravity = Gravity.CENTER
|
||||
}
|
||||
radioGroup.addView(radioButton)
|
||||
}
|
||||
row.addView(radioGroup)
|
||||
table.addView(row)
|
||||
}
|
||||
}
|
||||
|
||||
override fun validate(): Boolean {
|
||||
val table = layout.findViewById<TableLayout>(R.id.glass_table)
|
||||
for (i in 1 until table.childCount) {
|
||||
val row = table.getChildAt(i) as TableRow
|
||||
val radioGroup = row.getChildAt(1) as RadioGroup
|
||||
if (radioGroup.checkedRadioButtonId == -1) {
|
||||
return false
|
||||
}
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
override fun saveAnswer() {
|
||||
// Vorherige Punkte dieser Frage entfernen
|
||||
question.symptoms.forEach {
|
||||
val previousLabel = answers[it] as? String
|
||||
val previousPoint = pointsMap[previousLabel]
|
||||
if (previousPoint != null) {
|
||||
points.remove(previousPoint)
|
||||
}
|
||||
}
|
||||
|
||||
val table = layout.findViewById<TableLayout>(R.id.glass_table)
|
||||
for (i in 1 until table.childCount) {
|
||||
val row = table.getChildAt(i) as TableRow
|
||||
val symptomKey = question.symptoms[i - 1]
|
||||
|
||||
val radioGroup = row.getChildAt(1) as RadioGroup
|
||||
val checkedId = radioGroup.checkedRadioButtonId
|
||||
if (checkedId != -1) {
|
||||
val radioButton = radioGroup.findViewById<RadioButton>(checkedId)
|
||||
val selectedLabel = radioButton.tag as String
|
||||
answers[symptomKey] = selectedLabel
|
||||
|
||||
val point = pointsMap[selectedLabel] ?: 0
|
||||
points.add(point)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
93
app/src/main/java/com/dano/test1/HandlerLastPage.kt
Normal file
93
app/src/main/java/com/dano/test1/HandlerLastPage.kt
Normal file
@ -0,0 +1,93 @@
|
||||
package com.dano.test1
|
||||
|
||||
import android.view.View
|
||||
import android.widget.*
|
||||
import android.text.Html
|
||||
import kotlinx.coroutines.*
|
||||
|
||||
class HandlerLastPage(
|
||||
private val answers: Map<String, Any>,
|
||||
private val languageID: String,
|
||||
private val goToNextQuestion: () -> Unit,
|
||||
private val goToPreviousQuestion: () -> Unit,
|
||||
private val saveAnswersToDatabase: suspend (Map<String, Any>) -> Unit
|
||||
) : QuestionHandler {
|
||||
|
||||
private lateinit var currentQuestion: QuestionItem.LastPage
|
||||
private lateinit var layout: View
|
||||
private val minLoadingTimeMs = 2000L // Minimum loading time in milliseconds (2 seconds)
|
||||
|
||||
override fun bind(layout: View, question: QuestionItem) {
|
||||
this.layout = layout
|
||||
currentQuestion = question as QuestionItem.LastPage
|
||||
|
||||
// Set localized text for the last page
|
||||
layout.findViewById<TextView>(R.id.textView).text =
|
||||
LanguageManager.getText(languageID, currentQuestion.textKey)
|
||||
|
||||
// Set question text with HTML formatting
|
||||
layout.findViewById<TextView>(R.id.question).text =
|
||||
Html.fromHtml(
|
||||
LanguageManager.getText(languageID, currentQuestion.question),
|
||||
Html.FROM_HTML_MODE_LEGACY
|
||||
)
|
||||
|
||||
// Setup previous button
|
||||
layout.findViewById<Button>(R.id.Qprev).setOnClickListener {
|
||||
goToPreviousQuestion()
|
||||
}
|
||||
|
||||
// Setup finish button
|
||||
layout.findViewById<Button>(R.id.Qfinish).setOnClickListener {
|
||||
showLoading(true) // Show loading indicator
|
||||
|
||||
// Save answers on a background thread
|
||||
CoroutineScope(Dispatchers.IO).launch {
|
||||
val startTime = System.currentTimeMillis()
|
||||
|
||||
// Save answers to database (suspend function)
|
||||
saveAnswersToDatabase(answers)
|
||||
|
||||
// Calculate total points and update global value
|
||||
GlobalValues.INTEGRATION_INDEX = sumPoints()
|
||||
|
||||
// Save last client code globally if available
|
||||
val clientCode = answers["client_code"] as? String
|
||||
if (clientCode != null) GlobalValues.LAST_CLIENT_CODE = clientCode
|
||||
|
||||
// Ensure loading animation runs at least 2 seconds
|
||||
val elapsedTime = System.currentTimeMillis() - startTime
|
||||
if (elapsedTime < minLoadingTimeMs) {
|
||||
delay(minLoadingTimeMs - elapsedTime)
|
||||
}
|
||||
|
||||
// Switch back to main thread to update UI
|
||||
withContext(Dispatchers.Main) {
|
||||
showLoading(false) // Hide loading indicator
|
||||
val activity = layout.context as? MainActivity
|
||||
activity?.finishQuestionnaire() ?: goToNextQuestion()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override fun validate(): Boolean = true // No validation needed on last page
|
||||
override fun saveAnswer() {} // No answers to save here
|
||||
|
||||
// Calculate the sum of all keys ending with "_points"
|
||||
private fun sumPoints(): Int =
|
||||
answers.filterKeys { it.endsWith("_points") }
|
||||
.values.mapNotNull { it as? Int }
|
||||
.sum()
|
||||
|
||||
// Show or hide a ProgressBar (loading spinner)
|
||||
private fun showLoading(show: Boolean) {
|
||||
val progressBar = layout.findViewById<ProgressBar>(R.id.progressBar)
|
||||
val finishButton = layout.findViewById<Button>(R.id.Qfinish)
|
||||
val prevButton = layout.findViewById<Button>(R.id.Qprev)
|
||||
|
||||
progressBar?.visibility = if (show) View.VISIBLE else View.GONE
|
||||
finishButton?.isEnabled = !show
|
||||
prevButton?.isEnabled = !show
|
||||
}
|
||||
}
|
||||
104
app/src/main/java/com/dano/test1/HandlerMultiCheckboxQuestion.kt
Normal file
104
app/src/main/java/com/dano/test1/HandlerMultiCheckboxQuestion.kt
Normal file
@ -0,0 +1,104 @@
|
||||
package com.dano.test1
|
||||
|
||||
import android.content.Context
|
||||
import android.view.View
|
||||
import android.widget.*
|
||||
|
||||
class HandlerMultiCheckboxQuestion(
|
||||
private val context: Context,
|
||||
private val answers: MutableMap<String, Any>,
|
||||
private val points: MutableList<Int>,
|
||||
private val languageID: String,
|
||||
private val goToNextQuestion: () -> Unit,
|
||||
private val goToPreviousQuestion: () -> Unit,
|
||||
private val showToast: (String) -> Unit
|
||||
) : QuestionHandler {
|
||||
|
||||
private lateinit var layout: View
|
||||
private lateinit var question: QuestionItem.MultiCheckboxQuestion
|
||||
|
||||
override fun bind(layout: View, question: QuestionItem) {
|
||||
this.layout = layout
|
||||
this.question = question as QuestionItem.MultiCheckboxQuestion
|
||||
|
||||
val container = layout.findViewById<LinearLayout>(R.id.CheckboxContainer)
|
||||
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()
|
||||
|
||||
val selectedKeys = this.question.question?.let {
|
||||
(answers[it] as? List<*>)?.map { it.toString() }?.toSet()
|
||||
} ?: emptySet()
|
||||
|
||||
this.question.options.forEach { option ->
|
||||
val checkBox = CheckBox(context).apply {
|
||||
text = LanguageManager.getText(languageID, option.key)
|
||||
tag = option.key
|
||||
isChecked = selectedKeys.contains(option.key)
|
||||
layoutParams = LinearLayout.LayoutParams(
|
||||
LinearLayout.LayoutParams.MATCH_PARENT,
|
||||
LinearLayout.LayoutParams.WRAP_CONTENT
|
||||
).apply {
|
||||
val scale = context.resources.displayMetrics.density
|
||||
val marginBottom = (16 * scale + 0.5f).toInt()
|
||||
setMargins(0, 0, 0, marginBottom)
|
||||
}
|
||||
}
|
||||
container.addView(checkBox)
|
||||
}
|
||||
|
||||
layout.findViewById<Button>(R.id.Qnext).setOnClickListener {
|
||||
if (validate()) {
|
||||
saveAnswer()
|
||||
goToNextQuestion()
|
||||
} else {
|
||||
val msgKey = if (question.minSelection == 1) {
|
||||
"select_at_least_one_answer"
|
||||
} else {
|
||||
"select_at_least_minimum"
|
||||
}
|
||||
val errorMessage = LanguageManager.getTextFormatted(languageID, msgKey, "choose_more_elements")
|
||||
showToast(errorMessage)
|
||||
}
|
||||
}
|
||||
|
||||
layout.findViewById<Button>(R.id.Qprev).setOnClickListener {
|
||||
goToPreviousQuestion()
|
||||
}
|
||||
}
|
||||
|
||||
override fun validate(): Boolean {
|
||||
val container = layout.findViewById<LinearLayout>(R.id.CheckboxContainer)
|
||||
var selectedCount = 0
|
||||
for (i in 0 until container.childCount) {
|
||||
val checkBox = container.getChildAt(i) as? CheckBox ?: continue
|
||||
if (checkBox.isChecked) selectedCount++
|
||||
}
|
||||
return selectedCount >= question.minSelection
|
||||
}
|
||||
|
||||
override fun saveAnswer() {
|
||||
val container = layout.findViewById<LinearLayout>(R.id.CheckboxContainer)
|
||||
val selectedKeys = mutableListOf<String>()
|
||||
var totalPoints = 0
|
||||
|
||||
for (i in 0 until container.childCount) {
|
||||
val checkBox = container.getChildAt(i) as? CheckBox ?: continue
|
||||
if (checkBox.isChecked) {
|
||||
val key = checkBox.tag.toString()
|
||||
selectedKeys.add(key)
|
||||
totalPoints += question.pointsMap?.get(key) ?: 0
|
||||
}
|
||||
}
|
||||
|
||||
question.question?.let { questionKey ->
|
||||
answers[questionKey] = selectedKeys
|
||||
points.add(totalPoints)
|
||||
}
|
||||
}
|
||||
}
|
||||
513
app/src/main/java/com/dano/test1/HandlerOpeningScreen.kt
Normal file
513
app/src/main/java/com/dano/test1/HandlerOpeningScreen.kt
Normal file
@ -0,0 +1,513 @@
|
||||
package com.dano.test1
|
||||
|
||||
import android.content.Context
|
||||
import android.graphics.Canvas
|
||||
import android.graphics.Paint
|
||||
import android.graphics.Color
|
||||
import android.graphics.pdf.PdfDocument
|
||||
import android.view.View
|
||||
import android.widget.*
|
||||
import kotlinx.coroutines.*
|
||||
import org.json.JSONArray
|
||||
import android.util.Log
|
||||
import java.util.Calendar
|
||||
|
||||
// Global constants and values
|
||||
var INTEGRATION_INDEX_POINTS: Int? = null
|
||||
|
||||
class HandlerOpeningScreen(private val activity: MainActivity) {
|
||||
|
||||
private var languageID: String = "GERMAN"
|
||||
private lateinit var editText: EditText
|
||||
private lateinit var spinner: Spinner
|
||||
private lateinit var textView: TextView
|
||||
private lateinit var buttonContainer: LinearLayout
|
||||
private lateinit var buttonLoad: Button
|
||||
|
||||
private val dynamicButtons = mutableListOf<Button>()
|
||||
private val questionnaireFiles = mutableMapOf<Button, String>()
|
||||
private val buttonPoints: MutableMap<String, Int> = mutableMapOf()
|
||||
private var questionnaireOrder: List<String> = emptyList()
|
||||
private var questionnaireEntries: List<QuestionItem.QuestionnaireEntry> = emptyList()
|
||||
|
||||
fun init() {
|
||||
activity.setContentView(R.layout.opening_screen)
|
||||
|
||||
bindViews()
|
||||
loadQuestionnaireOrder()
|
||||
createQuestionnaireButtons()
|
||||
restorePreviousClientCode()
|
||||
|
||||
setupLanguageSpinner()
|
||||
setupLoadButton()
|
||||
|
||||
if (!editText.text.isNullOrBlank()) {
|
||||
buttonLoad.performClick()
|
||||
}
|
||||
}
|
||||
|
||||
private fun bindViews() {
|
||||
editText = activity.findViewById(R.id.editText)
|
||||
spinner = activity.findViewById(R.id.string_spinner1)
|
||||
textView = activity.findViewById(R.id.textView)
|
||||
buttonContainer = activity.findViewById(R.id.buttonContainer)
|
||||
buttonLoad = activity.findViewById(R.id.loadButton)
|
||||
|
||||
val tag = editText.tag as? String ?: ""
|
||||
editText.hint = LanguageManager.getText(languageID, tag)
|
||||
textView.text = LanguageManager.getText(languageID, "example_text")
|
||||
}
|
||||
|
||||
private fun loadQuestionnaireOrder() {
|
||||
try {
|
||||
val inputStream = activity.assets.open("questionnaire_order.json")
|
||||
val json = inputStream.bufferedReader().use { it.readText() }
|
||||
val jsonArray = JSONArray(json)
|
||||
|
||||
questionnaireEntries = (0 until jsonArray.length()).map { i ->
|
||||
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
|
||||
QuestionItem.QuestionnaireEntry(file, condition)
|
||||
}
|
||||
} catch (e: Exception) {
|
||||
e.printStackTrace()
|
||||
questionnaireEntries = emptyList()
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private fun createQuestionnaireButtons() {
|
||||
buttonContainer.removeAllViews()
|
||||
dynamicButtons.clear()
|
||||
questionnaireFiles.clear()
|
||||
|
||||
for ((index, entry) in questionnaireEntries.withIndex()) {
|
||||
val button = Button(activity).apply {
|
||||
layoutParams = LinearLayout.LayoutParams(
|
||||
LinearLayout.LayoutParams.MATCH_PARENT,
|
||||
LinearLayout.LayoutParams.WRAP_CONTENT
|
||||
).apply { setMargins(0, 8, 0, 8) }
|
||||
text = "Questionnaire ${index + 1}"
|
||||
id = View.generateViewId()
|
||||
}
|
||||
buttonContainer.addView(button)
|
||||
dynamicButtons.add(button)
|
||||
questionnaireFiles[button] = entry.file
|
||||
}
|
||||
|
||||
updateButtonTexts()
|
||||
setButtonsEnabled(listOf(dynamicButtons.firstOrNull()).filterNotNull())
|
||||
|
||||
dynamicButtons.forEach { button ->
|
||||
button.setOnClickListener {
|
||||
startQuestionnaireForButton(button)
|
||||
setButtonsEnabled(dynamicButtons.filter { it != button })
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun restorePreviousClientCode() {
|
||||
GlobalValues.LAST_CLIENT_CODE?.let { editText.setText(it) }
|
||||
}
|
||||
|
||||
private fun setupLanguageSpinner() {
|
||||
val languages = listOf("GERMAN", "ENGLISH", "FRENCH", "ROMANIAN", "ARABIC", "POLISH", "TURKISH", "UKRAINIAN", "RUSSIAN", "SPANISH")
|
||||
val adapter = ArrayAdapter(activity, android.R.layout.simple_spinner_item, languages).apply {
|
||||
setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item)
|
||||
}
|
||||
spinner.adapter = adapter
|
||||
spinner.setSelection(languages.indexOf(languageID))
|
||||
|
||||
spinner.onItemSelectedListener = object : AdapterView.OnItemSelectedListener {
|
||||
override fun onItemSelected(parent: AdapterView<*>, view: View?, position: Int, id: Long) {
|
||||
languageID = languages[position]
|
||||
updateButtonTexts()
|
||||
val hintTag = editText.tag as? String ?: ""
|
||||
editText.hint = LanguageManager.getText(languageID, hintTag)
|
||||
}
|
||||
|
||||
override fun onNothingSelected(parent: AdapterView<*>) {}
|
||||
}
|
||||
}
|
||||
|
||||
private fun setupLoadButton() {
|
||||
buttonLoad.text = LanguageManager.getText(languageID, "load")
|
||||
buttonLoad.setOnClickListener { handleLoadButton() }
|
||||
}
|
||||
|
||||
private fun handleLoadButton() {
|
||||
buttonPoints.clear()
|
||||
updateButtonTexts()
|
||||
setButtonsEnabled(emptyList())
|
||||
|
||||
val inputText = editText.text.toString().trim()
|
||||
if (inputText.isBlank()) {
|
||||
val message = LanguageManager.getText(languageID, "please_client_code")
|
||||
Toast.makeText(activity, message, Toast.LENGTH_SHORT).show()
|
||||
return
|
||||
}
|
||||
|
||||
val isDatabaseView = inputText.endsWith("_database")
|
||||
val clientCode = if (isDatabaseView) inputText.removeSuffix("_database") else inputText
|
||||
GlobalValues.LAST_CLIENT_CODE = clientCode
|
||||
|
||||
CoroutineScope(Dispatchers.IO).launch {
|
||||
val client = MyApp.database.clientDao().getClientByCode(clientCode)
|
||||
if (client == null) {
|
||||
withContext(Dispatchers.Main) {
|
||||
val message = LanguageManager.getText(languageID, "no_profile")
|
||||
Toast.makeText(activity, message, Toast.LENGTH_LONG).show()
|
||||
setButtonsEnabled(listOf(dynamicButtons.firstOrNull()).filterNotNull())
|
||||
}
|
||||
return@launch
|
||||
}
|
||||
|
||||
// Profil ist gültig → entweder normal laden oder PDF erzeugen
|
||||
withContext(Dispatchers.Main) {
|
||||
if (isDatabaseView) {
|
||||
// Profil laden + PDF erzeugen
|
||||
handleNormalLoad(clientCode) // ← Option: Zeige auch Punktefarben etc.
|
||||
showCompletedQuestionnaires(clientCode)
|
||||
} else {
|
||||
handleNormalLoad(clientCode)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
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)
|
||||
}
|
||||
|
||||
buttonPoints.clear()
|
||||
for (entry in completedEntries) {
|
||||
if (entry.isDone && (entry.sumPoints ?: 0) > 0) {
|
||||
buttonPoints[entry.questionnaireId] = entry.sumPoints ?: 0
|
||||
|
||||
if (entry.questionnaireId.contains("questionnaire_3_integration_index", ignoreCase = true)) {
|
||||
INTEGRATION_INDEX_POINTS = entry.sumPoints
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
updateButtonTexts()
|
||||
|
||||
val rhsPoints = buttonPoints.entries.firstOrNull { it.key.contains("questionnaire_2_rhs", ignoreCase = true) }?.value
|
||||
|
||||
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 // fallback: zeige Fragebogen
|
||||
}
|
||||
|
||||
if (conditionMet) break // Bedingung erfüllt → anzeigen
|
||||
else nextIndex++ // überspringen
|
||||
} else {
|
||||
break // keine Bedingung → anzeigen
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
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))
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
|
||||
private fun resetToStartState() {
|
||||
GlobalValues.LAST_CLIENT_CODE = null
|
||||
editText.setText("")
|
||||
setButtonsEnabled(listOf(dynamicButtons.firstOrNull()).filterNotNull())
|
||||
}
|
||||
|
||||
private fun updateButtonTexts() {
|
||||
questionnaireFiles.forEach { (button, fileName) ->
|
||||
val key = fileName.substringAfter("questionnaire_").substringAfter("_").removeSuffix(".json")
|
||||
var buttonText = LanguageManager.getText(languageID, key)
|
||||
|
||||
val matchedEntry = buttonPoints.entries.firstOrNull { fileName.contains(it.key, ignoreCase = true) }
|
||||
val points = matchedEntry?.value ?: 0
|
||||
if (points > 0) {
|
||||
buttonText += " (${points} P)"
|
||||
}
|
||||
button.text = buttonText
|
||||
|
||||
// Farbgebung je nach Punktzahl
|
||||
when {
|
||||
points in 1..12 -> button.setBackgroundColor(Color.parseColor("#4CAF50")) // Grün
|
||||
points in 13..36 -> button.setBackgroundColor(Color.parseColor("#FFEB3B")) // Gelb
|
||||
points in 37..100 -> button.setBackgroundColor(Color.parseColor("#F44336")) // Rot
|
||||
else -> button.setBackgroundColor(Color.parseColor("#E0E0E0")) // Standardgrau bei 0
|
||||
}
|
||||
}
|
||||
|
||||
buttonLoad.text = LanguageManager.getText(languageID, "load")
|
||||
}
|
||||
|
||||
private fun setButtonsEnabled(enabledButtons: List<Button>) {
|
||||
questionnaireFiles.keys.forEach { button ->
|
||||
val fileName = questionnaireFiles[button] ?: ""
|
||||
val shouldDisable = fileName.contains("questionnaire_5_final_interview.json", ignoreCase = true) &&
|
||||
buttonPoints.entries.firstOrNull { it.key.contains("questionnaire_2_rhs", ignoreCase = true) }?.value in 13..36
|
||||
|
||||
button.isEnabled = enabledButtons.contains(button) && !shouldDisable
|
||||
button.alpha = if (button.isEnabled) 1.0f else 0.5f
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private fun startQuestionnaireForButton(button: Button) {
|
||||
val fileName = questionnaireFiles[button] ?: return
|
||||
val questionnaire = QuestionnaireGeneric(fileName)
|
||||
startQuestionnaire(questionnaire)
|
||||
}
|
||||
|
||||
private fun startQuestionnaire(questionnaire: QuestionnaireBase<*>) {
|
||||
activity.startQuestionnaire(questionnaire, languageID)
|
||||
}
|
||||
|
||||
fun onBackPressed(): Boolean = false
|
||||
|
||||
private fun showCompletedQuestionnaires(clientCode: String) {
|
||||
CoroutineScope(Dispatchers.IO).launch {
|
||||
val actualClientCode = clientCode.removeSuffix("_database")
|
||||
val completedEntries = MyApp.database.completedQuestionnaireDao().getAllForClient(actualClientCode)
|
||||
|
||||
Log.d("PDF_DEBUG", "Completed entries for client $actualClientCode:")
|
||||
for (entry in completedEntries) {
|
||||
Log.d("PDF_DEBUG", "Questionnaire ID: ${entry.questionnaireId}, Done: ${entry.isDone}, Points: ${entry.sumPoints}")
|
||||
}
|
||||
|
||||
// ===== PDF ERSTELLUNG =====
|
||||
val pdfDocument = PdfDocument()
|
||||
val pageWidth = 595
|
||||
val pageHeight = 842
|
||||
val paint = Paint().apply { textSize = 12f }
|
||||
|
||||
// ===== CSV AUFBAU =====
|
||||
val csvBuilder = StringBuilder()
|
||||
csvBuilder.appendLine("ClientCode,QuestionnaireID,IsDone,Points,Question,Answer")
|
||||
|
||||
for ((index, entry) in completedEntries.withIndex()) {
|
||||
val pageInfo = PdfDocument.PageInfo.Builder(pageWidth, pageHeight, index + 1).create()
|
||||
var page = pdfDocument.startPage(pageInfo)
|
||||
var canvas = page.canvas
|
||||
var yPosition = 40f
|
||||
|
||||
// Header PDF
|
||||
canvas.drawText("Client Code: $actualClientCode", 20f, yPosition, paint)
|
||||
yPosition += 20f
|
||||
canvas.drawText("Questionnaire: ${entry.questionnaireId}", 20f, yPosition, paint)
|
||||
yPosition += 20f
|
||||
canvas.drawText("Status: ${entry.isDone}", 20f, yPosition, paint)
|
||||
yPosition += 20f
|
||||
canvas.drawText("Points: ${entry.sumPoints ?: "N/A"}", 20f, yPosition, paint)
|
||||
yPosition += 30f
|
||||
|
||||
val answers = MyApp.database.answerDao().getAnswersForClientAndQuestionnaire(actualClientCode, entry.questionnaireId)
|
||||
|
||||
for (answer in answers) {
|
||||
val questionKey = answer.questionId.substringAfter("-")
|
||||
val questionText = LanguageManager.getText("ENGLISH", questionKey)
|
||||
val rawAnswerText = LanguageManager.getText("ENGLISH", answer.answerValue)
|
||||
println("Entry " + entry)
|
||||
println("Question " + questionKey)
|
||||
println("Answer " + answer.answerValue)
|
||||
//if (questionKey = "counsultation_decision" and answer.answerValue == "red")
|
||||
val answerText = rawAnswerText.trim().removePrefix("[").removeSuffix("]")
|
||||
|
||||
// PDF
|
||||
yPosition = drawMultilineText(canvas, "Question: $questionText", 20f, yPosition, paint, pageWidth - 40, isBold = true)
|
||||
yPosition += 8f
|
||||
yPosition = drawMultilineText(canvas, "Answer: $answerText", 20f, yPosition, paint, pageWidth - 40)
|
||||
yPosition += 20f
|
||||
|
||||
paint.strokeWidth = 0.5f
|
||||
canvas.drawLine(20f, yPosition - 30f, pageWidth - 20f, yPosition - 30f, paint)
|
||||
paint.strokeWidth = 0f
|
||||
|
||||
// CSV
|
||||
val sanitizedQuestion = questionText.replace(",", " ").replace("\n", " ")
|
||||
val sanitizedAnswer = answerText.replace(",", " ").replace("\n", " ")
|
||||
csvBuilder.appendLine("${actualClientCode},${entry.questionnaireId},${entry.isDone},${entry.sumPoints ?: ""},\"$sanitizedQuestion\",\"$sanitizedAnswer\"")
|
||||
|
||||
if (yPosition > pageHeight - 60) {
|
||||
pdfDocument.finishPage(page)
|
||||
val newPageInfo = PdfDocument.PageInfo.Builder(pageWidth, pageHeight, pdfDocument.pages.size + 1).create()
|
||||
page = pdfDocument.startPage(newPageInfo)
|
||||
canvas = page.canvas
|
||||
yPosition = 40f
|
||||
}
|
||||
}
|
||||
|
||||
pdfDocument.finishPage(page)
|
||||
}
|
||||
|
||||
// ==== CSV LOG AUSGABE ====
|
||||
Log.d("CSV_OUTPUT", "Generated CSV:\n${csvBuilder.toString()}")
|
||||
|
||||
val pdfFileName = "DatabaseOutput_${actualClientCode}.pdf"
|
||||
val csvFileName = "DatabaseOutput_${actualClientCode}.csv"
|
||||
val resolver = activity.contentResolver
|
||||
|
||||
// Bestehende Dateien löschen
|
||||
val deleteIfExists: (String) -> Unit = { name ->
|
||||
val projection = arrayOf(android.provider.MediaStore.MediaColumns._ID)
|
||||
val selection = "${android.provider.MediaStore.MediaColumns.DISPLAY_NAME} = ?"
|
||||
val selectionArgs = arrayOf(name)
|
||||
val query = resolver.query(android.provider.MediaStore.Downloads.EXTERNAL_CONTENT_URI, projection, selection, selectionArgs, null)
|
||||
query?.use { cursor ->
|
||||
if (cursor.moveToFirst()) {
|
||||
val idColumn = cursor.getColumnIndexOrThrow(android.provider.MediaStore.MediaColumns._ID)
|
||||
val id = cursor.getLong(idColumn)
|
||||
val deleteUri = android.content.ContentUris.withAppendedId(android.provider.MediaStore.Downloads.EXTERNAL_CONTENT_URI, id)
|
||||
resolver.delete(deleteUri, null, null)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
deleteIfExists(pdfFileName)
|
||||
deleteIfExists(csvFileName)
|
||||
|
||||
// PDF und CSV speichern
|
||||
try {
|
||||
val pdfUri = resolver.insert(
|
||||
android.provider.MediaStore.Downloads.EXTERNAL_CONTENT_URI,
|
||||
android.content.ContentValues().apply {
|
||||
put(android.provider.MediaStore.MediaColumns.DISPLAY_NAME, pdfFileName)
|
||||
put(android.provider.MediaStore.MediaColumns.MIME_TYPE, "application/pdf")
|
||||
put(android.provider.MediaStore.MediaColumns.RELATIVE_PATH, "Download/")
|
||||
}
|
||||
)
|
||||
|
||||
val csvUri = resolver.insert(
|
||||
android.provider.MediaStore.Downloads.EXTERNAL_CONTENT_URI,
|
||||
android.content.ContentValues().apply {
|
||||
put(android.provider.MediaStore.MediaColumns.DISPLAY_NAME, csvFileName)
|
||||
put(android.provider.MediaStore.MediaColumns.MIME_TYPE, "text/csv")
|
||||
put(android.provider.MediaStore.MediaColumns.RELATIVE_PATH, "Download/")
|
||||
}
|
||||
)
|
||||
|
||||
pdfUri?.let {
|
||||
resolver.openOutputStream(it)?.use { out -> pdfDocument.writeTo(out) }
|
||||
pdfDocument.close()
|
||||
}
|
||||
|
||||
csvUri?.let {
|
||||
resolver.openOutputStream(it)?.use { out ->
|
||||
out.write(csvBuilder.toString().toByteArray(Charsets.UTF_8))
|
||||
}
|
||||
}
|
||||
|
||||
withContext(Dispatchers.Main) {
|
||||
Toast.makeText(activity, "PDF und CSV gespeichert in Downloads", Toast.LENGTH_LONG).show()
|
||||
|
||||
pdfUri?.let {
|
||||
val intent = android.content.Intent(android.content.Intent.ACTION_VIEW).apply {
|
||||
setDataAndType(it, "application/pdf")
|
||||
addFlags(android.content.Intent.FLAG_GRANT_READ_URI_PERMISSION or android.content.Intent.FLAG_ACTIVITY_NO_HISTORY)
|
||||
}
|
||||
try {
|
||||
activity.startActivity(intent)
|
||||
} catch (e: android.content.ActivityNotFoundException) {
|
||||
Toast.makeText(activity, "Kein PDF-Viewer installiert", Toast.LENGTH_SHORT).show()
|
||||
}
|
||||
}
|
||||
}
|
||||
} catch (e: Exception) {
|
||||
Log.e("SAVE", "Fehler beim Speichern der Dateien", e)
|
||||
withContext(Dispatchers.Main) {
|
||||
Toast.makeText(activity, "Fehler beim Speichern: ${e.message}", Toast.LENGTH_LONG).show()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private fun drawMultilineText(
|
||||
canvas: Canvas,
|
||||
text: String,
|
||||
x: Float,
|
||||
yStart: Float,
|
||||
paint: Paint,
|
||||
maxWidth: Int,
|
||||
isBold: Boolean = false
|
||||
): Float {
|
||||
paint.isFakeBoldText = isBold
|
||||
|
||||
val words = text.split(" ")
|
||||
var line = ""
|
||||
var y = yStart
|
||||
for (word in words) {
|
||||
val testLine = if (line.isEmpty()) word else "$line $word"
|
||||
val lineWidth = paint.measureText(testLine)
|
||||
if (lineWidth > maxWidth) {
|
||||
canvas.drawText(line, x, y, paint)
|
||||
y += paint.textSize * 1.4f
|
||||
line = word
|
||||
} else {
|
||||
line = testLine
|
||||
}
|
||||
}
|
||||
if (line.isNotEmpty()) {
|
||||
canvas.drawText(line, x, y, paint)
|
||||
y += paint.textSize * 1.4f
|
||||
}
|
||||
|
||||
paint.isFakeBoldText = false
|
||||
return y
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
136
app/src/main/java/com/dano/test1/HandlerRadioQuestion.kt
Normal file
136
app/src/main/java/com/dano/test1/HandlerRadioQuestion.kt
Normal file
@ -0,0 +1,136 @@
|
||||
package com.dano.test1
|
||||
|
||||
import android.content.Context
|
||||
import android.view.View
|
||||
import android.text.Html
|
||||
import android.widget.*
|
||||
|
||||
class HandlerRadioQuestion(
|
||||
private val context: Context,
|
||||
private val answers: MutableMap<String, Any>,
|
||||
private val points: MutableList<Int>,
|
||||
private val languageID: String,
|
||||
private val goToNextQuestion: () -> Unit,
|
||||
private val goToPreviousQuestion: () -> Unit,
|
||||
private val goToQuestionById: (String) -> Unit,
|
||||
private val showToast: (String) -> Unit
|
||||
) : QuestionHandler {
|
||||
|
||||
private lateinit var layout: View
|
||||
private lateinit var question: QuestionItem.RadioQuestion
|
||||
|
||||
// Bind the question data to the view
|
||||
override fun bind(layout: View, question: QuestionItem) {
|
||||
this.layout = layout
|
||||
this.question = question as QuestionItem.RadioQuestion
|
||||
|
||||
val radioGroup = layout.findViewById<RadioGroup>(R.id.RadioGroup)
|
||||
val questionTextView = layout.findViewById<TextView>(R.id.textView)
|
||||
val questionTitle = layout.findViewById<TextView>(R.id.question)
|
||||
|
||||
// Set question text and optional text key
|
||||
questionTextView.text = question.textKey?.let { LanguageManager.getText(languageID, it) } ?: ""
|
||||
questionTitle.text = question.question?.let {
|
||||
Html.fromHtml(LanguageManager.getText(languageID, it), Html.FROM_HTML_MODE_LEGACY)
|
||||
} ?: ""
|
||||
|
||||
|
||||
// Clear previous radio buttons if any
|
||||
radioGroup.removeAllViews()
|
||||
|
||||
// Dynamically create radio buttons based on options
|
||||
question.options.forEach { option ->
|
||||
val radioButton = RadioButton(context).apply {
|
||||
text = LanguageManager.getText(languageID, option.key)
|
||||
tag = option.key
|
||||
layoutParams = RadioGroup.LayoutParams(
|
||||
RadioGroup.LayoutParams.MATCH_PARENT,
|
||||
RadioGroup.LayoutParams.WRAP_CONTENT
|
||||
).apply {
|
||||
val scale = context.resources.displayMetrics.density
|
||||
val margin = (16 * scale + 0.5f).toInt()
|
||||
setMargins(0, 0, 0, margin)
|
||||
}
|
||||
}
|
||||
radioGroup.addView(radioButton)
|
||||
}
|
||||
|
||||
// Restore previous answer if one was saved
|
||||
restorePreviousAnswer(radioGroup)
|
||||
|
||||
// Handle Next button click
|
||||
layout.findViewById<Button>(R.id.Qnext).setOnClickListener {
|
||||
if (validate()) {
|
||||
saveAnswer()
|
||||
|
||||
val selectedId = radioGroup.checkedRadioButtonId
|
||||
val selectedRadioButton = layout.findViewById<RadioButton>(selectedId)
|
||||
val selectedKey = selectedRadioButton.tag.toString()
|
||||
|
||||
// Check if there is a specific next question ID
|
||||
val nextId = question.options.find { it.key == selectedKey }?.nextQuestionId
|
||||
|
||||
if (!nextId.isNullOrEmpty()) {
|
||||
goToQuestionById(nextId)
|
||||
} else {
|
||||
goToNextQuestion()
|
||||
}
|
||||
} else {
|
||||
showToast(LanguageManager.getText(languageID, "select_one_answer"))
|
||||
}
|
||||
}
|
||||
|
||||
// Handle Previous button click
|
||||
layout.findViewById<Button>(R.id.Qprev).setOnClickListener {
|
||||
goToPreviousQuestion()
|
||||
}
|
||||
}
|
||||
|
||||
// Restore previously saved answer (if exists)
|
||||
private fun restorePreviousAnswer(radioGroup: RadioGroup) {
|
||||
question.question?.let { questionKey ->
|
||||
val savedAnswer = answers[questionKey] as? String
|
||||
savedAnswer?.let { saved ->
|
||||
for (i in 0 until radioGroup.childCount) {
|
||||
val radioButton = radioGroup.getChildAt(i) as RadioButton
|
||||
if (radioButton.tag == saved) {
|
||||
radioButton.isChecked = true
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Validate if any radio button has been selected
|
||||
override fun validate(): Boolean {
|
||||
return layout.findViewById<RadioGroup>(R.id.RadioGroup).checkedRadioButtonId != -1
|
||||
}
|
||||
|
||||
// Save selected answer and update points list
|
||||
override fun saveAnswer() {
|
||||
val radioGroup = layout.findViewById<RadioGroup>(R.id.RadioGroup)
|
||||
val selectedId = radioGroup.checkedRadioButtonId
|
||||
val selectedRadioButton = layout.findViewById<RadioButton>(selectedId)
|
||||
val answerKey = selectedRadioButton.tag.toString()
|
||||
|
||||
question.question?.let { questionKey ->
|
||||
|
||||
// Alte Antwort und Punkt ermitteln
|
||||
val oldAnswerKey = answers[questionKey] as? String
|
||||
val oldPoint = oldAnswerKey?.let { question.pointsMap?.get(it) } ?: 0
|
||||
|
||||
// Alten Punkt entfernen, falls vorhanden
|
||||
points.remove(oldPoint)
|
||||
|
||||
// Neue Antwort speichern
|
||||
answers[questionKey] = answerKey
|
||||
|
||||
// Neuen Punkt ermitteln und hinzufügen
|
||||
val newPoint = question.pointsMap?.get(answerKey) ?: 0
|
||||
points.add(newPoint)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
93
app/src/main/java/com/dano/test1/HandlerStringSpinner.kt
Normal file
93
app/src/main/java/com/dano/test1/HandlerStringSpinner.kt
Normal file
@ -0,0 +1,93 @@
|
||||
package com.dano.test1
|
||||
|
||||
import android.content.Context
|
||||
import android.view.View
|
||||
import android.widget.*
|
||||
|
||||
class HandlerStringSpinner(
|
||||
private val context: Context,
|
||||
private val answers: MutableMap<String, Any>,
|
||||
private val languageID: String,
|
||||
private val goToNextQuestion: () -> Unit,
|
||||
private val goToPreviousQuestion: () -> Unit,
|
||||
private val showToast: (String) -> Unit
|
||||
) : QuestionHandler {
|
||||
|
||||
private lateinit var layout: View
|
||||
private lateinit var question: QuestionItem.StringSpinnerQuestion
|
||||
|
||||
override fun bind(layout: View, question: QuestionItem) {
|
||||
if (question !is QuestionItem.StringSpinnerQuestion) return
|
||||
|
||||
this.layout = layout
|
||||
this.question = question
|
||||
|
||||
val questionTextView = layout.findViewById<TextView>(R.id.question)
|
||||
val textView = layout.findViewById<TextView>(R.id.textView)
|
||||
val spinner = layout.findViewById<Spinner>(R.id.string_spinner)
|
||||
|
||||
questionTextView.text = question.question?.let { LanguageManager.getText(languageID, it) } ?: ""
|
||||
textView.text = question.textKey?.let { LanguageManager.getText(languageID, it) } ?: ""
|
||||
|
||||
val options = buildOptionsList()
|
||||
|
||||
val savedSelection = question.question?.let { answers[it] as? String }
|
||||
|
||||
setupSpinner(spinner, options, savedSelection)
|
||||
|
||||
layout.findViewById<Button>(R.id.Qnext).setOnClickListener {
|
||||
if (validate()) {
|
||||
saveAnswer()
|
||||
goToNextQuestion()
|
||||
}
|
||||
}
|
||||
|
||||
layout.findViewById<Button>(R.id.Qprev).setOnClickListener {
|
||||
goToPreviousQuestion()
|
||||
}
|
||||
}
|
||||
|
||||
override fun validate(): Boolean {
|
||||
val spinner = layout.findViewById<Spinner>(R.id.string_spinner)
|
||||
val selected = spinner.selectedItem as? String
|
||||
val prompt = LanguageManager.getText(languageID, "choose_answer")
|
||||
|
||||
return if (selected.isNullOrEmpty() || selected == prompt) {
|
||||
showToast(LanguageManager.getText(languageID, "select_one_answer"))
|
||||
false
|
||||
} else {
|
||||
true
|
||||
}
|
||||
}
|
||||
|
||||
override fun saveAnswer() {
|
||||
val spinner = layout.findViewById<Spinner>(R.id.string_spinner)
|
||||
val selected = spinner.selectedItem as? String ?: return
|
||||
|
||||
question.question?.let { key ->
|
||||
answers[key] = selected
|
||||
}
|
||||
}
|
||||
|
||||
private fun buildOptionsList(): List<String> {
|
||||
return if (question.id == "q11") {
|
||||
Countries.getAllCountries(languageID)
|
||||
} else {
|
||||
val prompt = LanguageManager.getText(languageID, "choose_answer")
|
||||
listOf(prompt) + question.options
|
||||
}
|
||||
}
|
||||
|
||||
private fun <T> setupSpinner(spinner: Spinner, items: List<T>, selectedItem: T?) {
|
||||
val adapter = ArrayAdapter(context, android.R.layout.simple_spinner_item, items)
|
||||
adapter.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item)
|
||||
spinner.adapter = adapter
|
||||
|
||||
selectedItem?.let {
|
||||
val index = items.indexOf(it)
|
||||
if (index >= 0) {
|
||||
spinner.setSelection(index)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
97
app/src/main/java/com/dano/test1/HandlerValueSpinner.kt
Normal file
97
app/src/main/java/com/dano/test1/HandlerValueSpinner.kt
Normal file
@ -0,0 +1,97 @@
|
||||
package com.dano.test1
|
||||
|
||||
import android.content.Context
|
||||
import android.view.View
|
||||
import android.widget.*
|
||||
|
||||
class HandlerValueSpinner(
|
||||
private val context: Context,
|
||||
private val answers: MutableMap<String, Any>,
|
||||
private val languageID: String,
|
||||
private val goToNextQuestion: () -> Unit,
|
||||
private val goToPreviousQuestion: () -> Unit,
|
||||
private val goToQuestionById: (String) -> Unit,
|
||||
private val showToast: (String) -> Unit
|
||||
) : QuestionHandler {
|
||||
|
||||
private lateinit var layout: View
|
||||
private lateinit var question: QuestionItem.ValueSpinnerQuestion
|
||||
|
||||
override fun bind(layout: View, question: QuestionItem) {
|
||||
if (question !is QuestionItem.ValueSpinnerQuestion) return
|
||||
|
||||
this.layout = layout
|
||||
this.question = question
|
||||
|
||||
val questionTextView = layout.findViewById<TextView>(R.id.question)
|
||||
val textView = layout.findViewById<TextView>(R.id.textView)
|
||||
val spinner = layout.findViewById<Spinner>(R.id.value_spinner)
|
||||
|
||||
questionTextView.text = question.question?.let { LanguageManager.getText(languageID, it) } ?: ""
|
||||
textView.text = question.textKey?.let { LanguageManager.getText(languageID, it) } ?: ""
|
||||
|
||||
val prompt = LanguageManager.getText(languageID, "choose_answer")
|
||||
val spinnerItems: List<String> = listOf(prompt) + if (question.range != null) {
|
||||
(question.range.min..question.range.max).map { it.toString() }
|
||||
} else {
|
||||
question.options.map { it.value.toString() }
|
||||
}
|
||||
|
||||
val savedValue = question.question?.let { answers[it] as? String }
|
||||
setupSpinner(spinner, spinnerItems, savedValue)
|
||||
|
||||
layout.findViewById<Button>(R.id.Qnext).setOnClickListener {
|
||||
if (validate()) {
|
||||
saveAnswer()
|
||||
val selectedValue = (spinner.selectedItem as? String)?.toIntOrNull()
|
||||
|
||||
if (!question.options.isNullOrEmpty() && selectedValue != null) {
|
||||
val selectedOption = question.options.find { it.value == selectedValue }
|
||||
val nextId = selectedOption?.nextQuestionId
|
||||
if (!nextId.isNullOrEmpty()) {
|
||||
goToQuestionById(nextId)
|
||||
return@setOnClickListener
|
||||
}
|
||||
}
|
||||
goToNextQuestion()
|
||||
}
|
||||
}
|
||||
|
||||
layout.findViewById<Button>(R.id.Qprev).setOnClickListener {
|
||||
goToPreviousQuestion()
|
||||
}
|
||||
}
|
||||
|
||||
override fun validate(): Boolean {
|
||||
val spinner = layout.findViewById<Spinner>(R.id.value_spinner)
|
||||
val selected = spinner.selectedItem as? String
|
||||
val prompt = LanguageManager.getText(languageID, "choose_answer")
|
||||
|
||||
return if (selected == null || selected == prompt) {
|
||||
showToast(LanguageManager.getText(languageID, "select_one_answer"))
|
||||
false
|
||||
} else {
|
||||
true
|
||||
}
|
||||
}
|
||||
|
||||
override fun saveAnswer() {
|
||||
val spinner = layout.findViewById<Spinner>(R.id.value_spinner)
|
||||
val selected = spinner.selectedItem as? String ?: return
|
||||
|
||||
question.question?.let { key ->
|
||||
answers[key] = selected
|
||||
}
|
||||
}
|
||||
|
||||
private fun <T> setupSpinner(spinner: Spinner, items: List<T>, selectedItem: T?) {
|
||||
val adapter = ArrayAdapter(context, android.R.layout.simple_spinner_item, items)
|
||||
adapter.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item)
|
||||
spinner.adapter = adapter
|
||||
|
||||
selectedItem?.let {
|
||||
val index = items.indexOf(it)
|
||||
if (index >= 0) spinner.setSelection(index)
|
||||
}
|
||||
}
|
||||
}
|
||||
2987
app/src/main/java/com/dano/test1/LanguageManager.kt
Normal file
2987
app/src/main/java/com/dano/test1/LanguageManager.kt
Normal file
File diff suppressed because it is too large
Load Diff
64
app/src/main/java/com/dano/test1/LocalizationHelper.kt
Normal file
64
app/src/main/java/com/dano/test1/LocalizationHelper.kt
Normal file
@ -0,0 +1,64 @@
|
||||
package com.dano.test1
|
||||
|
||||
import android.view.View
|
||||
import android.view.ViewGroup
|
||||
import android.widget.Button
|
||||
import android.widget.EditText
|
||||
import android.widget.*
|
||||
|
||||
object LocalizationHelper {
|
||||
|
||||
/**
|
||||
* Extension function to set localized hint on EditText.
|
||||
*/
|
||||
fun EditText.setLocalizedHint(languageID: String, key: String) {
|
||||
this.hint = LanguageManager.getText(languageID, key)
|
||||
}
|
||||
|
||||
/**
|
||||
* Extension function to set localized text on Button.
|
||||
*/
|
||||
fun Button.setLocalizedText(languageID: String, key: String) {
|
||||
this.text = LanguageManager.getText(languageID, key)
|
||||
}
|
||||
|
||||
/**
|
||||
* Recursively localizes the view hierarchy by updating text or hint
|
||||
* of views according to their tag as localization key.
|
||||
* @param root The root view to start localization.
|
||||
* @param languageID The language identifier for localization.
|
||||
*/
|
||||
fun localizeViewTree(root: View, languageID: String) {
|
||||
val key = root.tag as? String
|
||||
|
||||
when (root) {
|
||||
is EditText -> {
|
||||
// Set hint for EditText if tag key is present
|
||||
key?.let { root.setLocalizedHint(languageID, it) }
|
||||
}
|
||||
is Button -> {
|
||||
// Set text for Button if tag key is present
|
||||
key?.let { root.setLocalizedText(languageID, it) }
|
||||
}
|
||||
is TextView -> {
|
||||
// Set text for TextView if tag key is present
|
||||
// Note: Button and others inherit from TextView, but handled separately above
|
||||
if (root !is Button) key?.let { root.text = LanguageManager.getText(languageID, it) }
|
||||
}
|
||||
is RadioButton -> {
|
||||
// Set text for RadioButton if tag key is present
|
||||
key?.let { root.text = LanguageManager.getText(languageID, it) }
|
||||
}
|
||||
is CheckBox -> {
|
||||
// Set text for CheckBox if tag key is present
|
||||
key?.let { root.text = LanguageManager.getText(languageID, it) }
|
||||
}
|
||||
is ViewGroup -> {
|
||||
// Recursively localize all child views
|
||||
for (i in 0 until root.childCount) {
|
||||
localizeViewTree(root.getChildAt(i), languageID)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
52
app/src/main/java/com/dano/test1/MainActivity.kt
Normal file
52
app/src/main/java/com/dano/test1/MainActivity.kt
Normal file
@ -0,0 +1,52 @@
|
||||
package com.dano.test1
|
||||
|
||||
import android.os.Bundle
|
||||
import androidx.appcompat.app.AppCompatActivity
|
||||
|
||||
class MainActivity : AppCompatActivity() {
|
||||
|
||||
private lateinit var openingScreenHandler: HandlerOpeningScreen
|
||||
|
||||
var isInQuestionnaire: Boolean = false
|
||||
var isFirstQuestionnairePage: Boolean = false
|
||||
|
||||
override fun onCreate(savedInstanceState: Bundle?) {
|
||||
super.onCreate(savedInstanceState)
|
||||
// Initialize the opening screen handler and show the opening screen
|
||||
openingScreenHandler = HandlerOpeningScreen(this)
|
||||
openingScreenHandler.init()
|
||||
}
|
||||
|
||||
/**
|
||||
* Starts the given questionnaire and attaches it to this activity.
|
||||
* @param questionnaire The questionnaire instance to start.
|
||||
* @param languageID The language identifier for localization.
|
||||
*/
|
||||
fun startQuestionnaire(questionnaire: QuestionnaireBase<*>, languageID: String) {
|
||||
isInQuestionnaire = true
|
||||
isFirstQuestionnairePage = true
|
||||
questionnaire.attach(this, languageID)
|
||||
questionnaire.startQuestionnaire()
|
||||
}
|
||||
|
||||
/**
|
||||
* Handle the back button press.
|
||||
* If the openingScreenHandler can handle it, do not call super.
|
||||
* Otherwise, call the default back press behavior.
|
||||
*/
|
||||
override fun onBackPressed() {
|
||||
if (!openingScreenHandler.onBackPressed()) {
|
||||
super.onBackPressed()
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Finish the questionnaire and return to the opening screen.
|
||||
*/
|
||||
fun finishQuestionnaire() {
|
||||
// For example, switch back to the opening screen:
|
||||
isInQuestionnaire = false
|
||||
isFirstQuestionnairePage = false
|
||||
openingScreenHandler.init()
|
||||
}
|
||||
}
|
||||
24
app/src/main/java/com/dano/test1/MonthsSpinner.kt
Normal file
24
app/src/main/java/com/dano/test1/MonthsSpinner.kt
Normal file
@ -0,0 +1,24 @@
|
||||
package com.dano.test1
|
||||
|
||||
data class Month(val name: String) {
|
||||
override fun toString(): String = name
|
||||
}
|
||||
|
||||
object Months {
|
||||
fun getAllMonths(languageID: String): List<Any> {
|
||||
return listOf(
|
||||
Month(LanguageManager.getText(languageID, "january")),
|
||||
Month(LanguageManager.getText(languageID, "february")),
|
||||
Month(LanguageManager.getText(languageID, "march")),
|
||||
Month(LanguageManager.getText(languageID, "april")),
|
||||
Month(LanguageManager.getText(languageID, "may")),
|
||||
Month(LanguageManager.getText(languageID, "june")),
|
||||
Month(LanguageManager.getText(languageID, "july")),
|
||||
Month(LanguageManager.getText(languageID, "august")),
|
||||
Month(LanguageManager.getText(languageID, "september")),
|
||||
Month(LanguageManager.getText(languageID, "october")),
|
||||
Month(LanguageManager.getText(languageID, "november")),
|
||||
Month(LanguageManager.getText(languageID, "december"))
|
||||
)
|
||||
}
|
||||
}
|
||||
24
app/src/main/java/com/dano/test1/MyApp.kt
Normal file
24
app/src/main/java/com/dano/test1/MyApp.kt
Normal file
@ -0,0 +1,24 @@
|
||||
package com.dano.test1
|
||||
|
||||
import android.app.Application
|
||||
import androidx.room.Room
|
||||
import com.dano.test1.data.AppDatabase
|
||||
|
||||
class MyApp : Application() {
|
||||
|
||||
companion object {
|
||||
lateinit var database: AppDatabase
|
||||
private set
|
||||
}
|
||||
|
||||
override fun onCreate() {
|
||||
super.onCreate()
|
||||
database = Room.databaseBuilder(
|
||||
applicationContext,
|
||||
AppDatabase::class.java,
|
||||
"questionnaire_database"
|
||||
)
|
||||
.fallbackToDestructiveMigration()
|
||||
.build()
|
||||
}
|
||||
}
|
||||
8
app/src/main/java/com/dano/test1/QuestionHandler.kt
Normal file
8
app/src/main/java/com/dano/test1/QuestionHandler.kt
Normal file
@ -0,0 +1,8 @@
|
||||
package com.dano.test1
|
||||
import android.view.View
|
||||
|
||||
interface QuestionHandler {
|
||||
fun bind(layout: View, question: QuestionItem)
|
||||
fun validate(): Boolean
|
||||
fun saveAnswer()
|
||||
}
|
||||
249
app/src/main/java/com/dano/test1/QuestionnaireBase.kt
Normal file
249
app/src/main/java/com/dano/test1/QuestionnaireBase.kt
Normal file
@ -0,0 +1,249 @@
|
||||
package com.dano.test1
|
||||
|
||||
import android.app.Activity
|
||||
import android.util.Log
|
||||
import android.view.View
|
||||
import android.widget.*
|
||||
import com.dano.test1.data.*
|
||||
import com.google.gson.Gson
|
||||
import com.google.gson.JsonParser
|
||||
import kotlinx.coroutines.*
|
||||
import java.io.File
|
||||
import java.util.Calendar
|
||||
|
||||
// Global constants and values
|
||||
const val MAX_VALUE_AGE = 122
|
||||
val MAX_VALUE_YEAR = Calendar.getInstance().get(Calendar.YEAR)
|
||||
|
||||
object GlobalValues {
|
||||
var INTEGRATION_INDEX: Int = 0
|
||||
var LAST_CLIENT_CODE: String? = null
|
||||
var LOADED_CLIENT_CODE: String? = null
|
||||
}
|
||||
|
||||
// Data classes for questionnaire metadata
|
||||
data class QuestionnaireMeta(val id: String)
|
||||
data class QuestionnaireData(val meta: QuestionnaireMeta, val questions: List<QuestionItem>)
|
||||
|
||||
// Abstract base class for questionnaire
|
||||
abstract class QuestionnaireBase<T> {
|
||||
abstract fun startQuestionnaire()
|
||||
abstract fun showCurrentQuestion()
|
||||
|
||||
fun attach(activity: Activity, language: String) {
|
||||
this.context = activity
|
||||
this.languageID = language
|
||||
}
|
||||
|
||||
// Protected properties
|
||||
protected lateinit var questionnaireMeta: QuestionnaireMeta
|
||||
protected var currentIndex = 0
|
||||
protected lateinit var questions: List<QuestionItem>
|
||||
protected val answers = mutableMapOf<String, Any>()
|
||||
protected val points = mutableListOf<Int>()
|
||||
protected var languageID: String = "GERMAN"
|
||||
protected lateinit var context: Activity
|
||||
private val navigationHistory = mutableListOf<Int>()
|
||||
|
||||
// Utility functions
|
||||
protected fun showToast(message: String) {
|
||||
Toast.makeText(context, message, Toast.LENGTH_SHORT).show()
|
||||
}
|
||||
|
||||
protected fun validateEditTexts(vararg fields: EditText): Boolean {
|
||||
return fields.all { it.text.toString().trim().isNotEmpty() }
|
||||
}
|
||||
|
||||
// Replace placeholders in file content and save updated file
|
||||
protected fun updateFileContent(fileName: String, placeholders: Map<String, Any?>) {
|
||||
val file = File(context.filesDir, fileName)
|
||||
if (file.exists()) {
|
||||
var updatedContent = file.readText()
|
||||
placeholders.forEach { (key, value) ->
|
||||
updatedContent = updatedContent.replace("{$key}", value.toString())
|
||||
}
|
||||
file.writeText(updatedContent)
|
||||
}
|
||||
}
|
||||
|
||||
// Setup spinner UI element
|
||||
protected fun setupSpinner(spinner: Spinner, spinnerValues: List<Any>, selectedValue: Any?) {
|
||||
val adapter = ArrayAdapter(context, android.R.layout.simple_spinner_item, spinnerValues).apply {
|
||||
setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item)
|
||||
}
|
||||
spinner.adapter = adapter
|
||||
selectedValue?.let { value ->
|
||||
val index = spinnerValues.indexOfFirst {
|
||||
it.toString().trim().equals(value.toString().trim(), ignoreCase = true)
|
||||
}
|
||||
if (index >= 0) spinner.setSelection(index)
|
||||
}
|
||||
}
|
||||
|
||||
// Navigate to specific layout
|
||||
protected fun navigateTo(layoutResId: Int, setup: (View) -> Unit) {
|
||||
context.setContentView(layoutResId)
|
||||
val rootView = context.findViewById<View>(android.R.id.content)
|
||||
setup(rootView)
|
||||
}
|
||||
|
||||
protected fun setupPrevButton(buttonId: Int, action: () -> Unit) {
|
||||
context.findViewById<Button>(buttonId)?.setOnClickListener { action() }
|
||||
}
|
||||
|
||||
protected fun showEmptyScreen() {
|
||||
navigateTo(getLayoutResId("empty")) {
|
||||
setupPrevButton(R.id.Qprev) { goToPreviousQuestion() }
|
||||
}
|
||||
}
|
||||
|
||||
// Resolve layout resource ID from layout name
|
||||
protected fun getLayoutResId(layoutName: String?): Int {
|
||||
return layoutName?.let {
|
||||
context.resources.getIdentifier(it, "layout", context.packageName)
|
||||
} ?: 0
|
||||
}
|
||||
|
||||
// Calculate sum of all points
|
||||
protected fun calculateIntegrationIndex(): Int = points.sum()
|
||||
|
||||
// Navigation logic
|
||||
protected fun goToPreviousQuestion() {
|
||||
if (navigationHistory.isNotEmpty()) {
|
||||
currentIndex = navigationHistory.removeAt(navigationHistory.size - 1)
|
||||
showCurrentQuestion()
|
||||
} else {
|
||||
(context as? MainActivity)?.finishQuestionnaire()
|
||||
}
|
||||
}
|
||||
|
||||
protected fun goToNextQuestion() {
|
||||
if (currentIndex < questions.size - 1) {
|
||||
navigationHistory.add(currentIndex)
|
||||
currentIndex++
|
||||
showCurrentQuestion()
|
||||
}
|
||||
}
|
||||
|
||||
fun goToQuestionById(questionId: String) {
|
||||
val index = questions.indexOfFirst { it.id == questionId }
|
||||
if (index != -1) {
|
||||
navigationHistory.add(currentIndex)
|
||||
currentIndex = index
|
||||
showCurrentQuestion()
|
||||
}
|
||||
}
|
||||
|
||||
// Load questionnaire data from JSON
|
||||
protected fun loadQuestionnaireFromJson(filename: String): Pair<QuestionnaireMeta, List<QuestionItem>> {
|
||||
val jsonString = context.assets.open(filename).bufferedReader().use { it.readText() }
|
||||
val gson = Gson()
|
||||
val jsonObject = JsonParser.parseString(jsonString).asJsonObject
|
||||
|
||||
val meta = gson.fromJson(jsonObject.getAsJsonObject("meta"), QuestionnaireMeta::class.java)
|
||||
val jsonArray = jsonObject.getAsJsonArray("questions")
|
||||
|
||||
val questionTypeMap = mapOf(
|
||||
"client_coach_code_question" to QuestionItem.ClientCoachCodeQuestion::class.java,
|
||||
"radio_question" to QuestionItem.RadioQuestion::class.java,
|
||||
"date_spinner" to QuestionItem.DateSpinnerQuestion::class.java,
|
||||
"value_spinner" to QuestionItem.ValueSpinnerQuestion::class.java,
|
||||
"glass_scale_question" to QuestionItem.GlassScaleQuestion::class.java,
|
||||
"last_page" to QuestionItem.LastPage::class.java,
|
||||
"client_not_signed" to QuestionItem.ClientNotSigned::class.java,
|
||||
"string_spinner" to QuestionItem.StringSpinnerQuestion::class.java,
|
||||
"multi_check_box_question" to QuestionItem.MultiCheckboxQuestion::class.java
|
||||
)
|
||||
|
||||
val questionsList = jsonArray.mapNotNull { element ->
|
||||
val obj = element.asJsonObject
|
||||
val layoutType = obj.get("layout").asString
|
||||
val clazz = questionTypeMap[layoutType] ?: return@mapNotNull null
|
||||
gson.fromJson(obj, clazz)
|
||||
}
|
||||
|
||||
return Pair(meta, questionsList)
|
||||
}
|
||||
|
||||
|
||||
|
||||
// Factory to create handler for each question type
|
||||
protected open fun createHandlerForQuestion(question: QuestionItem): QuestionHandler? {
|
||||
return when (question) {
|
||||
is QuestionItem.RadioQuestion -> HandlerRadioQuestion(context, answers, points, languageID, ::goToNextQuestion, ::goToPreviousQuestion, ::goToQuestionById, ::showToast)
|
||||
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.ClientNotSigned -> HandlerClientNotSigned(answers, languageID, ::goToNextQuestion, ::goToPreviousQuestion, ::showToast)
|
||||
is QuestionItem.StringSpinnerQuestion -> HandlerStringSpinner(context, answers, languageID, ::goToNextQuestion, ::goToPreviousQuestion, ::showToast)
|
||||
is QuestionItem.MultiCheckboxQuestion -> HandlerMultiCheckboxQuestion(context, answers, points, languageID, ::goToNextQuestion, ::goToPreviousQuestion, ::showToast)
|
||||
is QuestionItem.LastPage -> HandlerLastPage(
|
||||
answers, languageID, ::goToNextQuestion, ::goToPreviousQuestion
|
||||
) { CoroutineScope(Dispatchers.IO).launch { saveAnswersToDatabase(answers, questionnaireMeta.id) } }
|
||||
else -> null
|
||||
}
|
||||
}
|
||||
|
||||
// Persist answers into database
|
||||
suspend fun saveAnswersToDatabase(answers: Map<String, Any>, questionnaireId: String) {
|
||||
Log.d("AnswersMap", answers.toString())
|
||||
val db = MyApp.database
|
||||
|
||||
val clientCode = answers["client_code"] as? String ?: return
|
||||
|
||||
saveClientAndQuestionnaire(db, clientCode, questionnaireId)
|
||||
saveQuestions(db, answers, questionnaireId)
|
||||
saveAnswers(db, answers, questionnaireId, clientCode)
|
||||
markQuestionnaireCompleted(db, questionnaireId, clientCode)
|
||||
}
|
||||
|
||||
private suspend fun saveClientAndQuestionnaire(db: AppDatabase, clientCode: String, questionnaireId: String) {
|
||||
db.clientDao().insertClient(Client(clientCode))
|
||||
db.questionnaireDao().insertQuestionnaire(Questionnaire(id = questionnaireId))
|
||||
}
|
||||
|
||||
private suspend fun saveQuestions(db: AppDatabase, answers: Map<String, Any>, questionnaireId: String) {
|
||||
val questionEntities = answers.keys
|
||||
.filterNot { it.startsWith("client") }
|
||||
.map { key ->
|
||||
val questionId = "$questionnaireId-$key"
|
||||
Question(
|
||||
questionId = questionId,
|
||||
questionnaireId = questionnaireId,
|
||||
question = LanguageManager.getText(languageID, key)
|
||||
)
|
||||
}
|
||||
db.questionDao().insertQuestions(questionEntities)
|
||||
}
|
||||
|
||||
private suspend fun saveAnswers(db: AppDatabase, answers: Map<String, Any>, questionnaireId: String, clientCode: String) {
|
||||
val answerEntities = answers.entries
|
||||
.filterNot { it.key.startsWith("client") }
|
||||
.map { (key, value) ->
|
||||
val questionId = "$questionnaireId-$key"
|
||||
Answer(
|
||||
clientCode = clientCode,
|
||||
questionId = questionId,
|
||||
answerValue = value.toString()
|
||||
)
|
||||
}
|
||||
db.answerDao().insertAnswers(answerEntities)
|
||||
}
|
||||
|
||||
private suspend fun markQuestionnaireCompleted(db: AppDatabase, questionnaireId: String, clientCode: String) {
|
||||
val completedEntry = CompletedQuestionnaire(
|
||||
clientCode = clientCode,
|
||||
questionnaireId = questionnaireId,
|
||||
isDone = true,
|
||||
sumPoints = points.sum()
|
||||
)
|
||||
db.completedQuestionnaireDao().insert(completedEntry)
|
||||
}
|
||||
|
||||
|
||||
|
||||
fun endQuestionnaire() {
|
||||
(context as? MainActivity)?.finishQuestionnaire()
|
||||
}
|
||||
}
|
||||
59
app/src/main/java/com/dano/test1/QuestionnaireGeneric.kt
Normal file
59
app/src/main/java/com/dano/test1/QuestionnaireGeneric.kt
Normal file
@ -0,0 +1,59 @@
|
||||
package com.dano.test1
|
||||
|
||||
import android.widget.Button
|
||||
|
||||
open class QuestionnaireGeneric(private val questionnaireFileName: String) : QuestionnaireBase<Unit>() {
|
||||
|
||||
/**
|
||||
* Starts the questionnaire by loading questions and metadata from JSON,
|
||||
* then shows the first question.
|
||||
*/
|
||||
override fun startQuestionnaire() {
|
||||
val (meta, questionsList) = loadQuestionnaireFromJson(questionnaireFileName)
|
||||
questionnaireMeta = meta
|
||||
questions = questionsList
|
||||
currentIndex = 0
|
||||
showCurrentQuestion()
|
||||
}
|
||||
|
||||
/**
|
||||
* Displays the current question based on the current index.
|
||||
* Loads the appropriate layout, applies localization, sets navigation buttons,
|
||||
* and delegates question binding to the appropriate handler.
|
||||
*/
|
||||
override fun showCurrentQuestion() {
|
||||
val question = questions[currentIndex]
|
||||
|
||||
// Get the layout resource ID based on the question layout name or fallback to default
|
||||
val layoutResId = getLayoutResId(question.layout ?: "default_layout")
|
||||
|
||||
if (layoutResId == 0) {
|
||||
// No valid layout found, show empty screen instead
|
||||
showEmptyScreen()
|
||||
return
|
||||
}
|
||||
|
||||
// Navigate to the question layout and initialize it
|
||||
navigateTo(layoutResId) { layout ->
|
||||
|
||||
// Localize all views in the layout tree according to current language
|
||||
LocalizationHelper.localizeViewTree(layout, languageID)
|
||||
|
||||
// Setup previous button navigation, if present
|
||||
layout.findViewById<Button>(R.id.Qprev)?.setOnClickListener {
|
||||
goToPreviousQuestion()
|
||||
}
|
||||
|
||||
// Create the appropriate handler for the question type
|
||||
val handler = createHandlerForQuestion(question)
|
||||
|
||||
if (handler != null) {
|
||||
// Bind the question data to the UI
|
||||
handler.bind(layout, question)
|
||||
} else {
|
||||
// No handler found for this question type; show empty screen
|
||||
showEmptyScreen()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
121
app/src/main/java/com/dano/test1/QuestionnaireItem.kt
Normal file
121
app/src/main/java/com/dano/test1/QuestionnaireItem.kt
Normal file
@ -0,0 +1,121 @@
|
||||
package com.dano.test1
|
||||
|
||||
data class Option(
|
||||
val key: String, // Must always be set
|
||||
val nextQuestionId: String? = null
|
||||
)
|
||||
|
||||
data class ValueOption(
|
||||
val value: Int,
|
||||
val nextQuestionId: String? = null
|
||||
)
|
||||
|
||||
data class Range(
|
||||
val min: Int,
|
||||
val max: Int
|
||||
)
|
||||
|
||||
data class Constraints(
|
||||
val notBefore: String? = null,
|
||||
val notAfter: String? = null
|
||||
)
|
||||
|
||||
sealed class QuestionItem {
|
||||
abstract val layout: String?
|
||||
abstract val id: String
|
||||
|
||||
data class ClientCoachCodeQuestion(
|
||||
override val layout: String?,
|
||||
override val id: String,
|
||||
val question: String,
|
||||
val hint1: String?,
|
||||
val hint2: String?
|
||||
) : QuestionItem()
|
||||
|
||||
data class RadioQuestion(
|
||||
override val layout: String?,
|
||||
override val id: String,
|
||||
val textKey: String?,
|
||||
val question: String?,
|
||||
val options: List<Option>,
|
||||
val pointsMap: Map<String, Int>? = null
|
||||
) : QuestionItem()
|
||||
|
||||
data class GlassScaleQuestion(
|
||||
override val layout: String?,
|
||||
override val id: String,
|
||||
val textKey: String?,
|
||||
val question: String,
|
||||
val symptomTextKeys: List<String>,
|
||||
val pointsMap: Map<String, Int>,
|
||||
val symptoms: List<String>
|
||||
) : QuestionItem()
|
||||
|
||||
data class ClientNotSigned(
|
||||
override val layout: String?,
|
||||
override val id: String,
|
||||
val textKey1: String?,
|
||||
val textKey2: String?,
|
||||
val question: String,
|
||||
val hint: String?
|
||||
) : QuestionItem()
|
||||
|
||||
data class MultiCheckboxQuestion(
|
||||
override val id: String,
|
||||
override val layout: String,
|
||||
val textKey: String?,
|
||||
val question: String,
|
||||
val options: List<Option>,
|
||||
val nextQuestionId: String?,
|
||||
val pointsMap: Map<String, Int>?,
|
||||
val minSelection: Int = 1
|
||||
) : QuestionItem()
|
||||
|
||||
data class DateSpinnerQuestion(
|
||||
override val layout: String?,
|
||||
override val id: String,
|
||||
val textKey: String?,
|
||||
val question: String,
|
||||
val nextQuestionId: String? = null,
|
||||
val storeAsGlobalKey: String? = null,
|
||||
val constraints: Constraints? = null
|
||||
) : QuestionItem()
|
||||
|
||||
data class ValueSpinnerQuestion(
|
||||
override val layout: String?,
|
||||
override val id: String,
|
||||
val textKey: String?,
|
||||
val question: String,
|
||||
val options: List<ValueOption> = emptyList(),
|
||||
val range: Range? = null
|
||||
) : QuestionItem()
|
||||
|
||||
data class StringSpinnerQuestion(
|
||||
override val layout: String?,
|
||||
override val id: String,
|
||||
val textKey: String?,
|
||||
val question: String,
|
||||
val options: List<String>,
|
||||
val nextQuestionId: String? = null
|
||||
) : QuestionItem()
|
||||
|
||||
data class LastPage(
|
||||
override val layout: String?,
|
||||
override val id: String,
|
||||
val textKey: String,
|
||||
val question: String
|
||||
) : QuestionItem()
|
||||
|
||||
data class QuestionnaireEntry(
|
||||
val file: String,
|
||||
val condition: Condition? = null
|
||||
)
|
||||
|
||||
data class Condition(
|
||||
val questionnaire: String,
|
||||
val questionId: String,
|
||||
val operator: String,
|
||||
val value: String
|
||||
)
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user