Compare commits
2 Commits
a-b-testin
...
092242f913
| Author | SHA1 | Date | |
|---|---|---|---|
| 092242f913 | |||
| e69be2ec6f |
4
.idea/deploymentTargetSelector.xml
generated
4
.idea/deploymentTargetSelector.xml
generated
@ -4,10 +4,10 @@
|
||||
<selectionStates>
|
||||
<SelectionState runConfigName="app">
|
||||
<option name="selectionMode" value="DROPDOWN" />
|
||||
<DropdownSelection timestamp="2026-03-24T11:30:25.894049082Z">
|
||||
<DropdownSelection timestamp="2025-09-29T10:52:30.282144200Z">
|
||||
<Target type="DEFAULT_BOOT">
|
||||
<handle>
|
||||
<DeviceId pluginId="PhysicalDevice" identifier="serial=R52T605XE0L" />
|
||||
<DeviceId pluginId="LocalEmulator" identifier="path=C:\Users\danie\.android\avd\Medium_Phone.avd" />
|
||||
</handle>
|
||||
</Target>
|
||||
</DropdownSelection>
|
||||
|
||||
13
.idea/deviceManager.xml
generated
13
.idea/deviceManager.xml
generated
@ -1,13 +0,0 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project version="4">
|
||||
<component name="DeviceTable">
|
||||
<option name="columnSorters">
|
||||
<list>
|
||||
<ColumnSorterState>
|
||||
<option name="column" value="Name" />
|
||||
<option name="order" value="ASCENDING" />
|
||||
</ColumnSorterState>
|
||||
</list>
|
||||
</option>
|
||||
</component>
|
||||
</project>
|
||||
@ -41,11 +41,6 @@
|
||||
</intent-filter>
|
||||
</activity>
|
||||
|
||||
<activity
|
||||
android:name=".ui.DevSettingsActivity"
|
||||
android:exported="false"
|
||||
android:parentActivityName=".MainActivity" />
|
||||
|
||||
</application>
|
||||
|
||||
</manifest>
|
||||
|
||||
@ -11,16 +11,14 @@ import android.widget.EditText
|
||||
import android.widget.LinearLayout
|
||||
import android.widget.ProgressBar
|
||||
import android.widget.Toast
|
||||
import androidx.activity.enableEdgeToEdge
|
||||
import androidx.appcompat.app.AlertDialog
|
||||
import androidx.appcompat.app.AppCompatActivity
|
||||
import androidx.core.view.ViewCompat
|
||||
import androidx.core.view.WindowInsetsCompat
|
||||
import com.dano.test1.auth.LoginManager
|
||||
import com.dano.test1.auth.TokenStore
|
||||
import com.dano.test1.network.DatabaseDownloader
|
||||
import com.dano.test1.network.LoginManager
|
||||
import com.dano.test1.network.TokenStore
|
||||
import com.dano.test1.questionnaire.QuestionnaireBase
|
||||
import com.dano.test1.ui.HandlerOpeningScreen
|
||||
import com.dano.test1.util.LanguageManager
|
||||
|
||||
class MainActivity : AppCompatActivity() {
|
||||
|
||||
@ -39,7 +37,6 @@ class MainActivity : AppCompatActivity() {
|
||||
private fun t(key: String): String = LanguageManager.getText(bootLanguageId, key)
|
||||
|
||||
override fun onCreate(savedInstanceState: Bundle?) {
|
||||
enableEdgeToEdge()
|
||||
super.onCreate(savedInstanceState)
|
||||
|
||||
// === Offline-Start ermöglichen ===
|
||||
@ -209,16 +206,6 @@ class MainActivity : AppCompatActivity() {
|
||||
}
|
||||
// --- /LIVE NETZSTATUS ---
|
||||
|
||||
override fun onContentChanged() {
|
||||
super.onContentChanged()
|
||||
val content = findViewById<View>(android.R.id.content) ?: return
|
||||
ViewCompat.setOnApplyWindowInsetsListener(content) { v, insets ->
|
||||
val bars = insets.getInsets(WindowInsetsCompat.Type.systemBars())
|
||||
v.setPadding(bars.left, bars.top, bars.right, bars.bottom)
|
||||
WindowInsetsCompat.CONSUMED
|
||||
}
|
||||
}
|
||||
|
||||
override fun onConfigurationChanged(newConfig: Configuration) {
|
||||
super.onConfigurationChanged(newConfig)
|
||||
}
|
||||
|
||||
@ -1,4 +1,4 @@
|
||||
package com.dano.test1.network
|
||||
package com.dano.test1.auth
|
||||
|
||||
import android.app.AlertDialog
|
||||
import android.content.Context
|
||||
@ -1,4 +1,4 @@
|
||||
package com.dano.test1.network
|
||||
package com.dano.test1.auth
|
||||
|
||||
import android.content.Context
|
||||
|
||||
@ -1,4 +1,4 @@
|
||||
package com.dano.test1.data
|
||||
package com.dano.test1.export
|
||||
|
||||
import android.content.ContentValues
|
||||
import android.content.Context
|
||||
@ -7,7 +7,7 @@ import android.net.Uri
|
||||
import android.os.Build
|
||||
import android.os.Environment
|
||||
import android.provider.MediaStore
|
||||
import com.dano.test1.LanguageManager
|
||||
import com.dano.test1.util.LanguageManager
|
||||
import com.dano.test1.MyApp
|
||||
import org.apache.poi.ss.usermodel.Row
|
||||
import org.apache.poi.xssf.usermodel.XSSFWorkbook
|
||||
@ -41,8 +41,8 @@ class ExcelExportService(
|
||||
val orderedIds = headerRepo.loadOrderedIds()
|
||||
if (orderedIds.isEmpty()) return null
|
||||
|
||||
val clients = MyApp.database.clientDao().getAllClients()
|
||||
val questionnaires = MyApp.database.questionnaireDao().getAll()
|
||||
val clients = MyApp.Companion.database.clientDao().getAllClients()
|
||||
val questionnaires = MyApp.Companion.database.questionnaireDao().getAll()
|
||||
val questionnaireIdSet = questionnaires.map { it.id }.toSet()
|
||||
|
||||
val wb = XSSFWorkbook()
|
||||
@ -71,9 +71,9 @@ class ExcelExportService(
|
||||
var c = 0
|
||||
row.createCell(c++).setCellValue((rowIdx + 1).toDouble())
|
||||
|
||||
val completedForClient = MyApp.database.completedQuestionnaireDao().getAllForClient(client.clientCode)
|
||||
val completedForClient = MyApp.Companion.database.completedQuestionnaireDao().getAllForClient(client.clientCode)
|
||||
val statusMap = completedForClient.associate { it.questionnaireId to it.isDone }
|
||||
val answers = MyApp.database.answerDao().getAnswersForClient(client.clientCode)
|
||||
val answers = MyApp.Companion.database.answerDao().getAnswersForClient(client.clientCode)
|
||||
val answerMap = answers.associate { it.questionId to it.answerValue }
|
||||
|
||||
orderedIds.forEach { id ->
|
||||
@ -1,9 +1,9 @@
|
||||
package com.dano.test1.data
|
||||
package com.dano.test1.export
|
||||
|
||||
import android.content.Context
|
||||
import android.util.Log
|
||||
import android.widget.Toast
|
||||
import com.dano.test1.LanguageManager
|
||||
import com.dano.test1.util.LanguageManager
|
||||
import org.apache.poi.ss.usermodel.CellType
|
||||
import org.apache.poi.ss.usermodel.DateUtil
|
||||
import org.apache.poi.xssf.usermodel.XSSFWorkbook
|
||||
@ -1,4 +1,4 @@
|
||||
package com.dano.test1
|
||||
package com.dano.test1.network
|
||||
|
||||
import java.io.File
|
||||
import java.security.SecureRandom
|
||||
@ -2,7 +2,6 @@ package com.dano.test1.network
|
||||
|
||||
import android.content.Context
|
||||
import android.util.Log
|
||||
import com.dano.test1.AES256Helper
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.launch
|
||||
|
||||
@ -8,7 +8,6 @@ import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.launch
|
||||
import android.database.Cursor
|
||||
import com.dano.test1.AES256Helper
|
||||
import okhttp3.*
|
||||
import okhttp3.MediaType.Companion.toMediaType
|
||||
import okhttp3.RequestBody.Companion.asRequestBody
|
||||
|
||||
@ -1,15 +1,12 @@
|
||||
package com.dano.test1.questionnaire.handlers
|
||||
package com.dano.test1.questionnaire
|
||||
|
||||
import android.view.View
|
||||
import android.widget.*
|
||||
import com.dano.test1.questionnaire.GlobalValues
|
||||
import com.dano.test1.LanguageManager
|
||||
import com.dano.test1.util.LanguageManager
|
||||
import com.dano.test1.util.setTextSizePercentOfScreenHeight
|
||||
import com.dano.test1.MyApp
|
||||
import com.dano.test1.questionnaire.QuestionHandler
|
||||
import com.dano.test1.questionnaire.QuestionItem
|
||||
import com.dano.test1.R
|
||||
import com.dano.test1.network.TokenStore
|
||||
import com.dano.test1.utils.ViewUtils
|
||||
import com.dano.test1.auth.TokenStore
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.launch
|
||||
@ -44,10 +41,10 @@ class HandlerClientCoachCode(
|
||||
|
||||
questionTextView.text = question.question?.let { LanguageManager.getText(languageID, it) } ?: ""
|
||||
|
||||
ViewUtils.setTextSizePercentOfScreenHeight(titleTextView, 0.03f)
|
||||
ViewUtils.setTextSizePercentOfScreenHeight(questionTextView, 0.03f)
|
||||
ViewUtils.setTextSizePercentOfScreenHeight(clientCodeField, 0.025f)
|
||||
ViewUtils.setTextSizePercentOfScreenHeight(coachCodeField, 0.025f)
|
||||
titleTextView.setTextSizePercentOfScreenHeight(0.03f)
|
||||
questionTextView.setTextSizePercentOfScreenHeight(0.03f)
|
||||
clientCodeField.setTextSizePercentOfScreenHeight(0.025f)
|
||||
coachCodeField.setTextSizePercentOfScreenHeight(0.025f)
|
||||
|
||||
// Client-Code: nur verwenden, wenn bereits geladen
|
||||
val loadedClientCode = GlobalValues.LOADED_CLIENT_CODE
|
||||
@ -63,7 +60,7 @@ class HandlerClientCoachCode(
|
||||
val coachFromLogin = TokenStore.getUsername(layout.context)
|
||||
if (!coachFromLogin.isNullOrBlank()) {
|
||||
coachCodeField.setText(coachFromLogin)
|
||||
ViewUtils.lockEditField(coachCodeField) // optisch & technisch gesperrt
|
||||
lockCoachField(coachCodeField) // optisch & technisch gesperrt
|
||||
} else {
|
||||
// Falls (theoretisch) kein Login-Username vorhanden ist, verhalten wie bisher
|
||||
coachCodeField.setText(answers["coach_code"] as? String ?: "")
|
||||
@ -96,7 +93,7 @@ class HandlerClientCoachCode(
|
||||
val dbExistedBefore = dbPath.exists()
|
||||
|
||||
CoroutineScope(Dispatchers.IO).launch {
|
||||
val existingClient = MyApp.database.clientDao().getClientByCode(clientCode)
|
||||
val existingClient = MyApp.Companion.database.clientDao().getClientByCode(clientCode)
|
||||
|
||||
withContext(Dispatchers.Main) {
|
||||
if (existingClient != null && clientCodeField.isEnabled) {
|
||||
@ -107,7 +104,7 @@ class HandlerClientCoachCode(
|
||||
goToNextQuestion()
|
||||
|
||||
if (!dbExistedBefore) {
|
||||
MyApp.database.close()
|
||||
MyApp.Companion.database.close()
|
||||
dbPath.delete()
|
||||
val journalFile = layout.context.getDatabasePath("questionnaire_database-journal")
|
||||
journalFile.delete()
|
||||
@ -142,4 +139,19 @@ class HandlerClientCoachCode(
|
||||
// Not used
|
||||
}
|
||||
|
||||
private fun lockCoachField(field: EditText) {
|
||||
field.isFocusable = false
|
||||
field.isFocusableInTouchMode = false
|
||||
field.isCursorVisible = false
|
||||
field.keyListener = null
|
||||
field.isLongClickable = false
|
||||
field.isClickable = false
|
||||
field.setBackgroundResource(R.drawable.bg_field_locked)
|
||||
field.setCompoundDrawablesWithIntrinsicBounds(0, 0, R.drawable.ic_lock_24, 0)
|
||||
field.compoundDrawablePadding = dp(8)
|
||||
field.alpha = 0.95f
|
||||
}
|
||||
|
||||
private fun dp(v: Int): Int =
|
||||
(v * layout.resources.displayMetrics.density).toInt()
|
||||
}
|
||||
@ -1,10 +1,8 @@
|
||||
package com.dano.test1.questionnaire.handlers
|
||||
package com.dano.test1.questionnaire
|
||||
|
||||
import android.view.View
|
||||
import android.widget.*
|
||||
import com.dano.test1.LanguageManager
|
||||
import com.dano.test1.questionnaire.QuestionHandler
|
||||
import com.dano.test1.questionnaire.QuestionItem
|
||||
import com.dano.test1.util.LanguageManager
|
||||
import com.dano.test1.R
|
||||
|
||||
/*
|
||||
@ -1,4 +1,4 @@
|
||||
package com.dano.test1.questionnaire.handlers
|
||||
package com.dano.test1.questionnaire
|
||||
|
||||
import android.content.Context
|
||||
import android.view.View
|
||||
@ -6,16 +6,13 @@ import android.widget.*
|
||||
import kotlinx.coroutines.*
|
||||
import java.text.SimpleDateFormat
|
||||
import java.util.*
|
||||
import com.dano.test1.questionnaire.GlobalValues
|
||||
import com.dano.test1.LanguageManager
|
||||
import com.dano.test1.questionnaire.MAX_VALUE_YEAR
|
||||
import com.dano.test1.ui.Month
|
||||
import com.dano.test1.ui.Months
|
||||
import com.dano.test1.util.LanguageManager
|
||||
import com.dano.test1.util.setTextSizePercentOfScreenHeight
|
||||
import com.dano.test1.util.setupSpinner
|
||||
import com.dano.test1.util.Month
|
||||
import com.dano.test1.util.Months
|
||||
import com.dano.test1.MyApp
|
||||
import com.dano.test1.questionnaire.QuestionHandler
|
||||
import com.dano.test1.questionnaire.QuestionItem
|
||||
import com.dano.test1.R
|
||||
import com.dano.test1.utils.ViewUtils
|
||||
|
||||
/*
|
||||
Zweck:
|
||||
@ -57,12 +54,11 @@ class HandlerDateSpinner(
|
||||
questionTextView.text = question.question?.let { LanguageManager.getText(languageID, it) } ?: ""
|
||||
textView.text = question.textKey?.let { LanguageManager.getText(languageID, it) } ?: ""
|
||||
|
||||
// Schriftgrößen pro Bildschirmhöhe
|
||||
ViewUtils.setTextSizePercentOfScreenHeight(textView, 0.03f)
|
||||
ViewUtils.setTextSizePercentOfScreenHeight(questionTextView, 0.03f)
|
||||
ViewUtils.setTextSizePercentOfScreenHeight(labelDay, 0.025f)
|
||||
ViewUtils.setTextSizePercentOfScreenHeight(labelMonth, 0.025f)
|
||||
ViewUtils.setTextSizePercentOfScreenHeight(labelYear, 0.025f)
|
||||
textView.setTextSizePercentOfScreenHeight(0.03f)
|
||||
questionTextView.setTextSizePercentOfScreenHeight(0.03f)
|
||||
labelDay.setTextSizePercentOfScreenHeight(0.025f)
|
||||
labelMonth.setTextSizePercentOfScreenHeight(0.025f)
|
||||
labelYear.setTextSizePercentOfScreenHeight(0.025f)
|
||||
|
||||
// gespeicherte Antwort (YYYY-MM-DD) lesen
|
||||
val (savedYear, savedMonthIndex, savedDay) = question.question?.let {
|
||||
@ -79,10 +75,9 @@ class HandlerDateSpinner(
|
||||
?: months[today.get(Calendar.MONTH)]
|
||||
val defaultYear = savedYear ?: today.get(Calendar.YEAR)
|
||||
|
||||
// Spinner responsiv aufsetzen (Schrift + Zeilenhöhe ohne Abschneiden)
|
||||
ViewUtils.setupResponsiveSpinner(context, spinnerDay, days, defaultDay)
|
||||
ViewUtils.setupResponsiveSpinner(context, spinnerMonth, months, defaultMonth)
|
||||
ViewUtils.setupResponsiveSpinner(context, spinnerYear, years, defaultYear)
|
||||
spinnerDay.setupSpinner(days, defaultDay)
|
||||
spinnerMonth.setupSpinner(months, defaultMonth)
|
||||
spinnerYear.setupSpinner(years, defaultYear)
|
||||
|
||||
// DB-Abfrage, falls noch nicht im answers-Map
|
||||
val answerMapKey = question.question ?: (question.id ?: "")
|
||||
@ -92,7 +87,7 @@ class HandlerDateSpinner(
|
||||
val clientCode = GlobalValues.LAST_CLIENT_CODE
|
||||
if (clientCode.isNullOrBlank()) return@launch
|
||||
|
||||
val allAnswersForClient = MyApp.database.answerDao().getAnswersForClient(clientCode)
|
||||
val allAnswersForClient = MyApp.Companion.database.answerDao().getAnswersForClient(clientCode)
|
||||
val myQuestionId = questionnaireMeta + "-" + question.question
|
||||
val dbAnswer = allAnswersForClient.find { it.questionId == myQuestionId }?.answerValue
|
||||
|
||||
@ -1,16 +1,14 @@
|
||||
package com.dano.test1.questionnaire.handlers
|
||||
package com.dano.test1.questionnaire
|
||||
|
||||
import android.content.Context
|
||||
import android.util.TypedValue
|
||||
import android.view.Gravity
|
||||
import android.view.View
|
||||
import android.widget.*
|
||||
import com.dano.test1.questionnaire.GlobalValues
|
||||
import com.dano.test1.LanguageManager
|
||||
import com.dano.test1.util.LanguageManager
|
||||
import com.dano.test1.util.setTextSizePercentOfScreenHeight
|
||||
import com.dano.test1.MyApp
|
||||
import com.dano.test1.questionnaire.QuestionHandler
|
||||
import com.dano.test1.questionnaire.QuestionItem
|
||||
import com.dano.test1.R
|
||||
import com.dano.test1.utils.ViewUtils
|
||||
import kotlinx.coroutines.*
|
||||
|
||||
/*
|
||||
@ -72,8 +70,8 @@ class HandlerGlassScaleQuestion(
|
||||
titleTv.text = question.textKey?.let { LanguageManager.getText(languageID, it) } ?: ""
|
||||
questionTv.text = question.question?.let { LanguageManager.getText(languageID, it) } ?: ""
|
||||
|
||||
ViewUtils.setTextSizePercentOfScreenHeight(titleTv, 0.03f)
|
||||
ViewUtils.setTextSizePercentOfScreenHeight(questionTv, 0.03f)
|
||||
titleTv.setTextSizePercentOfScreenHeight(0.03f)
|
||||
questionTv.setTextSizePercentOfScreenHeight(0.03f)
|
||||
|
||||
// Header Icons
|
||||
val header = layout.findViewById<LinearLayout>(R.id.glass_header)
|
||||
@ -107,7 +105,7 @@ class HandlerGlassScaleQuestion(
|
||||
CoroutineScope(Dispatchers.IO).launch {
|
||||
try {
|
||||
val clientCode = GlobalValues.LAST_CLIENT_CODE ?: return@launch
|
||||
val allAnswersForClient = MyApp.database.answerDao().getAnswersForClient(clientCode)
|
||||
val allAnswersForClient = MyApp.Companion.database.answerDao().getAnswersForClient(clientCode)
|
||||
val answerMap = allAnswersForClient.associateBy({ it.questionId }, { it.answerValue })
|
||||
|
||||
withContext(Dispatchers.Main) {
|
||||
@ -155,7 +153,7 @@ class HandlerGlassScaleQuestion(
|
||||
text = LanguageManager.getText(languageID, symptomKey)
|
||||
layoutParams = TableRow.LayoutParams(0, TableRow.LayoutParams.WRAP_CONTENT, 4f)
|
||||
setPadding(4, 16, 4, 16)
|
||||
ViewUtils.setTextSizePercentOfScreenHeight(this, 0.022f)
|
||||
setTextSizePercentOfScreenHeight(0.022f)
|
||||
}
|
||||
row.addView(symptomText)
|
||||
|
||||
@ -1,18 +1,15 @@
|
||||
package com.dano.test1.questionnaire.handlers
|
||||
package com.dano.test1.questionnaire
|
||||
|
||||
import android.util.TypedValue
|
||||
import android.view.View
|
||||
import android.widget.*
|
||||
import android.text.Html
|
||||
import androidx.core.widget.TextViewCompat
|
||||
import kotlinx.coroutines.*
|
||||
import com.dano.test1.questionnaire.GlobalValues
|
||||
import com.dano.test1.LanguageManager
|
||||
import android.util.TypedValue
|
||||
import androidx.core.widget.TextViewCompat
|
||||
import com.dano.test1.util.LanguageManager
|
||||
import com.dano.test1.util.setTextSizePercentOfScreenHeight
|
||||
import com.dano.test1.MainActivity
|
||||
import com.dano.test1.questionnaire.QuestionHandler
|
||||
import com.dano.test1.questionnaire.QuestionItem
|
||||
import com.dano.test1.R
|
||||
import com.dano.test1.utils.ViewUtils
|
||||
import com.google.android.material.button.MaterialButton
|
||||
|
||||
/*
|
||||
@ -63,9 +60,8 @@ class HandlerLastPage(
|
||||
finishBtn.isAllCaps = false
|
||||
applyResponsiveTextSizing(finishBtn)
|
||||
|
||||
// Überschriften responsiv skalieren (wie zuvor)
|
||||
ViewUtils.setTextSizePercentOfScreenHeight(titleTv, 0.03f)
|
||||
ViewUtils.setTextSizePercentOfScreenHeight(questionTv, 0.03f)
|
||||
titleTv.setTextSizePercentOfScreenHeight(0.03f)
|
||||
questionTv.setTextSizePercentOfScreenHeight(0.03f)
|
||||
|
||||
// Buttons
|
||||
prevBtn.setOnClickListener { goToPreviousQuestion() }
|
||||
@ -1,18 +1,15 @@
|
||||
package com.dano.test1.questionnaire.handlers
|
||||
package com.dano.test1.questionnaire
|
||||
|
||||
import android.content.Context
|
||||
import android.util.TypedValue
|
||||
import android.view.View
|
||||
import android.widget.*
|
||||
import androidx.core.widget.TextViewCompat
|
||||
import kotlinx.coroutines.*
|
||||
import com.dano.test1.questionnaire.GlobalValues
|
||||
import com.dano.test1.LanguageManager
|
||||
import android.util.TypedValue
|
||||
import androidx.core.widget.TextViewCompat
|
||||
import com.dano.test1.util.LanguageManager
|
||||
import com.dano.test1.util.setTextSizePercentOfScreenHeight
|
||||
import com.dano.test1.MyApp
|
||||
import com.dano.test1.questionnaire.QuestionHandler
|
||||
import com.dano.test1.questionnaire.QuestionItem
|
||||
import com.dano.test1.R
|
||||
import com.dano.test1.utils.ViewUtils
|
||||
|
||||
/*
|
||||
Zweck:
|
||||
@ -44,9 +41,8 @@ class HandlerMultiCheckboxQuestion(
|
||||
questionTextView.text = this.question.textKey?.let { LanguageManager.getText(languageID, it) } ?: ""
|
||||
questionTitle.text = this.question.question?.let { LanguageManager.getText(languageID, it) } ?: ""
|
||||
|
||||
// Textgrößen pro Bildschirmhöhe (wie bei deinen anderen Handlern)
|
||||
ViewUtils.setTextSizePercentOfScreenHeight(questionTextView, 0.03f)
|
||||
ViewUtils.setTextSizePercentOfScreenHeight(questionTitle, 0.03f)
|
||||
questionTextView.setTextSizePercentOfScreenHeight(0.03f)
|
||||
questionTitle.setTextSizePercentOfScreenHeight(0.03f)
|
||||
|
||||
container.removeAllViews()
|
||||
|
||||
@ -95,7 +91,7 @@ class HandlerMultiCheckboxQuestion(
|
||||
val clientCode = GlobalValues.LAST_CLIENT_CODE
|
||||
if (clientCode.isNullOrBlank()) return@launch
|
||||
|
||||
val allAnswersForClient = MyApp.database.answerDao().getAnswersForClient(clientCode)
|
||||
val allAnswersForClient = MyApp.Companion.database.answerDao().getAnswersForClient(clientCode)
|
||||
val myQuestionId = questionnaireMeta + "-" + question.question
|
||||
val dbAnswer = allAnswersForClient.find { it.questionId == myQuestionId }?.answerValue
|
||||
|
||||
@ -1,17 +1,14 @@
|
||||
package com.dano.test1.questionnaire.handlers
|
||||
package com.dano.test1.questionnaire
|
||||
|
||||
import android.content.Context
|
||||
import android.view.View
|
||||
import android.text.Html
|
||||
import android.widget.*
|
||||
import kotlinx.coroutines.*
|
||||
import com.dano.test1.questionnaire.GlobalValues
|
||||
import com.dano.test1.LanguageManager
|
||||
import com.dano.test1.util.LanguageManager
|
||||
import com.dano.test1.util.setTextSizePercentOfScreenHeight
|
||||
import com.dano.test1.MyApp
|
||||
import com.dano.test1.questionnaire.QuestionHandler
|
||||
import com.dano.test1.questionnaire.QuestionItem
|
||||
import com.dano.test1.R
|
||||
import com.dano.test1.utils.ViewUtils
|
||||
|
||||
/*
|
||||
Zweck:
|
||||
@ -46,8 +43,8 @@ class HandlerRadioQuestion(
|
||||
Html.fromHtml(LanguageManager.getText(languageID, it), Html.FROM_HTML_MODE_LEGACY)
|
||||
} ?: ""
|
||||
|
||||
ViewUtils.setTextSizePercentOfScreenHeight(questionTextView, 0.03f)
|
||||
ViewUtils.setTextSizePercentOfScreenHeight(questionTitle, 0.03f)
|
||||
questionTextView.setTextSizePercentOfScreenHeight(0.03f)
|
||||
questionTitle.setTextSizePercentOfScreenHeight(0.03f)
|
||||
|
||||
radioGroup.removeAllViews()
|
||||
|
||||
@ -56,8 +53,7 @@ class HandlerRadioQuestion(
|
||||
text = LanguageManager.getText(languageID, option.key)
|
||||
tag = option.key
|
||||
|
||||
// RadioButton-Text analog zu EditTexts: 2.5% der Bildschirmhöhe
|
||||
ViewUtils.setTextSizePercentOfScreenHeight(this, 0.025f)
|
||||
setTextSizePercentOfScreenHeight(0.025f)
|
||||
|
||||
layoutParams = RadioGroup.LayoutParams(
|
||||
RadioGroup.LayoutParams.MATCH_PARENT,
|
||||
@ -83,7 +79,7 @@ class HandlerRadioQuestion(
|
||||
val clientCode = GlobalValues.LAST_CLIENT_CODE
|
||||
if (clientCode.isNullOrBlank()) return@launch
|
||||
|
||||
val allAnswersForClient = MyApp.database.answerDao().getAnswersForClient(clientCode)
|
||||
val allAnswersForClient = MyApp.Companion.database.answerDao().getAnswersForClient(clientCode)
|
||||
val myQuestionId = questionnaireMeta + "-" + question.question
|
||||
val dbAnswer = allAnswersForClient.find { it.questionId == myQuestionId }?.answerValue
|
||||
|
||||
@ -1,17 +1,15 @@
|
||||
package com.dano.test1.questionnaire.handlers
|
||||
package com.dano.test1.questionnaire
|
||||
|
||||
import android.content.Context
|
||||
import android.view.View
|
||||
import android.widget.*
|
||||
import kotlinx.coroutines.*
|
||||
import com.dano.test1.ui.Countries
|
||||
import com.dano.test1.questionnaire.GlobalValues
|
||||
import com.dano.test1.LanguageManager
|
||||
import com.dano.test1.util.LanguageManager
|
||||
import com.dano.test1.util.setTextSizePercentOfScreenHeight
|
||||
import com.dano.test1.util.setupSpinner
|
||||
import com.dano.test1.MyApp
|
||||
import com.dano.test1.questionnaire.QuestionHandler
|
||||
import com.dano.test1.questionnaire.QuestionItem
|
||||
import com.dano.test1.R
|
||||
import com.dano.test1.utils.ViewUtils
|
||||
import com.dano.test1.util.Countries
|
||||
|
||||
/*
|
||||
Zweck:
|
||||
@ -46,17 +44,15 @@ class HandlerStringSpinner(
|
||||
questionTextView.text = question.question?.let { LanguageManager.getText(languageID, it) } ?: ""
|
||||
textView.text = question.textKey?.let { LanguageManager.getText(languageID, it) } ?: ""
|
||||
|
||||
// Textgrößen prozentual zur Bildschirmhöhe
|
||||
ViewUtils.setTextSizePercentOfScreenHeight(textView, 0.03f)
|
||||
ViewUtils.setTextSizePercentOfScreenHeight(questionTextView, 0.03f)
|
||||
textView.setTextSizePercentOfScreenHeight(0.03f)
|
||||
questionTextView.setTextSizePercentOfScreenHeight(0.03f)
|
||||
|
||||
val options = buildOptionsList()
|
||||
|
||||
// vorhandene Auswahl (falls vorhanden)
|
||||
val savedSelection = question.question?.let { answers[it] as? String }
|
||||
|
||||
// Spinner aufsetzen
|
||||
ViewUtils.setupResponsiveSpinner(context, spinner, options, savedSelection)
|
||||
spinner.setupSpinner(options, savedSelection)
|
||||
|
||||
// Falls noch keine Antwort im Map: aus DB laden
|
||||
val answerMapKey = question.question ?: (question.id ?: "")
|
||||
@ -66,7 +62,7 @@ class HandlerStringSpinner(
|
||||
val clientCode = GlobalValues.LAST_CLIENT_CODE
|
||||
if (clientCode.isNullOrBlank()) return@launch
|
||||
|
||||
val allAnswersForClient = MyApp.database.answerDao().getAnswersForClient(clientCode)
|
||||
val allAnswersForClient = MyApp.Companion.database.answerDao().getAnswersForClient(clientCode)
|
||||
val myQuestionId = questionnaireMeta + "-" + question.question
|
||||
val dbAnswer = allAnswersForClient.find { it.questionId == myQuestionId }?.answerValue
|
||||
|
||||
@ -1,16 +1,14 @@
|
||||
package com.dano.test1.questionnaire.handlers
|
||||
package com.dano.test1.questionnaire
|
||||
|
||||
import android.content.Context
|
||||
import android.view.View
|
||||
import android.widget.*
|
||||
import kotlinx.coroutines.*
|
||||
import com.dano.test1.questionnaire.GlobalValues
|
||||
import com.dano.test1.LanguageManager
|
||||
import com.dano.test1.util.LanguageManager
|
||||
import com.dano.test1.util.setTextSizePercentOfScreenHeight
|
||||
import com.dano.test1.util.setupSpinner
|
||||
import com.dano.test1.MyApp
|
||||
import com.dano.test1.questionnaire.QuestionHandler
|
||||
import com.dano.test1.questionnaire.QuestionItem
|
||||
import com.dano.test1.R
|
||||
import com.dano.test1.utils.ViewUtils
|
||||
|
||||
/*
|
||||
Zweck:
|
||||
@ -47,8 +45,8 @@ class HandlerValueSpinner(
|
||||
questionTextView.text = question.question?.let { LanguageManager.getText(languageID, it) } ?: ""
|
||||
textView.text = question.textKey?.let { LanguageManager.getText(languageID, it) } ?: ""
|
||||
|
||||
ViewUtils.setTextSizePercentOfScreenHeight(textView, 0.03f)
|
||||
ViewUtils.setTextSizePercentOfScreenHeight(questionTextView, 0.03f)
|
||||
textView.setTextSizePercentOfScreenHeight(0.03f)
|
||||
questionTextView.setTextSizePercentOfScreenHeight(0.03f)
|
||||
|
||||
val prompt = LanguageManager.getText(languageID, "choose_answer")
|
||||
val spinnerItems: List<String> = listOf(prompt) + if (question.range != null) {
|
||||
@ -58,7 +56,7 @@ class HandlerValueSpinner(
|
||||
}
|
||||
|
||||
val savedValue = question.question?.let { answers[it] as? String }
|
||||
ViewUtils.setupResponsiveSpinner(context, spinner, spinnerItems, savedValue)
|
||||
spinner.setupSpinner(spinnerItems, savedValue)
|
||||
|
||||
//DB-Abfrage falls noch keine Antwort im Map existiert
|
||||
val answerMapKey = question.question ?: (question.id ?: "")
|
||||
@ -68,7 +66,7 @@ class HandlerValueSpinner(
|
||||
val clientCode = GlobalValues.LAST_CLIENT_CODE
|
||||
if (clientCode.isNullOrBlank()) return@launch
|
||||
|
||||
val allAnswersForClient = MyApp.database.answerDao().getAnswersForClient(clientCode)
|
||||
val allAnswersForClient = MyApp.Companion.database.answerDao().getAnswersForClient(clientCode)
|
||||
val myQuestionId = questionnaireMeta + "-" + question.question
|
||||
val dbAnswer = allAnswersForClient.find { it.questionId == myQuestionId }?.answerValue
|
||||
|
||||
@ -1,554 +0,0 @@
|
||||
package com.dano.test1.questionnaire
|
||||
|
||||
import android.graphics.Color
|
||||
import android.text.Editable
|
||||
import android.text.TextWatcher
|
||||
import android.util.TypedValue
|
||||
import android.view.LayoutInflater
|
||||
import android.view.View
|
||||
import android.view.ViewGroup
|
||||
import android.widget.*
|
||||
import androidx.constraintlayout.widget.ConstraintLayout
|
||||
import com.dano.test1.LanguageManager
|
||||
import com.dano.test1.LocalizationHelper
|
||||
import com.dano.test1.MainActivity
|
||||
import com.dano.test1.R
|
||||
import com.dano.test1.network.TokenStore
|
||||
import com.dano.test1.utils.ViewUtils
|
||||
import com.google.android.material.button.MaterialButton
|
||||
import com.google.android.material.card.MaterialCardView
|
||||
import kotlinx.coroutines.*
|
||||
|
||||
class QuestionnaireAllInOne(private val questionnaireFileName: String) : QuestionnaireBase<Unit>() {
|
||||
|
||||
private data class Section(
|
||||
val index: Int,
|
||||
val question: QuestionItem,
|
||||
val card: MaterialCardView,
|
||||
val sectionView: View,
|
||||
val handler: QuestionHandler?
|
||||
)
|
||||
|
||||
private val sections = mutableListOf<Section>()
|
||||
private lateinit var container: LinearLayout
|
||||
private lateinit var btnSave: MaterialButton
|
||||
private var setupComplete = false
|
||||
|
||||
override fun startQuestionnaire() {
|
||||
val (meta, questionsList) = loadQuestionnaireFromJson(questionnaireFileName)
|
||||
questionnaireMeta = meta
|
||||
questions = questionsList
|
||||
currentIndex = 0
|
||||
buildAllInOneUi()
|
||||
}
|
||||
|
||||
override fun showCurrentQuestion() {
|
||||
// Not used in all-in-one mode
|
||||
}
|
||||
|
||||
private fun buildAllInOneUi() {
|
||||
sections.clear()
|
||||
setupComplete = false
|
||||
|
||||
context.setContentView(R.layout.questionnaire_all_in_one)
|
||||
|
||||
container = context.findViewById(R.id.questionContainer)
|
||||
btnSave = context.findViewById(R.id.btnSave)
|
||||
val btnBack = context.findViewById<MaterialButton>(R.id.btnBack)
|
||||
val progressBar = context.findViewById<ProgressBar>(R.id.progressBar)
|
||||
|
||||
btnSave.text = LanguageManager.getText(languageID, "save")
|
||||
ViewUtils.setTextSizePercentOfScreenHeight(btnSave, 0.025f)
|
||||
|
||||
btnSave.isEnabled = false
|
||||
btnSave.alpha = 0.5f
|
||||
|
||||
btnBack.setOnClickListener {
|
||||
(context as? MainActivity)?.finishQuestionnaire()
|
||||
}
|
||||
|
||||
setupLanguageSpinner(context.findViewById(R.id.langSpinner))
|
||||
|
||||
val inflater = LayoutInflater.from(context)
|
||||
|
||||
for ((idx, question) in questions.withIndex()) {
|
||||
if (question is QuestionItem.LastPage) continue
|
||||
|
||||
val layoutResId = getLayoutResId(question.layout ?: "default_layout")
|
||||
if (layoutResId == 0) continue
|
||||
|
||||
val sectionView = inflater.inflate(layoutResId, container, false)
|
||||
adaptLayoutForEmbedding(sectionView)
|
||||
LocalizationHelper.localizeViewTree(sectionView, languageID)
|
||||
|
||||
val card = wrapInCard(sectionView)
|
||||
container.addView(card)
|
||||
|
||||
val handler = createEmbeddedHandler(question)
|
||||
handler?.bind(sectionView, question)
|
||||
reduceTextSizes(sectionView)
|
||||
|
||||
sections.add(Section(idx, question, card, sectionView, handler))
|
||||
}
|
||||
|
||||
installChangeListeners()
|
||||
recalculateVisibility()
|
||||
|
||||
// Allow DB restores to finish before enabling interaction tracking
|
||||
container.postDelayed({
|
||||
setupComplete = true
|
||||
recalculateVisibility()
|
||||
updateSaveButtonState()
|
||||
}, 600)
|
||||
|
||||
btnSave.setOnClickListener {
|
||||
handleSave(progressBar)
|
||||
}
|
||||
}
|
||||
|
||||
private fun setupLanguageSpinner(spinner: Spinner) {
|
||||
val adapter = ArrayAdapter(context, android.R.layout.simple_spinner_item, LANGUAGE_LABELS).apply {
|
||||
setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item)
|
||||
}
|
||||
spinner.adapter = adapter
|
||||
spinner.setSelection(LANGUAGE_IDS.indexOf(languageID).coerceAtLeast(0))
|
||||
|
||||
spinner.onItemSelectedListener = object : AdapterView.OnItemSelectedListener {
|
||||
override fun onItemSelected(parent: AdapterView<*>?, v: View?, position: Int, id: Long) {
|
||||
val newLang = LANGUAGE_IDS[position]
|
||||
if (newLang == languageID) return
|
||||
spinner.post { onLanguageChanged(newLang) }
|
||||
}
|
||||
override fun onNothingSelected(parent: AdapterView<*>?) {}
|
||||
}
|
||||
}
|
||||
|
||||
private fun onLanguageChanged(newLang: String) {
|
||||
for (section in sections) {
|
||||
if (section.card.visibility == View.VISIBLE) {
|
||||
try { section.handler?.saveAnswer() } catch (_: Exception) {}
|
||||
}
|
||||
}
|
||||
languageID = newLang
|
||||
buildAllInOneUi()
|
||||
}
|
||||
|
||||
private fun createEmbeddedHandler(question: QuestionItem): QuestionHandler? {
|
||||
val noop = {}
|
||||
val noopId: (String) -> Unit = {}
|
||||
val toast: (String) -> Unit = { showToast(it) }
|
||||
val metaId = questionnaireMeta.id
|
||||
|
||||
return when (question) {
|
||||
is QuestionItem.RadioQuestion ->
|
||||
com.dano.test1.questionnaire.handlers.HandlerRadioQuestion(
|
||||
context, answers, points, languageID, noop, noop, noopId, toast, metaId
|
||||
)
|
||||
is QuestionItem.ClientCoachCodeQuestion ->
|
||||
com.dano.test1.questionnaire.handlers.HandlerClientCoachCode(
|
||||
answers, languageID, noop, noop, toast
|
||||
)
|
||||
is QuestionItem.DateSpinnerQuestion ->
|
||||
com.dano.test1.questionnaire.handlers.HandlerDateSpinner(
|
||||
context, answers, languageID, noop, noop, toast, metaId
|
||||
)
|
||||
is QuestionItem.ValueSpinnerQuestion ->
|
||||
com.dano.test1.questionnaire.handlers.HandlerValueSpinner(
|
||||
context, answers, languageID, noop, noop, noopId, toast, metaId
|
||||
)
|
||||
is QuestionItem.GlassScaleQuestion ->
|
||||
com.dano.test1.questionnaire.handlers.HandlerGlassScaleQuestion(
|
||||
context, answers, points, languageID, noop, noop, toast, metaId
|
||||
)
|
||||
is QuestionItem.ClientNotSigned ->
|
||||
com.dano.test1.questionnaire.handlers.HandlerClientNotSigned(
|
||||
answers, languageID, noop, noop, toast
|
||||
)
|
||||
is QuestionItem.StringSpinnerQuestion ->
|
||||
com.dano.test1.questionnaire.handlers.HandlerStringSpinner(
|
||||
context, answers, languageID, noop, noop, toast, metaId
|
||||
)
|
||||
is QuestionItem.MultiCheckboxQuestion ->
|
||||
com.dano.test1.questionnaire.handlers.HandlerMultiCheckboxQuestion(
|
||||
context, answers, points, languageID, noop, noop, toast, metaId
|
||||
)
|
||||
else -> null
|
||||
}
|
||||
}
|
||||
|
||||
private fun adaptLayoutForEmbedding(view: View) {
|
||||
if (view is ConstraintLayout) {
|
||||
view.layoutParams = LinearLayout.LayoutParams(
|
||||
LinearLayout.LayoutParams.MATCH_PARENT,
|
||||
LinearLayout.LayoutParams.WRAP_CONTENT
|
||||
)
|
||||
// Ensure the last field in the card is never clipped at the bottom
|
||||
view.setPadding(
|
||||
view.paddingLeft, view.paddingTop,
|
||||
view.paddingRight, ViewUtils.dp(context, 12)
|
||||
)
|
||||
}
|
||||
|
||||
view.findViewById<View>(R.id.Qprev)?.visibility = View.GONE
|
||||
view.findViewById<View>(R.id.Qnext)?.visibility = View.GONE
|
||||
|
||||
view.findViewById<View>(R.id.gTop)?.let { guideline ->
|
||||
if (guideline is androidx.constraintlayout.widget.Guideline) {
|
||||
val params = guideline.layoutParams as? ConstraintLayout.LayoutParams
|
||||
params?.guideBegin = ViewUtils.dp(context, 8)
|
||||
guideline.layoutParams = params
|
||||
}
|
||||
}
|
||||
|
||||
adaptInnerScrollView(view, R.id.radioScroll)
|
||||
adaptInnerScrollView(view, R.id.scrollView)
|
||||
adaptInnerScrollView(view, R.id.glassScroll)
|
||||
|
||||
view.findViewById<EditText>(R.id.client_code)?.let { et ->
|
||||
val params = et.layoutParams as? ConstraintLayout.LayoutParams ?: return@let
|
||||
if (params.matchConstraintPercentHeight > 0f) {
|
||||
params.matchConstraintPercentHeight = 0f
|
||||
params.height = ViewUtils.dp(context, 64)
|
||||
et.layoutParams = params
|
||||
}
|
||||
}
|
||||
view.findViewById<EditText>(R.id.coach_code)?.let { et ->
|
||||
val params = et.layoutParams as? ConstraintLayout.LayoutParams ?: return@let
|
||||
if (params.matchConstraintPercentHeight > 0f) {
|
||||
params.matchConstraintPercentHeight = 0f
|
||||
params.height = ViewUtils.dp(context, 64)
|
||||
et.layoutParams = params
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Scales down all TextView text sizes by [factor] after the handler has set them.
|
||||
* Spinners manage their own adapter text sizes and are skipped.
|
||||
*/
|
||||
private fun reduceTextSizes(view: View, factor: Float = 0.82f) {
|
||||
if (view is Spinner) return
|
||||
if (view is TextView) {
|
||||
val dm = view.context.resources.displayMetrics
|
||||
val currentSp = view.textSize / dm.scaledDensity
|
||||
view.setTextSize(TypedValue.COMPLEX_UNIT_SP, currentSp * factor)
|
||||
}
|
||||
if (view is ViewGroup) {
|
||||
for (i in 0 until view.childCount) reduceTextSizes(view.getChildAt(i), factor)
|
||||
}
|
||||
}
|
||||
|
||||
private fun adaptInnerScrollView(root: View, scrollViewId: Int) {
|
||||
val sv = root.findViewById<ScrollView>(scrollViewId) ?: return
|
||||
val params = sv.layoutParams
|
||||
if (params is ConstraintLayout.LayoutParams) {
|
||||
params.height = ConstraintLayout.LayoutParams.WRAP_CONTENT
|
||||
params.bottomToTop = ConstraintLayout.LayoutParams.UNSET
|
||||
params.bottomToBottom = ConstraintLayout.LayoutParams.PARENT_ID
|
||||
sv.layoutParams = params
|
||||
}
|
||||
sv.isNestedScrollingEnabled = false
|
||||
}
|
||||
|
||||
private fun wrapInCard(sectionView: View): MaterialCardView {
|
||||
val card = MaterialCardView(context).apply {
|
||||
layoutParams = LinearLayout.LayoutParams(
|
||||
LinearLayout.LayoutParams.MATCH_PARENT,
|
||||
LinearLayout.LayoutParams.WRAP_CONTENT
|
||||
).apply {
|
||||
val margin = ViewUtils.dp(context, 6)
|
||||
setMargins(0, margin, 0, margin)
|
||||
}
|
||||
radius = ViewUtils.dp(context, 16).toFloat()
|
||||
cardElevation = ViewUtils.dp(context, 2).toFloat()
|
||||
setCardBackgroundColor(Color.WHITE)
|
||||
strokeColor = Color.parseColor("#D8D1F0")
|
||||
strokeWidth = ViewUtils.dp(context, 1)
|
||||
setContentPadding(
|
||||
ViewUtils.dp(context, 4),
|
||||
ViewUtils.dp(context, 8),
|
||||
ViewUtils.dp(context, 4),
|
||||
ViewUtils.dp(context, 12)
|
||||
)
|
||||
}
|
||||
card.addView(sectionView)
|
||||
return card
|
||||
}
|
||||
|
||||
// ---- Visibility walk algorithm (reads from UI state, not answers map) ----
|
||||
|
||||
private fun recalculateVisibility() {
|
||||
val visibleIndices = mutableSetOf<Int>()
|
||||
var i = 0
|
||||
|
||||
while (i < questions.size) {
|
||||
val q = questions[i]
|
||||
if (q is QuestionItem.LastPage) { i++; continue }
|
||||
|
||||
visibleIndices.add(i)
|
||||
|
||||
val nextTarget = resolveNextFromUi(q)
|
||||
when {
|
||||
nextTarget != null -> {
|
||||
if (questions.any { it is QuestionItem.LastPage && it.id == nextTarget }) {
|
||||
break
|
||||
}
|
||||
val targetIdx = questions.indexOfFirst { it.id == nextTarget }
|
||||
i = if (targetIdx != -1) targetIdx else i + 1
|
||||
}
|
||||
hasBranchingOptions(q) && !hasAnswerInUi(q) -> break
|
||||
else -> i++
|
||||
}
|
||||
}
|
||||
|
||||
for (section in sections) {
|
||||
val shouldShow = visibleIndices.contains(section.index)
|
||||
section.card.visibility = if (shouldShow) View.VISIBLE else View.GONE
|
||||
}
|
||||
}
|
||||
|
||||
private fun resolveNextFromUi(q: QuestionItem): String? {
|
||||
val section = sections.find { it.question === q } ?: return null
|
||||
val view = section.sectionView
|
||||
|
||||
return when (q) {
|
||||
is QuestionItem.RadioQuestion -> {
|
||||
val radioGroup = view.findViewById<RadioGroup>(R.id.RadioGroup) ?: return null
|
||||
val checkedId = radioGroup.checkedRadioButtonId
|
||||
if (checkedId == -1) return null
|
||||
val rb = radioGroup.findViewById<RadioButton>(checkedId) ?: return null
|
||||
val key = rb.tag?.toString() ?: return null
|
||||
q.options?.find { it.key == key }?.nextQuestionId
|
||||
}
|
||||
is QuestionItem.ValueSpinnerQuestion -> {
|
||||
val spinner = view.findViewById<Spinner>(R.id.value_spinner) ?: return null
|
||||
val selected = spinner.selectedItem?.toString() ?: return null
|
||||
val prompt = LanguageManager.getText(languageID, "choose_answer")
|
||||
if (selected == prompt) return null
|
||||
val selectedVal = selected.toIntOrNull()
|
||||
if (selectedVal != null) {
|
||||
q.options?.find { it.value == selectedVal }?.nextQuestionId
|
||||
} else null
|
||||
}
|
||||
else -> null
|
||||
}
|
||||
}
|
||||
|
||||
private fun hasBranchingOptions(q: QuestionItem): Boolean {
|
||||
return when (q) {
|
||||
is QuestionItem.RadioQuestion ->
|
||||
q.options?.any { it.nextQuestionId != null } ?: false
|
||||
is QuestionItem.ValueSpinnerQuestion ->
|
||||
q.options?.any { it.nextQuestionId != null } ?: false
|
||||
else -> false
|
||||
}
|
||||
}
|
||||
|
||||
private fun hasAnswerInUi(q: QuestionItem): Boolean {
|
||||
val section = sections.find { it.question === q } ?: return false
|
||||
val view = section.sectionView
|
||||
|
||||
return when (q) {
|
||||
is QuestionItem.RadioQuestion -> {
|
||||
val rg = view.findViewById<RadioGroup>(R.id.RadioGroup) ?: return false
|
||||
rg.checkedRadioButtonId != -1
|
||||
}
|
||||
is QuestionItem.ValueSpinnerQuestion -> {
|
||||
val spinner = view.findViewById<Spinner>(R.id.value_spinner) ?: return false
|
||||
val selected = spinner.selectedItem?.toString()
|
||||
val prompt = LanguageManager.getText(languageID, "choose_answer")
|
||||
!selected.isNullOrEmpty() && selected != prompt
|
||||
}
|
||||
else -> true
|
||||
}
|
||||
}
|
||||
|
||||
// ---- Change listeners ----
|
||||
|
||||
private fun installChangeListeners() {
|
||||
for (section in sections) {
|
||||
val view = section.sectionView
|
||||
|
||||
when (section.question) {
|
||||
is QuestionItem.RadioQuestion -> {
|
||||
view.findViewById<RadioGroup>(R.id.RadioGroup)
|
||||
?.setOnCheckedChangeListener { _, checkedId ->
|
||||
if (!setupComplete || checkedId == -1) return@setOnCheckedChangeListener
|
||||
recalculateVisibility()
|
||||
updateSaveButtonState()
|
||||
}
|
||||
}
|
||||
is QuestionItem.ValueSpinnerQuestion -> {
|
||||
installSpinnerChangeListener(view, R.id.value_spinner)
|
||||
}
|
||||
is QuestionItem.StringSpinnerQuestion -> {
|
||||
installSpinnerChangeListener(view, R.id.string_spinner)
|
||||
}
|
||||
is QuestionItem.DateSpinnerQuestion -> {
|
||||
val listener = createSimpleSpinnerListener()
|
||||
view.findViewById<Spinner>(R.id.spinner_value_day)?.onItemSelectedListener = listener
|
||||
view.findViewById<Spinner>(R.id.spinner_value_month)?.onItemSelectedListener = listener
|
||||
view.findViewById<Spinner>(R.id.spinner_value_year)?.onItemSelectedListener = listener
|
||||
}
|
||||
is QuestionItem.GlassScaleQuestion -> {
|
||||
installGlassScaleClickListeners(view)
|
||||
}
|
||||
is QuestionItem.MultiCheckboxQuestion -> {
|
||||
installCheckboxClickListeners(view)
|
||||
}
|
||||
is QuestionItem.ClientCoachCodeQuestion -> {
|
||||
installEditTextWatcher(view, R.id.client_code)
|
||||
installEditTextWatcher(view, R.id.coach_code)
|
||||
}
|
||||
is QuestionItem.ClientNotSigned -> {
|
||||
installEditTextWatcher(view, R.id.coach_code)
|
||||
}
|
||||
else -> {}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun installSpinnerChangeListener(view: View, spinnerId: Int) {
|
||||
view.findViewById<Spinner>(spinnerId)?.onItemSelectedListener =
|
||||
object : AdapterView.OnItemSelectedListener {
|
||||
override fun onItemSelected(
|
||||
parent: AdapterView<*>?, v: View?, pos: Int, id: Long
|
||||
) {
|
||||
if (!setupComplete) return
|
||||
recalculateVisibility()
|
||||
updateSaveButtonState()
|
||||
}
|
||||
override fun onNothingSelected(parent: AdapterView<*>?) {}
|
||||
}
|
||||
}
|
||||
|
||||
private fun createSimpleSpinnerListener(): AdapterView.OnItemSelectedListener {
|
||||
return object : AdapterView.OnItemSelectedListener {
|
||||
override fun onItemSelected(parent: AdapterView<*>?, v: View?, pos: Int, id: Long) {
|
||||
if (!setupComplete) return
|
||||
updateSaveButtonState()
|
||||
}
|
||||
override fun onNothingSelected(parent: AdapterView<*>?) {}
|
||||
}
|
||||
}
|
||||
|
||||
private fun installGlassScaleClickListeners(view: View) {
|
||||
val table = view.findViewById<TableLayout>(R.id.glass_table) ?: return
|
||||
for (r in 0 until table.childCount) {
|
||||
val row = table.getChildAt(r) as? TableRow ?: continue
|
||||
val radioGroup = row.getChildAt(1) as? RadioGroup ?: continue
|
||||
for (c in 0 until radioGroup.childCount) {
|
||||
val rb = getRadioFromChild(radioGroup.getChildAt(c)) ?: continue
|
||||
rb.setOnClickListener {
|
||||
if (setupComplete) updateSaveButtonState()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun getRadioFromChild(child: View): RadioButton? = when (child) {
|
||||
is RadioButton -> child
|
||||
is FrameLayout -> child.getChildAt(0) as? RadioButton
|
||||
else -> null
|
||||
}
|
||||
|
||||
private fun installCheckboxClickListeners(view: View) {
|
||||
val cont = view.findViewById<LinearLayout>(R.id.CheckboxContainer) ?: return
|
||||
for (i in 0 until cont.childCount) {
|
||||
val cb = cont.getChildAt(i) as? CheckBox ?: continue
|
||||
cb.setOnClickListener {
|
||||
if (setupComplete) updateSaveButtonState()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun installEditTextWatcher(view: View, editTextId: Int) {
|
||||
view.findViewById<EditText>(editTextId)?.addTextChangedListener(object : TextWatcher {
|
||||
override fun beforeTextChanged(s: CharSequence?, start: Int, count: Int, after: Int) {}
|
||||
override fun onTextChanged(s: CharSequence?, start: Int, before: Int, count: Int) {}
|
||||
override fun afterTextChanged(s: Editable?) {
|
||||
if (!setupComplete) return
|
||||
updateSaveButtonState()
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
// ---- Save button state ----
|
||||
|
||||
private fun updateSaveButtonState() {
|
||||
val allValid = sections
|
||||
.filter { it.card.visibility == View.VISIBLE }
|
||||
.all { section ->
|
||||
val handler = section.handler ?: return@all true
|
||||
try { handler.validate() } catch (_: Exception) { false }
|
||||
}
|
||||
btnSave.isEnabled = allValid
|
||||
btnSave.alpha = if (allValid) 1f else 0.5f
|
||||
}
|
||||
|
||||
// ---- Save flow ----
|
||||
|
||||
private fun handleSave(progressBar: ProgressBar) {
|
||||
val visibleSections = sections.filter { it.card.visibility == View.VISIBLE }
|
||||
|
||||
for (section in visibleSections) {
|
||||
val handler = section.handler ?: continue
|
||||
if (!handler.validate()) {
|
||||
showToast(LanguageManager.getText(languageID, "fill_all_fields"))
|
||||
scrollToSection(section)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
for (section in visibleSections) {
|
||||
if (section.question is QuestionItem.ClientCoachCodeQuestion) {
|
||||
saveClientCoachCodeFromUi(section.sectionView)
|
||||
}
|
||||
section.handler?.saveAnswer()
|
||||
}
|
||||
|
||||
showLoading(progressBar, true)
|
||||
|
||||
CoroutineScope(Dispatchers.IO).launch {
|
||||
val startTime = System.currentTimeMillis()
|
||||
|
||||
saveAnswersToDatabase(answers, questionnaireMeta.id)
|
||||
|
||||
GlobalValues.INTEGRATION_INDEX = points.sum()
|
||||
val clientCode = answers["client_code"] as? String
|
||||
if (clientCode != null) {
|
||||
GlobalValues.LAST_CLIENT_CODE = clientCode
|
||||
GlobalValues.LOADED_CLIENT_CODE = clientCode
|
||||
}
|
||||
|
||||
val elapsed = System.currentTimeMillis() - startTime
|
||||
if (elapsed < 1500L) delay(1500L - elapsed)
|
||||
|
||||
withContext(Dispatchers.Main) {
|
||||
showLoading(progressBar, false)
|
||||
endQuestionnaire()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun saveClientCoachCodeFromUi(view: View) {
|
||||
val clientCode = view.findViewById<EditText>(R.id.client_code)?.text?.toString() ?: ""
|
||||
val coachCode = view.findViewById<EditText>(R.id.coach_code)?.text?.toString() ?: ""
|
||||
GlobalValues.LAST_CLIENT_CODE = clientCode
|
||||
answers["client_code"] = clientCode
|
||||
answers["coach_code"] = TokenStore.getUsername(context) ?: coachCode
|
||||
}
|
||||
|
||||
private fun showLoading(progressBar: ProgressBar, show: Boolean) {
|
||||
progressBar.visibility = if (show) View.VISIBLE else View.GONE
|
||||
btnSave.isEnabled = !show
|
||||
btnSave.alpha = if (show) 0.5f else 1f
|
||||
}
|
||||
|
||||
private fun scrollToSection(section: Section) {
|
||||
val scrollView = context.findViewById<androidx.core.widget.NestedScrollView>(R.id.scrollContainer)
|
||||
scrollView?.post {
|
||||
scrollView.smoothScrollTo(0, section.card.top)
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -5,19 +5,10 @@ import android.app.Activity
|
||||
import android.util.Log
|
||||
import android.view.View
|
||||
import android.widget.*
|
||||
import com.dano.test1.LanguageManager
|
||||
import com.dano.test1.util.LanguageManager
|
||||
import com.dano.test1.MainActivity
|
||||
import com.dano.test1.MyApp
|
||||
import com.dano.test1.data.*
|
||||
import com.dano.test1.questionnaire.handlers.HandlerClientCoachCode
|
||||
import com.dano.test1.questionnaire.handlers.HandlerClientNotSigned
|
||||
import com.dano.test1.questionnaire.handlers.HandlerDateSpinner
|
||||
import com.dano.test1.questionnaire.handlers.HandlerGlassScaleQuestion
|
||||
import com.dano.test1.questionnaire.handlers.HandlerLastPage
|
||||
import com.dano.test1.questionnaire.handlers.HandlerMultiCheckboxQuestion
|
||||
import com.dano.test1.questionnaire.handlers.HandlerRadioQuestion
|
||||
import com.dano.test1.questionnaire.handlers.HandlerStringSpinner
|
||||
import com.dano.test1.questionnaire.handlers.HandlerValueSpinner
|
||||
import com.google.gson.Gson
|
||||
import com.google.gson.JsonParser
|
||||
import kotlinx.coroutines.*
|
||||
@ -40,17 +31,6 @@ abstract class QuestionnaireBase<T> {
|
||||
abstract fun startQuestionnaire()
|
||||
abstract fun showCurrentQuestion()
|
||||
|
||||
companion object {
|
||||
val LANGUAGE_IDS = listOf(
|
||||
"GERMAN", "ENGLISH", "FRENCH", "ROMANIAN", "ARABIC",
|
||||
"POLISH", "TURKISH", "UKRAINIAN", "RUSSIAN", "SPANISH"
|
||||
)
|
||||
val LANGUAGE_LABELS = listOf(
|
||||
"DE", "EN", "FR", "RO", "AR",
|
||||
"PL", "TR", "UA", "RU", "ES"
|
||||
)
|
||||
}
|
||||
|
||||
fun attach(activity: Activity, language: String) {
|
||||
this.context = activity
|
||||
this.languageID = language
|
||||
@ -196,7 +176,7 @@ abstract class QuestionnaireBase<T> {
|
||||
|
||||
suspend fun saveAnswersToDatabase(answers: Map<String, Any>, questionnaireId: String) {
|
||||
Log.d("AnswersMap", answers.toString())
|
||||
val db = MyApp.database
|
||||
val db = MyApp.Companion.database
|
||||
|
||||
val clientCode = answers["client_code"] as? String ?: return
|
||||
|
||||
|
||||
@ -1,16 +1,8 @@
|
||||
package com.dano.test1.questionnaire
|
||||
|
||||
import android.view.Gravity
|
||||
import android.view.View
|
||||
import android.widget.AdapterView
|
||||
import android.widget.ArrayAdapter
|
||||
import android.widget.Button
|
||||
import android.widget.FrameLayout
|
||||
import android.widget.Spinner
|
||||
import androidx.core.content.ContextCompat
|
||||
import com.dano.test1.LocalizationHelper
|
||||
import com.dano.test1.util.LocalizationHelper
|
||||
import com.dano.test1.R
|
||||
import com.dano.test1.utils.ViewUtils
|
||||
|
||||
open class QuestionnaireGeneric(private val questionnaireFileName: String) : QuestionnaireBase<Unit>() {
|
||||
|
||||
@ -47,40 +39,6 @@ open class QuestionnaireGeneric(private val questionnaireFileName: String) : Que
|
||||
} else {
|
||||
showEmptyScreen()
|
||||
}
|
||||
|
||||
injectLanguageSpinner(layout)
|
||||
}
|
||||
}
|
||||
|
||||
private fun injectLanguageSpinner(layout: View) {
|
||||
val container = layout as? FrameLayout ?: return
|
||||
|
||||
val spinner = Spinner(context)
|
||||
val adapter = ArrayAdapter(context, android.R.layout.simple_spinner_item, LANGUAGE_LABELS).apply {
|
||||
setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item)
|
||||
}
|
||||
spinner.adapter = adapter
|
||||
spinner.background = ContextCompat.getDrawable(context, R.drawable.bg_field_filled)
|
||||
spinner.setSelection(LANGUAGE_IDS.indexOf(languageID).coerceAtLeast(0))
|
||||
|
||||
val margin = ViewUtils.dp(context, 10)
|
||||
container.addView(
|
||||
spinner,
|
||||
FrameLayout.LayoutParams(
|
||||
FrameLayout.LayoutParams.WRAP_CONTENT,
|
||||
FrameLayout.LayoutParams.WRAP_CONTENT,
|
||||
Gravity.TOP or Gravity.END
|
||||
).apply { setMargins(0, margin, margin, 0) }
|
||||
)
|
||||
|
||||
spinner.onItemSelectedListener = object : AdapterView.OnItemSelectedListener {
|
||||
override fun onItemSelected(parent: AdapterView<*>?, v: View?, position: Int, id: Long) {
|
||||
val newLang = LANGUAGE_IDS[position]
|
||||
if (newLang == languageID) return
|
||||
languageID = newLang
|
||||
spinner.post { showCurrentQuestion() }
|
||||
}
|
||||
override fun onNothingSelected(parent: AdapterView<*>?) {}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -1,9 +0,0 @@
|
||||
package com.dano.test1.questionnaire
|
||||
|
||||
object QuestionnaireProgressiveCallbacks {
|
||||
@JvmStatic
|
||||
fun maybeTrim(questionId: String?) {
|
||||
// Intentional no-op: handlers call this during user interaction,
|
||||
// but trimming is handled by each questionnaire variant itself.
|
||||
}
|
||||
}
|
||||
@ -1,46 +0,0 @@
|
||||
package com.dano.test1.ui
|
||||
|
||||
import android.content.Context
|
||||
|
||||
/**
|
||||
* Persists dev-only A/B overrides. Not a security boundary; values are for local QA.
|
||||
* Use [effectiveVariant] when branching questionnaire or remote-config logic.
|
||||
*/
|
||||
object AbTestSettingsStore {
|
||||
private const val PREF = "dev_ab_settings"
|
||||
private const val KEY_OVERRIDE = "override_enabled"
|
||||
private const val KEY_VARIANT = "variant"
|
||||
|
||||
const val VARIANT_NONE = "NONE"
|
||||
const val VARIANT_A = "A"
|
||||
const val VARIANT_B = "B"
|
||||
|
||||
fun isOverrideEnabled(context: Context): Boolean =
|
||||
context.getSharedPreferences(PREF, Context.MODE_PRIVATE).getBoolean(KEY_OVERRIDE, false)
|
||||
|
||||
fun setOverrideEnabled(context: Context, enabled: Boolean) {
|
||||
context.getSharedPreferences(PREF, Context.MODE_PRIVATE).edit()
|
||||
.putBoolean(KEY_OVERRIDE, enabled)
|
||||
.apply()
|
||||
}
|
||||
|
||||
fun getVariant(context: Context): String =
|
||||
context.getSharedPreferences(PREF, Context.MODE_PRIVATE).getString(KEY_VARIANT, VARIANT_NONE)
|
||||
?: VARIANT_NONE
|
||||
|
||||
fun setVariant(context: Context, variant: String) {
|
||||
context.getSharedPreferences(PREF, Context.MODE_PRIVATE).edit()
|
||||
.putString(KEY_VARIANT, variant)
|
||||
.apply()
|
||||
}
|
||||
|
||||
/** Returns "A" or "B" when override is on and a branch is selected; otherwise null. */
|
||||
fun effectiveVariant(context: Context): String? {
|
||||
if (!isOverrideEnabled(context)) return null
|
||||
return when (getVariant(context)) {
|
||||
VARIANT_A -> "A"
|
||||
VARIANT_B -> "B"
|
||||
else -> null
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -4,22 +4,31 @@ import android.graphics.Color
|
||||
import android.graphics.Typeface
|
||||
import android.util.Log
|
||||
import android.view.View
|
||||
import android.widget.*
|
||||
import com.dano.test1.data.ExcelExportService
|
||||
import com.dano.test1.utils.ViewUtils
|
||||
import androidx.appcompat.app.AppCompatActivity
|
||||
import com.dano.test1.LanguageManager
|
||||
import android.widget.Button
|
||||
import android.widget.ProgressBar
|
||||
import android.widget.TableLayout
|
||||
import android.widget.TableRow
|
||||
import android.widget.TextView
|
||||
import android.widget.Toast
|
||||
import com.dano.test1.export.ExcelExportService
|
||||
import com.dano.test1.export.HeaderOrderRepository
|
||||
import com.dano.test1.util.LanguageManager
|
||||
import com.dano.test1.MainActivity
|
||||
import com.dano.test1.MyApp
|
||||
import com.dano.test1.R
|
||||
import com.dano.test1.data.Client
|
||||
import com.dano.test1.data.HeaderOrderRepository
|
||||
import com.dano.test1.data.Question
|
||||
import com.dano.test1.data.Questionnaire
|
||||
import kotlinx.coroutines.*
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.SupervisorJob
|
||||
import kotlinx.coroutines.launch
|
||||
import kotlinx.coroutines.withContext
|
||||
import org.json.JSONArray
|
||||
import kotlin.math.roundToInt
|
||||
|
||||
class DatabaseButtonHandler(
|
||||
private val activity: AppCompatActivity,
|
||||
private val activity: MainActivity,
|
||||
private val databaseButton: Button,
|
||||
private val onClose: () -> Unit,
|
||||
private val languageIDProvider: () -> String = { "GERMAN" }
|
||||
@ -67,7 +76,7 @@ class DatabaseButtonHandler(
|
||||
|
||||
uiScope.launch {
|
||||
val clients: List<Client> = withContext(Dispatchers.IO) {
|
||||
MyApp.database.clientDao().getAllClients()
|
||||
MyApp.Companion.database.clientDao().getAllClients()
|
||||
}
|
||||
|
||||
progress.visibility = View.GONE
|
||||
@ -154,9 +163,11 @@ class DatabaseButtonHandler(
|
||||
|
||||
uiScope.launch {
|
||||
val result = withContext(Dispatchers.IO) {
|
||||
val allQuestionnairesDb = MyApp.database.questionnaireDao().getAll()
|
||||
val completedForClient = MyApp.database.completedQuestionnaireDao().getAllForClient(clientCode)
|
||||
val allAnswersForClient = MyApp.database.answerDao().getAnswersForClient(clientCode)
|
||||
val allQuestionnairesDb = MyApp.Companion.database.questionnaireDao().getAll()
|
||||
val completedForClient =
|
||||
MyApp.Companion.database.completedQuestionnaireDao().getAllForClient(clientCode)
|
||||
val allAnswersForClient =
|
||||
MyApp.Companion.database.answerDao().getAnswersForClient(clientCode)
|
||||
Triple(allQuestionnairesDb, completedForClient, allAnswersForClient)
|
||||
}
|
||||
|
||||
@ -275,8 +286,9 @@ class DatabaseButtonHandler(
|
||||
|
||||
uiScope.launch {
|
||||
val (questions, answersForClient) = withContext(Dispatchers.IO) {
|
||||
val qs = MyApp.database.questionDao().getQuestionsForQuestionnaire(questionnaireId)
|
||||
val ans = MyApp.database.answerDao()
|
||||
val qs = MyApp.Companion.database.questionDao()
|
||||
.getQuestionsForQuestionnaire(questionnaireId)
|
||||
val ans = MyApp.Companion.database.answerDao()
|
||||
.getAnswersForClientAndQuestionnaire(clientCode, questionnaireId)
|
||||
qs to ans
|
||||
}
|
||||
@ -420,7 +432,10 @@ class DatabaseButtonHandler(
|
||||
bgColor?.let { setBackgroundColor(it) }
|
||||
}
|
||||
|
||||
private fun dp(value: Int): Int = ViewUtils.dp(activity, value)
|
||||
private fun dp(value: Int): Int {
|
||||
val density = activity.resources.displayMetrics.density
|
||||
return (value * density).roundToInt()
|
||||
}
|
||||
|
||||
private fun <T : View> requireView(id: Int, name: String): T {
|
||||
val v = activity.findViewById<T>(id)
|
||||
|
||||
@ -1,92 +0,0 @@
|
||||
package com.dano.test1.ui
|
||||
|
||||
import android.os.Bundle
|
||||
import android.view.View
|
||||
import android.widget.Button
|
||||
import android.widget.RadioButton
|
||||
import android.widget.RadioGroup
|
||||
import androidx.activity.enableEdgeToEdge
|
||||
import androidx.appcompat.app.AppCompatActivity
|
||||
import androidx.appcompat.widget.SwitchCompat
|
||||
import androidx.appcompat.widget.Toolbar
|
||||
import androidx.core.view.ViewCompat
|
||||
import androidx.core.view.WindowInsetsCompat
|
||||
import com.dano.test1.R
|
||||
|
||||
/**
|
||||
* Dev-only A/B settings. Entry: client code [_dev_settings_] on the opening screen Load action.
|
||||
* Not a security boundary (secret string is in the APK).
|
||||
*/
|
||||
class DevSettingsActivity : AppCompatActivity() {
|
||||
|
||||
private lateinit var switchOverride: SwitchCompat
|
||||
private lateinit var radioGroup: RadioGroup
|
||||
|
||||
override fun onCreate(savedInstanceState: Bundle?) {
|
||||
enableEdgeToEdge()
|
||||
super.onCreate(savedInstanceState)
|
||||
setContentView(R.layout.activity_dev_settings)
|
||||
|
||||
findViewById<View>(android.R.id.content)?.let { content ->
|
||||
ViewCompat.setOnApplyWindowInsetsListener(content) { v, insets ->
|
||||
val bars = insets.getInsets(WindowInsetsCompat.Type.systemBars())
|
||||
v.setPadding(bars.left, bars.top, bars.right, bars.bottom)
|
||||
WindowInsetsCompat.CONSUMED
|
||||
}
|
||||
}
|
||||
|
||||
val toolbar = findViewById<Toolbar>(R.id.devSettingsToolbar)
|
||||
setSupportActionBar(toolbar)
|
||||
supportActionBar?.setDisplayHomeAsUpEnabled(true)
|
||||
|
||||
switchOverride = findViewById(R.id.switchAbOverride)
|
||||
radioGroup = findViewById(R.id.radioGroupVariant)
|
||||
|
||||
applyPrefsToUi()
|
||||
|
||||
switchOverride.setOnCheckedChangeListener { _, isChecked ->
|
||||
AbTestSettingsStore.setOverrideEnabled(this, isChecked)
|
||||
updateRadiosEnabled(isChecked)
|
||||
}
|
||||
|
||||
radioGroup.setOnCheckedChangeListener { _, checkedId ->
|
||||
val variant = when (checkedId) {
|
||||
R.id.radioVariantA -> AbTestSettingsStore.VARIANT_A
|
||||
R.id.radioVariantB -> AbTestSettingsStore.VARIANT_B
|
||||
else -> AbTestSettingsStore.VARIANT_NONE
|
||||
}
|
||||
AbTestSettingsStore.setVariant(this, variant)
|
||||
}
|
||||
|
||||
val databaseButton = findViewById<Button>(R.id.databaseButton)
|
||||
DatabaseButtonHandler(
|
||||
activity = this,
|
||||
databaseButton = databaseButton,
|
||||
onClose = { recreate() }
|
||||
).setup()
|
||||
}
|
||||
|
||||
private fun applyPrefsToUi() {
|
||||
val overrideOn = AbTestSettingsStore.isOverrideEnabled(this)
|
||||
switchOverride.isChecked = overrideOn
|
||||
updateRadiosEnabled(overrideOn)
|
||||
|
||||
when (AbTestSettingsStore.getVariant(this)) {
|
||||
AbTestSettingsStore.VARIANT_A -> radioGroup.check(R.id.radioVariantA)
|
||||
AbTestSettingsStore.VARIANT_B -> radioGroup.check(R.id.radioVariantB)
|
||||
else -> radioGroup.check(R.id.radioVariantDefault)
|
||||
}
|
||||
}
|
||||
|
||||
private fun updateRadiosEnabled(enabled: Boolean) {
|
||||
for (i in 0 until radioGroup.childCount) {
|
||||
(radioGroup.getChildAt(i) as? RadioButton)?.isEnabled = enabled
|
||||
}
|
||||
radioGroup.alpha = if (enabled) 1f else 0.5f
|
||||
}
|
||||
|
||||
override fun onSupportNavigateUp(): Boolean {
|
||||
finish()
|
||||
return true
|
||||
}
|
||||
}
|
||||
97
app/src/main/java/com/dano/test1/ui/EditButtonHandler.kt
Normal file
97
app/src/main/java/com/dano/test1/ui/EditButtonHandler.kt
Normal file
@ -0,0 +1,97 @@
|
||||
package com.dano.test1.ui
|
||||
|
||||
import android.widget.Button
|
||||
import android.widget.EditText
|
||||
import android.widget.Toast
|
||||
import com.dano.test1.util.LanguageManager
|
||||
import com.dano.test1.MainActivity
|
||||
import com.dano.test1.MyApp
|
||||
import kotlinx.coroutines.*
|
||||
import com.dano.test1.data.CompletedQuestionnaire
|
||||
import com.dano.test1.questionnaire.GlobalValues
|
||||
|
||||
class EditButtonHandler(
|
||||
private val activity: MainActivity,
|
||||
private val editButton: Button,
|
||||
private val editText: EditText,
|
||||
private val languageIDProvider: () -> String,
|
||||
private val questionnaireFiles: Map<Button, String>,
|
||||
private val buttonPoints: MutableMap<String, Int>,
|
||||
private val updateButtonTexts: () -> Unit,
|
||||
private val setButtonsEnabled: (List<Button>, Boolean) -> Unit,
|
||||
private val setUiFreeze: (Boolean) -> Unit,
|
||||
private val triggerLoad: () -> Unit
|
||||
) {
|
||||
|
||||
fun setup() {
|
||||
editButton.text = LanguageManager.getText(languageIDProvider(), "edit")
|
||||
editButton.setOnClickListener { handleEditButtonClick() }
|
||||
}
|
||||
|
||||
private fun handleEditButtonClick() {
|
||||
val typed = editText.text.toString().trim()
|
||||
val desiredCode = when {
|
||||
typed.isNotBlank() -> typed
|
||||
!GlobalValues.LOADED_CLIENT_CODE.isNullOrBlank() -> GlobalValues.LOADED_CLIENT_CODE!!
|
||||
else -> ""
|
||||
}
|
||||
|
||||
if (desiredCode.isBlank()) {
|
||||
val message = LanguageManager.getText(languageIDProvider(), "please_client_code")
|
||||
Toast.makeText(activity, message, Toast.LENGTH_SHORT).show()
|
||||
return
|
||||
}
|
||||
|
||||
GlobalValues.LAST_CLIENT_CODE = desiredCode
|
||||
|
||||
val needLoad = GlobalValues.LOADED_CLIENT_CODE?.equals(desiredCode) != true
|
||||
if (needLoad) {
|
||||
setUiFreeze(true) // Zwischenzustände unterdrücken
|
||||
triggerLoad()
|
||||
}
|
||||
|
||||
CoroutineScope(Dispatchers.IO).launch {
|
||||
val loadedOk = waitUntilClientLoaded(desiredCode, timeoutMs = 2500, stepMs = 50)
|
||||
if (!loadedOk) {
|
||||
withContext(Dispatchers.Main) {
|
||||
val msg = LanguageManager.getText(languageIDProvider(), "open_client_via_load")
|
||||
Toast.makeText(activity, msg, Toast.LENGTH_LONG).show()
|
||||
setUiFreeze(false)
|
||||
}
|
||||
return@launch
|
||||
}
|
||||
|
||||
val completedEntries: List<CompletedQuestionnaire> =
|
||||
MyApp.Companion.database.completedQuestionnaireDao().getAllForClient(desiredCode)
|
||||
|
||||
val completedFiles = completedEntries.filter { it.isDone }.map { it.questionnaireId.lowercase() }
|
||||
|
||||
buttonPoints.clear()
|
||||
for (entry in completedEntries) {
|
||||
if (entry.isDone) {
|
||||
buttonPoints[entry.questionnaireId] = entry.sumPoints ?: 0
|
||||
}
|
||||
}
|
||||
|
||||
withContext(Dispatchers.Main) {
|
||||
updateButtonTexts()
|
||||
val enabledButtons = questionnaireFiles.filter { (_, fileName) ->
|
||||
completedFiles.any { completedId -> fileName.lowercase().contains(completedId) }
|
||||
}.keys.toList()
|
||||
setButtonsEnabled(enabledButtons, true)
|
||||
setUiFreeze(false)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private suspend fun waitUntilClientLoaded(expectedCode: String, timeoutMs: Long, stepMs: Long): Boolean {
|
||||
if (GlobalValues.LOADED_CLIENT_CODE?.equals(expectedCode) == true) return true
|
||||
var waited = 0L
|
||||
while (waited < timeoutMs) {
|
||||
delay(stepMs)
|
||||
waited += stepMs
|
||||
if (GlobalValues.LOADED_CLIENT_CODE?.equals(expectedCode) == true) return true
|
||||
}
|
||||
return GlobalValues.LOADED_CLIENT_CODE?.equals(expectedCode) == true
|
||||
}
|
||||
}
|
||||
@ -10,17 +10,16 @@ import android.util.TypedValue
|
||||
import android.view.Gravity
|
||||
import android.view.View
|
||||
import android.widget.*
|
||||
import com.dano.test1.LanguageManager
|
||||
import com.dano.test1.util.LanguageManager
|
||||
import com.dano.test1.MainActivity
|
||||
import com.dano.test1.R
|
||||
import com.dano.test1.auth.LoginManager
|
||||
import com.dano.test1.auth.TokenStore
|
||||
import com.dano.test1.network.DatabaseUploader
|
||||
import com.dano.test1.network.LoginManager
|
||||
import com.dano.test1.network.NetworkUtils
|
||||
import com.dano.test1.network.TokenStore
|
||||
import com.dano.test1.questionnaire.GlobalValues
|
||||
import com.dano.test1.questionnaire.QuestionItem
|
||||
import com.dano.test1.questionnaire.QuestionnaireBase
|
||||
import com.dano.test1.questionnaire.QuestionnaireAllInOne
|
||||
import com.dano.test1.questionnaire.QuestionnaireGeneric
|
||||
import com.google.android.material.button.MaterialButton
|
||||
import org.json.JSONArray
|
||||
@ -28,7 +27,6 @@ import org.json.JSONObject
|
||||
import java.io.File
|
||||
import java.util.concurrent.TimeUnit
|
||||
import com.google.android.material.dialog.MaterialAlertDialogBuilder
|
||||
import com.dano.test1.utils.ViewUtils
|
||||
|
||||
|
||||
var RHS_POINTS: Int? = null
|
||||
@ -43,8 +41,10 @@ class HandlerOpeningScreen(private val activity: MainActivity) {
|
||||
private lateinit var buttonContainer: LinearLayout
|
||||
private lateinit var buttonLoad: Button
|
||||
private lateinit var saveButton: Button
|
||||
private lateinit var editButton: Button
|
||||
private lateinit var uploadButton: Button
|
||||
private lateinit var downloadButton: Button
|
||||
private lateinit var databaseButton: Button
|
||||
private lateinit var statusSession: TextView
|
||||
private lateinit var statusOnline: TextView
|
||||
private val SESSION_WARN_AFTER_MS = 12 * 60 * 60 * 1000L // 12h
|
||||
@ -93,8 +93,10 @@ class HandlerOpeningScreen(private val activity: MainActivity) {
|
||||
setupLanguageSpinner()
|
||||
setupLoadButton()
|
||||
setupSaveButton()
|
||||
setupEditButtonHandler()
|
||||
setupUploadButton()
|
||||
setupDownloadButton()
|
||||
setupDatabaseButtonHandler()
|
||||
|
||||
uiHandler.removeCallbacks(statusTicker)
|
||||
updateStatusStrip()
|
||||
@ -120,11 +122,13 @@ class HandlerOpeningScreen(private val activity: MainActivity) {
|
||||
buttonContainer = activity.findViewById(R.id.buttonContainer)
|
||||
buttonLoad = activity.findViewById(R.id.loadButton)
|
||||
saveButton = activity.findViewById(R.id.saveButton)
|
||||
editButton = activity.findViewById(R.id.editButton)
|
||||
uploadButton = activity.findViewById(R.id.uploadButton)
|
||||
|
||||
downloadButton = activity.findViewById(R.id.downloadButton)
|
||||
downloadButton.visibility = View.GONE
|
||||
|
||||
databaseButton = activity.findViewById(R.id.databaseButton)
|
||||
statusSession = activity.findViewById(R.id.statusSession)
|
||||
statusOnline = activity.findViewById(R.id.statusOnline)
|
||||
val tag = editText.tag as? String ?: ""
|
||||
@ -226,12 +230,7 @@ class HandlerOpeningScreen(private val activity: MainActivity) {
|
||||
setOnClickListener {
|
||||
GlobalValues.LAST_CLIENT_CODE = GlobalValues.LOADED_CLIENT_CODE
|
||||
val fileName = questionnaireFiles[this] ?: return@setOnClickListener
|
||||
val variant = AbTestSettingsStore.effectiveVariant(activity)
|
||||
val questionnaire: QuestionnaireBase<*> = if (variant == "B") {
|
||||
QuestionnaireAllInOne(fileName)
|
||||
} else {
|
||||
QuestionnaireGeneric(fileName)
|
||||
}
|
||||
val questionnaire = QuestionnaireGeneric(fileName)
|
||||
startQuestionnaire(questionnaire)
|
||||
applySetButtonsEnabled(dynamicButtons.filter { it == this }, allowCompleted = false, force = false)
|
||||
}
|
||||
@ -331,7 +330,7 @@ class HandlerOpeningScreen(private val activity: MainActivity) {
|
||||
dynamicButtonsProvider = { dynamicButtons },
|
||||
buttonPoints = buttonPoints,
|
||||
updateButtonTexts = { applyUpdateButtonTexts(force = false) },
|
||||
setButtonsEnabled = { list -> applySetButtonsEnabled(list, allowCompleted = true, force = false) },
|
||||
setButtonsEnabled = { list -> applySetButtonsEnabled(list, allowCompleted = false, force = false) },
|
||||
updateMainButtonsState = { updateMainButtonsState(it) },
|
||||
).setup()
|
||||
}
|
||||
@ -387,8 +386,10 @@ class HandlerOpeningScreen(private val activity: MainActivity) {
|
||||
}
|
||||
buttonLoad.text = t("load")
|
||||
saveButton.text = t("save")
|
||||
editButton.text = t("edit")
|
||||
uploadButton.text = t("upload")
|
||||
downloadButton.text = t("download")
|
||||
databaseButton.text = t("database")
|
||||
val hintTag = editText.tag as? String ?: ""
|
||||
editText.hint = t(hintTag)
|
||||
val coachTag = coachEditText.tag as? String ?: ""
|
||||
@ -453,6 +454,23 @@ class HandlerOpeningScreen(private val activity: MainActivity) {
|
||||
).setup()
|
||||
}
|
||||
|
||||
private fun setupEditButtonHandler() {
|
||||
EditButtonHandler(
|
||||
activity = activity,
|
||||
editButton = editButton,
|
||||
editText = editText,
|
||||
languageIDProvider = { languageID },
|
||||
questionnaireFiles = questionnaireFiles,
|
||||
buttonPoints = buttonPoints,
|
||||
updateButtonTexts = { applyUpdateButtonTexts(force = true) },
|
||||
setButtonsEnabled = { list, allowCompleted ->
|
||||
applySetButtonsEnabled(list, allowCompleted, force = true)
|
||||
},
|
||||
setUiFreeze = { freeze -> uiFreeze = freeze },
|
||||
triggerLoad = { buttonLoad.performClick() }
|
||||
).setup()
|
||||
}
|
||||
|
||||
private fun setupUploadButton() {
|
||||
uploadButton.text = t("upload")
|
||||
uploadButton.setOnClickListener {
|
||||
@ -514,8 +532,17 @@ class HandlerOpeningScreen(private val activity: MainActivity) {
|
||||
}
|
||||
}
|
||||
|
||||
private fun setupDatabaseButtonHandler() {
|
||||
DatabaseButtonHandler(
|
||||
activity = activity,
|
||||
databaseButton = databaseButton,
|
||||
onClose = { init() },
|
||||
languageIDProvider = { languageID }
|
||||
).setup()
|
||||
}
|
||||
|
||||
private fun updateMainButtonsState(isDatabaseAvailable: Boolean) {
|
||||
listOf(buttonLoad, saveButton).forEach { b ->
|
||||
listOf(buttonLoad, saveButton, editButton, databaseButton).forEach { b ->
|
||||
b.isEnabled = isDatabaseAvailable
|
||||
b.alpha = if (isDatabaseAvailable) 1.0f else 0.5f
|
||||
}
|
||||
@ -545,7 +572,7 @@ class HandlerOpeningScreen(private val activity: MainActivity) {
|
||||
}
|
||||
}
|
||||
|
||||
private fun dp(v: Int): Int = ViewUtils.dp(activity, v)
|
||||
private fun dp(v: Int): Int = (v * activity.resources.displayMetrics.density).toInt()
|
||||
|
||||
private fun isCompleted(button: Button): Boolean {
|
||||
val fileName = questionnaireFiles[button] ?: return false
|
||||
@ -637,7 +664,16 @@ class HandlerOpeningScreen(private val activity: MainActivity) {
|
||||
}
|
||||
|
||||
private fun lockCoachCodeField() {
|
||||
ViewUtils.lockEditField(coachEditText)
|
||||
coachEditText.isFocusable = false
|
||||
coachEditText.isFocusableInTouchMode = false
|
||||
coachEditText.isCursorVisible = false
|
||||
coachEditText.keyListener = null
|
||||
coachEditText.isLongClickable = false
|
||||
coachEditText.isClickable = false
|
||||
coachEditText.setBackgroundResource(R.drawable.bg_field_locked)
|
||||
coachEditText.setCompoundDrawablesWithIntrinsicBounds(0, 0, R.drawable.ic_lock_24, 0)
|
||||
coachEditText.compoundDrawablePadding = dp(8)
|
||||
coachEditText.alpha = 0.95f
|
||||
}
|
||||
|
||||
private fun applySessionAgeHighlight(ageMs: Long) {
|
||||
|
||||
@ -1,10 +1,9 @@
|
||||
package com.dano.test1.ui
|
||||
|
||||
import android.content.Intent
|
||||
import android.widget.Button
|
||||
import android.widget.EditText
|
||||
import android.widget.Toast
|
||||
import com.dano.test1.LanguageManager
|
||||
import com.dano.test1.util.LanguageManager
|
||||
import com.dano.test1.MainActivity
|
||||
import com.dano.test1.MyApp
|
||||
import kotlinx.coroutines.*
|
||||
@ -25,11 +24,6 @@ class LoadButtonHandler(
|
||||
private val updateMainButtonsState: (Boolean) -> Unit,
|
||||
) {
|
||||
|
||||
companion object {
|
||||
/** Opening-screen client code that opens dev A/B settings (not a security boundary). */
|
||||
private const val DEV_SETTINGS_SECRET = "_dev_settings_"
|
||||
}
|
||||
|
||||
fun setup() {
|
||||
loadButton.text = LanguageManager.getText(languageIDProvider(), "load")
|
||||
loadButton.setOnClickListener { handleLoadButton() }
|
||||
@ -43,12 +37,6 @@ class LoadButtonHandler(
|
||||
return
|
||||
}
|
||||
|
||||
if (inputText == DEV_SETTINGS_SECRET) {
|
||||
editText.text.clear()
|
||||
activity.startActivity(Intent(activity, DevSettingsActivity::class.java))
|
||||
return
|
||||
}
|
||||
|
||||
buttonPoints.clear()
|
||||
setButtonsEnabled(emptyList()) // temporär sperren
|
||||
updateButtonTexts() // Chips zeigen vorläufig „Gesperrt“
|
||||
@ -57,7 +45,7 @@ class LoadButtonHandler(
|
||||
GlobalValues.LAST_CLIENT_CODE = clientCode
|
||||
|
||||
CoroutineScope(Dispatchers.IO).launch {
|
||||
val client = MyApp.database.clientDao().getClientByCode(clientCode)
|
||||
val client = MyApp.Companion.database.clientDao().getClientByCode(clientCode)
|
||||
if (client == null) {
|
||||
GlobalValues.LOADED_CLIENT_CODE = null
|
||||
withContext(Dispatchers.Main) {
|
||||
@ -97,7 +85,7 @@ class LoadButtonHandler(
|
||||
}
|
||||
}
|
||||
is QuestionItem.Condition.QuestionCondition -> {
|
||||
val answers = MyApp.database.answerDao().getAnswersForClientAndQuestionnaire(clientCode, condition.questionnaire)
|
||||
val answers = MyApp.Companion.database.answerDao().getAnswersForClientAndQuestionnaire(clientCode, condition.questionnaire)
|
||||
val relevant = answers.find { it.questionId.endsWith(condition.questionId, ignoreCase = true) }
|
||||
val answerValue = relevant?.answerValue ?: ""
|
||||
when (condition.operator) {
|
||||
@ -114,7 +102,7 @@ class LoadButtonHandler(
|
||||
}
|
||||
if (!reqOk) return false
|
||||
val q = condition.questionCheck ?: return true
|
||||
val answers = MyApp.database.answerDao().getAnswersForClientAndQuestionnaire(clientCode, q.questionnaire)
|
||||
val answers = MyApp.Companion.database.answerDao().getAnswersForClientAndQuestionnaire(clientCode, q.questionnaire)
|
||||
val relevant = answers.find { it.questionId.endsWith(q.questionId, ignoreCase = true) }
|
||||
val answerValue = relevant?.answerValue ?: ""
|
||||
when (q.operator) {
|
||||
@ -134,7 +122,7 @@ class LoadButtonHandler(
|
||||
|
||||
private suspend fun handleNormalLoad(clientCode: String) {
|
||||
val completedEntries = withContext(Dispatchers.IO) {
|
||||
MyApp.database.completedQuestionnaireDao().getAllForClient(clientCode)
|
||||
MyApp.Companion.database.completedQuestionnaireDao().getAllForClient(clientCode)
|
||||
}
|
||||
|
||||
buttonPoints.clear()
|
||||
@ -160,9 +148,10 @@ class LoadButtonHandler(
|
||||
(completedNorm.contains(targetNorm) || targetNorm.contains(completedNorm)) && completed.isDone
|
||||
}
|
||||
}
|
||||
if (isCompleted) continue
|
||||
|
||||
val condMet = evaluateCondition(entry.condition, clientCode, completedEntries)
|
||||
if (condMet || isCompleted) enabledButtons.add(button)
|
||||
if (condMet) enabledButtons.add(button)
|
||||
}
|
||||
|
||||
withContext(Dispatchers.Main) {
|
||||
|
||||
@ -12,7 +12,7 @@ import android.util.Log
|
||||
import android.widget.Button
|
||||
import android.widget.EditText
|
||||
import android.widget.Toast
|
||||
import com.dano.test1.LanguageManager
|
||||
import com.dano.test1.util.LanguageManager
|
||||
import com.dano.test1.MainActivity
|
||||
import com.dano.test1.MyApp
|
||||
import com.dano.test1.questionnaire.GlobalValues
|
||||
@ -44,7 +44,7 @@ class SaveButtonHandler(
|
||||
private fun showCompletedQuestionnaires(clientCode: String) {
|
||||
CoroutineScope(Dispatchers.IO).launch {
|
||||
val actualClientCode = clientCode.removeSuffix("_database")
|
||||
val completedEntries = MyApp.database.completedQuestionnaireDao().getAllForClient(actualClientCode)
|
||||
val completedEntries = MyApp.Companion.database.completedQuestionnaireDao().getAllForClient(actualClientCode)
|
||||
|
||||
Log.d("PDF_DEBUG", "Completed entries for client $actualClientCode:")
|
||||
for (entry in completedEntries) {
|
||||
@ -74,7 +74,7 @@ class SaveButtonHandler(
|
||||
canvas.drawText("Points: ${entry.sumPoints ?: "N/A"}", 20f, yPosition, paint)
|
||||
yPosition += 30f
|
||||
|
||||
val answers = MyApp.database.answerDao().getAnswersForClientAndQuestionnaire(actualClientCode, entry.questionnaireId)
|
||||
val answers = MyApp.Companion.database.answerDao().getAnswersForClientAndQuestionnaire(actualClientCode, entry.questionnaireId)
|
||||
|
||||
for (answer in answers) {
|
||||
val questionKey = answer.questionId.substringAfter("-")
|
||||
|
||||
@ -1,6 +1,6 @@
|
||||
package com.dano.test1.ui
|
||||
package com.dano.test1.util
|
||||
|
||||
import com.dano.test1.LanguageManager
|
||||
import com.dano.test1.util.LanguageManager
|
||||
|
||||
object Countries {
|
||||
fun getAllCountries(languageID: String): List<String> {
|
||||
@ -1,4 +1,4 @@
|
||||
package com.dano.test1
|
||||
package com.dano.test1.util
|
||||
|
||||
import com.dano.test1.questionnaire.MAX_VALUE_AGE
|
||||
import com.dano.test1.questionnaire.MAX_VALUE_YEAR
|
||||
@ -1,4 +1,4 @@
|
||||
package com.dano.test1
|
||||
package com.dano.test1.util
|
||||
|
||||
import android.view.View
|
||||
import android.view.ViewGroup
|
||||
@ -1,13 +1,11 @@
|
||||
package com.dano.test1.ui
|
||||
|
||||
import com.dano.test1.LanguageManager
|
||||
package com.dano.test1.util
|
||||
|
||||
data class Month(val name: String) {
|
||||
override fun toString(): String = name
|
||||
}
|
||||
|
||||
object Months {
|
||||
fun getAllMonths(languageID: String): List<Any> {
|
||||
fun getAllMonths(languageID: String): List<Month> {
|
||||
return listOf(
|
||||
Month(LanguageManager.getText(languageID, "january")),
|
||||
Month(LanguageManager.getText(languageID, "february")),
|
||||
73
app/src/main/java/com/dano/test1/util/UiUtils.kt
Normal file
73
app/src/main/java/com/dano/test1/util/UiUtils.kt
Normal file
@ -0,0 +1,73 @@
|
||||
package com.dano.test1.util
|
||||
|
||||
import android.util.TypedValue
|
||||
import android.view.Gravity
|
||||
import android.view.View
|
||||
import android.view.ViewGroup
|
||||
import android.widget.AbsListView
|
||||
import android.widget.ArrayAdapter
|
||||
import android.widget.Spinner
|
||||
import android.widget.TextView
|
||||
import androidx.core.widget.TextViewCompat
|
||||
|
||||
fun TextView.setTextSizePercentOfScreenHeight(percentOfHeight: Float) {
|
||||
val dm = context.resources.displayMetrics
|
||||
val sp = (dm.heightPixels * percentOfHeight) / dm.scaledDensity
|
||||
TextViewCompat.setAutoSizeTextTypeWithDefaults(this, TextViewCompat.AUTO_SIZE_TEXT_TYPE_NONE)
|
||||
setTextSize(TypedValue.COMPLEX_UNIT_SP, sp)
|
||||
}
|
||||
|
||||
fun <T> Spinner.setupSpinner(items: List<T>, selectedItem: T?) {
|
||||
val dm = context.resources.displayMetrics
|
||||
|
||||
fun spFromScreenHeight(percent: Float): Float = (dm.heightPixels * percent) / dm.scaledDensity
|
||||
fun pxFromSp(sp: Float): Int = (sp * dm.scaledDensity).toInt()
|
||||
|
||||
val textSp = spFromScreenHeight(0.0275f)
|
||||
val textPx = pxFromSp(textSp)
|
||||
val vPadPx = (textPx * 0.50f).toInt()
|
||||
val rowHeight = (textPx * 2.20f + 2 * vPadPx).toInt()
|
||||
|
||||
val adapter = object : ArrayAdapter<T>(context, android.R.layout.simple_spinner_item, items) {
|
||||
private fun styleRow(tv: TextView, forceHeight: Boolean) {
|
||||
tv.setTextSize(TypedValue.COMPLEX_UNIT_SP, textSp)
|
||||
tv.includeFontPadding = true
|
||||
tv.setLineSpacing(0f, 1.2f)
|
||||
tv.gravity = (tv.gravity and Gravity.HORIZONTAL_GRAVITY_MASK) or Gravity.CENTER_VERTICAL
|
||||
tv.setPadding(tv.paddingLeft, vPadPx, tv.paddingRight, vPadPx)
|
||||
tv.minHeight = rowHeight
|
||||
tv.isSingleLine = true
|
||||
if (forceHeight) {
|
||||
val lp = tv.layoutParams
|
||||
if (lp == null || lp.height <= 0) {
|
||||
tv.layoutParams = AbsListView.LayoutParams(AbsListView.LayoutParams.MATCH_PARENT, rowHeight)
|
||||
} else {
|
||||
lp.height = rowHeight
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override fun getView(position: Int, convertView: View?, parent: ViewGroup): View {
|
||||
val v = super.getView(position, convertView, parent) as TextView
|
||||
styleRow(v, forceHeight = false)
|
||||
return v
|
||||
}
|
||||
|
||||
override fun getDropDownView(position: Int, convertView: View?, parent: ViewGroup): View {
|
||||
val v = super.getDropDownView(position, convertView, parent) as TextView
|
||||
styleRow(v, forceHeight = true)
|
||||
return v
|
||||
}
|
||||
}
|
||||
|
||||
adapter.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item)
|
||||
this.adapter = adapter
|
||||
setPadding(paddingLeft, vPadPx, paddingRight, vPadPx)
|
||||
minimumHeight = rowHeight
|
||||
requestLayout()
|
||||
|
||||
selectedItem?.let {
|
||||
val index = items.indexOf(it)
|
||||
if (index >= 0) setSelection(index)
|
||||
}
|
||||
}
|
||||
@ -1,112 +0,0 @@
|
||||
package com.dano.test1.utils
|
||||
|
||||
import android.content.Context
|
||||
import android.util.TypedValue
|
||||
import android.view.Gravity
|
||||
import android.view.View
|
||||
import android.view.ViewGroup
|
||||
import android.widget.AbsListView
|
||||
import android.widget.ArrayAdapter
|
||||
import android.widget.EditText
|
||||
import android.widget.Spinner
|
||||
import android.widget.TextView
|
||||
import androidx.core.widget.TextViewCompat
|
||||
import com.dano.test1.R
|
||||
import kotlin.math.roundToInt
|
||||
|
||||
object ViewUtils {
|
||||
|
||||
/**
|
||||
* Sets the text size of a TextView to a percentage of the screen height (in sp).
|
||||
* Disables auto-sizing to prevent conflicts.
|
||||
*/
|
||||
fun setTextSizePercentOfScreenHeight(view: TextView, percentOfHeight: Float) {
|
||||
val dm = view.context.resources.displayMetrics
|
||||
val shortSide = minOf(dm.heightPixels, dm.widthPixels)
|
||||
val sp = (shortSide * percentOfHeight) / dm.scaledDensity
|
||||
TextViewCompat.setAutoSizeTextTypeWithDefaults(view, TextViewCompat.AUTO_SIZE_TEXT_TYPE_NONE)
|
||||
view.setTextSize(TypedValue.COMPLEX_UNIT_SP, sp)
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets up a Spinner with a responsive, styled adapter.
|
||||
* Font size and row height are derived from screen height to prevent clipping.
|
||||
*/
|
||||
fun <T> setupResponsiveSpinner(context: Context, spinner: Spinner, items: List<T>, selectedItem: T?) {
|
||||
val dm = context.resources.displayMetrics
|
||||
val shortSide = minOf(dm.heightPixels, dm.widthPixels)
|
||||
|
||||
fun spFromScreenHeight(percent: Float): Float = (shortSide * percent) / dm.scaledDensity
|
||||
fun pxFromSp(sp: Float): Int = (sp * dm.scaledDensity).toInt()
|
||||
|
||||
val textSp = spFromScreenHeight(0.0275f)
|
||||
val textPx = pxFromSp(textSp)
|
||||
val vPadPx = (textPx * 0.50f).toInt()
|
||||
val rowHeight = (textPx * 2.20f + 2 * vPadPx).toInt()
|
||||
|
||||
val adapter = object : ArrayAdapter<T>(context, android.R.layout.simple_spinner_item, items) {
|
||||
private fun styleRow(tv: TextView, forceHeight: Boolean) {
|
||||
tv.setTextSize(TypedValue.COMPLEX_UNIT_SP, textSp)
|
||||
tv.includeFontPadding = true
|
||||
tv.setLineSpacing(0f, 1.2f)
|
||||
tv.gravity = (tv.gravity and Gravity.HORIZONTAL_GRAVITY_MASK) or Gravity.CENTER_VERTICAL
|
||||
tv.setPadding(tv.paddingLeft, vPadPx, tv.paddingRight, vPadPx)
|
||||
tv.minHeight = rowHeight
|
||||
tv.isSingleLine = true
|
||||
if (forceHeight) {
|
||||
val lp = tv.layoutParams
|
||||
if (lp == null || lp.height <= 0) {
|
||||
tv.layoutParams = AbsListView.LayoutParams(AbsListView.LayoutParams.MATCH_PARENT, rowHeight)
|
||||
} else {
|
||||
lp.height = rowHeight
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override fun getView(position: Int, convertView: View?, parent: ViewGroup): View {
|
||||
val v = super.getView(position, convertView, parent) as TextView
|
||||
styleRow(v, forceHeight = false)
|
||||
return v
|
||||
}
|
||||
|
||||
override fun getDropDownView(position: Int, convertView: View?, parent: ViewGroup): View {
|
||||
val v = super.getDropDownView(position, convertView, parent) as TextView
|
||||
styleRow(v, forceHeight = true)
|
||||
return v
|
||||
}
|
||||
}
|
||||
|
||||
adapter.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item)
|
||||
spinner.adapter = adapter
|
||||
spinner.setPadding(spinner.paddingLeft, vPadPx, spinner.paddingRight, vPadPx)
|
||||
spinner.minimumHeight = rowHeight
|
||||
spinner.requestLayout()
|
||||
|
||||
selectedItem?.let {
|
||||
val index = items.indexOf(it)
|
||||
if (index >= 0) spinner.setSelection(index)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Locks an EditText field visually and functionally (e.g. for coach code fields).
|
||||
*/
|
||||
fun lockEditField(field: EditText, dpPadding: Int = 8) {
|
||||
field.isFocusable = false
|
||||
field.isFocusableInTouchMode = false
|
||||
field.isCursorVisible = false
|
||||
field.keyListener = null
|
||||
field.isLongClickable = false
|
||||
field.isClickable = false
|
||||
field.setBackgroundResource(R.drawable.bg_field_locked)
|
||||
field.setCompoundDrawablesWithIntrinsicBounds(0, 0, R.drawable.ic_lock_24, 0)
|
||||
field.compoundDrawablePadding = dp(field.context, dpPadding)
|
||||
field.alpha = 0.95f
|
||||
}
|
||||
|
||||
/**
|
||||
* Converts dp to pixels using the given context.
|
||||
*/
|
||||
fun dp(context: Context, value: Int): Int =
|
||||
(value * context.resources.displayMetrics.density).roundToInt()
|
||||
}
|
||||
@ -1,96 +0,0 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:orientation="vertical">
|
||||
|
||||
<androidx.appcompat.widget.Toolbar
|
||||
android:id="@+id/devSettingsToolbar"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="?attr/actionBarSize"
|
||||
android:background="?attr/colorPrimary"
|
||||
app:title="A/B testing (dev)"
|
||||
app:titleTextColor="@android:color/white" />
|
||||
|
||||
<ScrollView
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:fillViewport="true">
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="vertical"
|
||||
android:padding="16dp">
|
||||
|
||||
<TextView
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:paddingBottom="8dp"
|
||||
android:text="Manual A/B override for local testing. Not a security boundary."
|
||||
android:textSize="14sp" />
|
||||
|
||||
<androidx.appcompat.widget.SwitchCompat
|
||||
android:id="@+id/switchAbOverride"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:paddingVertical="8dp"
|
||||
android:text="Manual A/B override" />
|
||||
|
||||
<TextView
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:paddingTop="16dp"
|
||||
android:paddingBottom="8dp"
|
||||
android:text="Variant"
|
||||
android:textStyle="bold" />
|
||||
|
||||
<RadioGroup
|
||||
android:id="@+id/radioGroupVariant"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="vertical">
|
||||
|
||||
<RadioButton
|
||||
android:id="@+id/radioVariantDefault"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="Default (no override)" />
|
||||
|
||||
<RadioButton
|
||||
android:id="@+id/radioVariantA"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="Variant A" />
|
||||
|
||||
<RadioButton
|
||||
android:id="@+id/radioVariantB"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="Variant B" />
|
||||
</RadioGroup>
|
||||
|
||||
<View
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="1dp"
|
||||
android:layout_marginTop="24dp"
|
||||
android:layout_marginBottom="16dp"
|
||||
android:background="#DDDDDD" />
|
||||
|
||||
<TextView
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:paddingBottom="8dp"
|
||||
android:text="Database"
|
||||
android:textStyle="bold" />
|
||||
|
||||
<Button
|
||||
android:id="@+id/databaseButton"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="Datenbank" />
|
||||
|
||||
</LinearLayout>
|
||||
</ScrollView>
|
||||
</LinearLayout>
|
||||
@ -195,6 +195,21 @@
|
||||
app:cornerRadius="@dimen/pill_radius"
|
||||
app:backgroundTint="@color/brand_purple"/>
|
||||
|
||||
<com.google.android.material.button.MaterialButton
|
||||
android:id="@+id/editButton"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="@dimen/pill_height"
|
||||
android:layout_weight="1"
|
||||
android:layout_marginEnd="12dp"
|
||||
android:textAllCaps="false"
|
||||
android:textColor="@android:color/white"
|
||||
app:icon="@drawable/ic_dot_16"
|
||||
app:iconTint="@android:color/white"
|
||||
app:iconPadding="8dp"
|
||||
app:iconGravity="textStart"
|
||||
app:cornerRadius="@dimen/pill_radius"
|
||||
app:backgroundTint="@color/brand_purple"/>
|
||||
|
||||
<com.google.android.material.button.MaterialButton
|
||||
android:id="@+id/saveButton"
|
||||
android:layout_width="0dp"
|
||||
@ -238,6 +253,19 @@
|
||||
style="@style/Widget.MaterialComponents.Button.OutlinedButton"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="@dimen/pill_height"
|
||||
android:layout_marginBottom="12dp"
|
||||
android:textAllCaps="false"
|
||||
android:textColor="@color/brand_purple"
|
||||
app:cornerRadius="@dimen/pill_radius"
|
||||
app:strokeColor="@color/brand_purple"
|
||||
app:strokeWidth="@dimen/pill_stroke"
|
||||
app:backgroundTint="@android:color/transparent"/>
|
||||
|
||||
<com.google.android.material.button.MaterialButton
|
||||
android:id="@+id/databaseButton"
|
||||
style="@style/Widget.MaterialComponents.Button.OutlinedButton"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="@dimen/pill_height"
|
||||
android:textAllCaps="false"
|
||||
android:textColor="@color/brand_purple"
|
||||
app:cornerRadius="@dimen/pill_radius"
|
||||
|
||||
@ -1,95 +0,0 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:orientation="vertical">
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="horizontal"
|
||||
android:gravity="center_vertical"
|
||||
android:paddingStart="12dp"
|
||||
android:paddingEnd="12dp"
|
||||
android:paddingTop="12dp">
|
||||
|
||||
<com.google.android.material.button.MaterialButton
|
||||
android:id="@+id/btnBack"
|
||||
android:layout_width="@dimen/nav_btn_size"
|
||||
android:layout_height="@dimen/nav_btn_size"
|
||||
android:text=""
|
||||
android:textAllCaps="false"
|
||||
app:icon="@drawable/ic_chevron_left"
|
||||
app:iconTint="@color/btn_nav_left_icon_tint"
|
||||
app:iconSize="@dimen/nav_icon_size"
|
||||
app:iconPadding="0dp"
|
||||
app:cornerRadius="999dp"
|
||||
app:backgroundTint="@color/btn_nav_left_tint"
|
||||
app:rippleColor="@color/btn_nav_left_ripple" />
|
||||
|
||||
<View
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="1dp"
|
||||
android:layout_weight="1" />
|
||||
|
||||
<Spinner
|
||||
android:id="@+id/langSpinner"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:background="@drawable/bg_field_filled"
|
||||
android:paddingStart="8dp"
|
||||
android:paddingEnd="8dp"
|
||||
android:paddingTop="6dp"
|
||||
android:paddingBottom="6dp" />
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
<androidx.core.widget.NestedScrollView
|
||||
android:id="@+id/scrollContainer"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="0dp"
|
||||
android:layout_weight="1"
|
||||
android:fillViewport="true"
|
||||
android:clipToPadding="false"
|
||||
android:paddingBottom="16dp">
|
||||
|
||||
<LinearLayout
|
||||
android:id="@+id/questionContainer"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="vertical"
|
||||
android:paddingStart="12dp"
|
||||
android:paddingEnd="12dp"
|
||||
android:paddingTop="8dp" />
|
||||
</androidx.core.widget.NestedScrollView>
|
||||
|
||||
<FrameLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:padding="12dp"
|
||||
android:background="@android:color/white"
|
||||
android:elevation="8dp">
|
||||
|
||||
<com.google.android.material.button.MaterialButton
|
||||
android:id="@+id/btnSave"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="@dimen/nav_btn_size"
|
||||
android:textAllCaps="false"
|
||||
android:minWidth="0dp"
|
||||
android:insetLeft="0dp"
|
||||
android:insetRight="0dp"
|
||||
app:cornerRadius="999dp"
|
||||
app:backgroundTint="@color/btn_nav_right_tint"
|
||||
app:rippleColor="@color/btn_nav_right_ripple" />
|
||||
|
||||
<ProgressBar
|
||||
android:id="@+id/progressBar"
|
||||
style="?android:attr/progressBarStyle"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_gravity="center"
|
||||
android:visibility="gone" />
|
||||
</FrameLayout>
|
||||
|
||||
</LinearLayout>
|
||||
Reference in New Issue
Block a user