Compare commits
10 Commits
092242f913
...
a-b-testin
| Author | SHA1 | Date | |
|---|---|---|---|
| d48906bd3b | |||
| b6fea5be7a | |||
| e5531e6616 | |||
| e8e223978b | |||
| e99d681f3c | |||
| a0a9ba45fa | |||
| 3228f75b35 | |||
| 67bbc3ea06 | |||
| b95977e28d | |||
| cc89c77186 |
4
.idea/deploymentTargetSelector.xml
generated
4
.idea/deploymentTargetSelector.xml
generated
@ -4,10 +4,10 @@
|
|||||||
<selectionStates>
|
<selectionStates>
|
||||||
<SelectionState runConfigName="app">
|
<SelectionState runConfigName="app">
|
||||||
<option name="selectionMode" value="DROPDOWN" />
|
<option name="selectionMode" value="DROPDOWN" />
|
||||||
<DropdownSelection timestamp="2025-09-29T10:52:30.282144200Z">
|
<DropdownSelection timestamp="2026-03-24T11:30:25.894049082Z">
|
||||||
<Target type="DEFAULT_BOOT">
|
<Target type="DEFAULT_BOOT">
|
||||||
<handle>
|
<handle>
|
||||||
<DeviceId pluginId="LocalEmulator" identifier="path=C:\Users\danie\.android\avd\Medium_Phone.avd" />
|
<DeviceId pluginId="PhysicalDevice" identifier="serial=R52T605XE0L" />
|
||||||
</handle>
|
</handle>
|
||||||
</Target>
|
</Target>
|
||||||
</DropdownSelection>
|
</DropdownSelection>
|
||||||
|
|||||||
13
.idea/deviceManager.xml
generated
Normal file
13
.idea/deviceManager.xml
generated
Normal file
@ -0,0 +1,13 @@
|
|||||||
|
<?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,6 +41,11 @@
|
|||||||
</intent-filter>
|
</intent-filter>
|
||||||
</activity>
|
</activity>
|
||||||
|
|
||||||
|
<activity
|
||||||
|
android:name=".ui.DevSettingsActivity"
|
||||||
|
android:exported="false"
|
||||||
|
android:parentActivityName=".MainActivity" />
|
||||||
|
|
||||||
</application>
|
</application>
|
||||||
|
|
||||||
</manifest>
|
</manifest>
|
||||||
|
|||||||
@ -1,93 +0,0 @@
|
|||||||
package com.dano.test1
|
|
||||||
|
|
||||||
import android.widget.Button
|
|
||||||
import android.widget.EditText
|
|
||||||
import android.widget.Toast
|
|
||||||
import kotlinx.coroutines.*
|
|
||||||
import com.dano.test1.data.CompletedQuestionnaire
|
|
||||||
|
|
||||||
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.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
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -11,9 +11,16 @@ import android.widget.EditText
|
|||||||
import android.widget.LinearLayout
|
import android.widget.LinearLayout
|
||||||
import android.widget.ProgressBar
|
import android.widget.ProgressBar
|
||||||
import android.widget.Toast
|
import android.widget.Toast
|
||||||
|
import androidx.activity.enableEdgeToEdge
|
||||||
import androidx.appcompat.app.AlertDialog
|
import androidx.appcompat.app.AlertDialog
|
||||||
import androidx.appcompat.app.AppCompatActivity
|
import androidx.appcompat.app.AppCompatActivity
|
||||||
import java.io.File
|
import androidx.core.view.ViewCompat
|
||||||
|
import androidx.core.view.WindowInsetsCompat
|
||||||
|
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
|
||||||
|
|
||||||
class MainActivity : AppCompatActivity() {
|
class MainActivity : AppCompatActivity() {
|
||||||
|
|
||||||
@ -32,6 +39,7 @@ class MainActivity : AppCompatActivity() {
|
|||||||
private fun t(key: String): String = LanguageManager.getText(bootLanguageId, key)
|
private fun t(key: String): String = LanguageManager.getText(bootLanguageId, key)
|
||||||
|
|
||||||
override fun onCreate(savedInstanceState: Bundle?) {
|
override fun onCreate(savedInstanceState: Bundle?) {
|
||||||
|
enableEdgeToEdge()
|
||||||
super.onCreate(savedInstanceState)
|
super.onCreate(savedInstanceState)
|
||||||
|
|
||||||
// === Offline-Start ermöglichen ===
|
// === Offline-Start ermöglichen ===
|
||||||
@ -201,6 +209,16 @@ class MainActivity : AppCompatActivity() {
|
|||||||
}
|
}
|
||||||
// --- /LIVE NETZSTATUS ---
|
// --- /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) {
|
override fun onConfigurationChanged(newConfig: Configuration) {
|
||||||
super.onConfigurationChanged(newConfig)
|
super.onConfigurationChanged(newConfig)
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,42 +0,0 @@
|
|||||||
package com.dano.test1
|
|
||||||
|
|
||||||
import android.widget.Button
|
|
||||||
|
|
||||||
open class QuestionnaireGeneric(private val questionnaireFileName: String) : QuestionnaireBase<Unit>() {
|
|
||||||
|
|
||||||
override fun startQuestionnaire() {
|
|
||||||
val (meta, questionsList) = loadQuestionnaireFromJson(questionnaireFileName)
|
|
||||||
questionnaireMeta = meta
|
|
||||||
questions = questionsList
|
|
||||||
currentIndex = 0
|
|
||||||
showCurrentQuestion()
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun showCurrentQuestion() {
|
|
||||||
val question = questions[currentIndex]
|
|
||||||
|
|
||||||
val layoutResId = getLayoutResId(question.layout ?: "default_layout")
|
|
||||||
|
|
||||||
if (layoutResId == 0) {
|
|
||||||
showEmptyScreen()
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
navigateTo(layoutResId) { layout ->
|
|
||||||
|
|
||||||
LocalizationHelper.localizeViewTree(layout, languageID)
|
|
||||||
|
|
||||||
layout.findViewById<Button>(R.id.Qprev)?.setOnClickListener {
|
|
||||||
goToPreviousQuestion()
|
|
||||||
}
|
|
||||||
|
|
||||||
val handler = createHandlerForQuestion(question)
|
|
||||||
|
|
||||||
if (handler != null) {
|
|
||||||
handler.bind(layout, question)
|
|
||||||
} else {
|
|
||||||
showEmptyScreen()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -1,4 +1,4 @@
|
|||||||
package com.dano.test1
|
package com.dano.test1.data
|
||||||
|
|
||||||
import android.content.ContentValues
|
import android.content.ContentValues
|
||||||
import android.content.Context
|
import android.content.Context
|
||||||
@ -7,8 +7,12 @@ import android.net.Uri
|
|||||||
import android.os.Build
|
import android.os.Build
|
||||||
import android.os.Environment
|
import android.os.Environment
|
||||||
import android.provider.MediaStore
|
import android.provider.MediaStore
|
||||||
|
import com.dano.test1.LanguageManager
|
||||||
|
import com.dano.test1.MyApp
|
||||||
import org.apache.poi.ss.usermodel.Row
|
import org.apache.poi.ss.usermodel.Row
|
||||||
import org.apache.poi.xssf.usermodel.XSSFWorkbook
|
import org.apache.poi.xssf.usermodel.XSSFWorkbook
|
||||||
|
import java.io.ByteArrayOutputStream
|
||||||
|
import java.io.File
|
||||||
|
|
||||||
/*
|
/*
|
||||||
Aufgabe:
|
Aufgabe:
|
||||||
@ -83,7 +87,7 @@ class ExcelExportService(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
val bytes = java.io.ByteArrayOutputStream().use { bos ->
|
val bytes = ByteArrayOutputStream().use { bos ->
|
||||||
wb.write(bos); bos.toByteArray()
|
wb.write(bos); bos.toByteArray()
|
||||||
}
|
}
|
||||||
wb.close()
|
wb.close()
|
||||||
@ -112,7 +116,7 @@ class ExcelExportService(
|
|||||||
} else {
|
} else {
|
||||||
val downloadsDir = Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DOWNLOADS)
|
val downloadsDir = Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DOWNLOADS)
|
||||||
if (!downloadsDir.exists()) downloadsDir.mkdirs()
|
if (!downloadsDir.exists()) downloadsDir.mkdirs()
|
||||||
val outFile = java.io.File(downloadsDir, filename)
|
val outFile = File(downloadsDir, filename)
|
||||||
outFile.writeBytes(bytes)
|
outFile.writeBytes(bytes)
|
||||||
MediaScannerConnection.scanFile(
|
MediaScannerConnection.scanFile(
|
||||||
context,
|
context,
|
||||||
@ -175,4 +179,4 @@ class ExcelExportService(
|
|||||||
for (key in candidates) localizeEnglishNoBrackets(key)?.let { return it }
|
for (key in candidates) localizeEnglishNoBrackets(key)?.let { return it }
|
||||||
return raw
|
return raw
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -1,8 +1,11 @@
|
|||||||
package com.dano.test1
|
package com.dano.test1.data
|
||||||
|
|
||||||
import android.content.Context
|
import android.content.Context
|
||||||
import android.util.Log
|
import android.util.Log
|
||||||
import android.widget.Toast
|
import android.widget.Toast
|
||||||
|
import com.dano.test1.LanguageManager
|
||||||
|
import org.apache.poi.ss.usermodel.CellType
|
||||||
|
import org.apache.poi.ss.usermodel.DateUtil
|
||||||
import org.apache.poi.xssf.usermodel.XSSFWorkbook
|
import org.apache.poi.xssf.usermodel.XSSFWorkbook
|
||||||
import org.json.JSONArray
|
import org.json.JSONArray
|
||||||
import java.nio.charset.Charset
|
import java.nio.charset.Charset
|
||||||
@ -63,16 +66,16 @@ class HeaderOrderRepository(
|
|||||||
for (i in first until last) {
|
for (i in first until last) {
|
||||||
val cell = row.getCell(i) ?: continue
|
val cell = row.getCell(i) ?: continue
|
||||||
val value = when (cell.cellType) {
|
val value = when (cell.cellType) {
|
||||||
org.apache.poi.ss.usermodel.CellType.STRING -> cell.stringCellValue
|
CellType.STRING -> cell.stringCellValue
|
||||||
org.apache.poi.ss.usermodel.CellType.NUMERIC ->
|
CellType.NUMERIC ->
|
||||||
if (org.apache.poi.ss.usermodel.DateUtil.isCellDateFormatted(cell))
|
if (DateUtil.isCellDateFormatted(cell))
|
||||||
cell.dateCellValue.time.toString()
|
cell.dateCellValue.time.toString()
|
||||||
else {
|
else {
|
||||||
val n = cell.numericCellValue
|
val n = cell.numericCellValue
|
||||||
if (n % 1.0 == 0.0) n.toLong().toString() else n.toString()
|
if (n % 1.0 == 0.0) n.toLong().toString() else n.toString()
|
||||||
}
|
}
|
||||||
org.apache.poi.ss.usermodel.CellType.BOOLEAN -> cell.booleanCellValue.toString()
|
CellType.BOOLEAN -> cell.booleanCellValue.toString()
|
||||||
org.apache.poi.ss.usermodel.CellType.FORMULA -> cell.richStringCellValue.string
|
CellType.FORMULA -> cell.richStringCellValue.string
|
||||||
else -> ""
|
else -> ""
|
||||||
}.trim()
|
}.trim()
|
||||||
|
|
||||||
@ -1,7 +1,8 @@
|
|||||||
package com.dano.test1
|
package com.dano.test1.network
|
||||||
|
|
||||||
import android.content.Context
|
import android.content.Context
|
||||||
import android.util.Log
|
import android.util.Log
|
||||||
|
import com.dano.test1.AES256Helper
|
||||||
import kotlinx.coroutines.CoroutineScope
|
import kotlinx.coroutines.CoroutineScope
|
||||||
import kotlinx.coroutines.Dispatchers
|
import kotlinx.coroutines.Dispatchers
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
@ -1,4 +1,4 @@
|
|||||||
package com.dano.test1
|
package com.dano.test1.network
|
||||||
|
|
||||||
import android.content.Context
|
import android.content.Context
|
||||||
import android.database.sqlite.SQLiteDatabase
|
import android.database.sqlite.SQLiteDatabase
|
||||||
@ -8,6 +8,7 @@ import kotlinx.coroutines.CoroutineScope
|
|||||||
import kotlinx.coroutines.Dispatchers
|
import kotlinx.coroutines.Dispatchers
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
import android.database.Cursor
|
import android.database.Cursor
|
||||||
|
import com.dano.test1.AES256Helper
|
||||||
import okhttp3.*
|
import okhttp3.*
|
||||||
import okhttp3.MediaType.Companion.toMediaType
|
import okhttp3.MediaType.Companion.toMediaType
|
||||||
import okhttp3.RequestBody.Companion.asRequestBody
|
import okhttp3.RequestBody.Companion.asRequestBody
|
||||||
@ -1,9 +1,9 @@
|
|||||||
package com.dano.test1
|
package com.dano.test1.network
|
||||||
|
|
||||||
import android.app.AlertDialog
|
import android.app.AlertDialog
|
||||||
import android.content.Context
|
import android.content.Context
|
||||||
|
import android.text.InputType
|
||||||
import android.util.Log
|
import android.util.Log
|
||||||
import android.view.LayoutInflater
|
|
||||||
import android.widget.EditText
|
import android.widget.EditText
|
||||||
import android.widget.LinearLayout
|
import android.widget.LinearLayout
|
||||||
import android.widget.Toast
|
import android.widget.Toast
|
||||||
@ -96,13 +96,13 @@ object LoginManager {
|
|||||||
}
|
}
|
||||||
val etNew = EditText(context).apply {
|
val etNew = EditText(context).apply {
|
||||||
hint = "Neues Passwort"
|
hint = "Neues Passwort"
|
||||||
inputType = android.text.InputType.TYPE_CLASS_TEXT or
|
inputType = InputType.TYPE_CLASS_TEXT or
|
||||||
android.text.InputType.TYPE_TEXT_VARIATION_PASSWORD
|
InputType.TYPE_TEXT_VARIATION_PASSWORD
|
||||||
}
|
}
|
||||||
val etRepeat = EditText(context).apply {
|
val etRepeat = EditText(context).apply {
|
||||||
hint = "Neues Passwort (wiederholen)"
|
hint = "Neues Passwort (wiederholen)"
|
||||||
inputType = android.text.InputType.TYPE_CLASS_TEXT or
|
inputType = InputType.TYPE_CLASS_TEXT or
|
||||||
android.text.InputType.TYPE_TEXT_VARIATION_PASSWORD
|
InputType.TYPE_TEXT_VARIATION_PASSWORD
|
||||||
}
|
}
|
||||||
container.addView(etNew)
|
container.addView(etNew)
|
||||||
container.addView(etRepeat)
|
container.addView(etRepeat)
|
||||||
@ -1,4 +1,4 @@
|
|||||||
package com.dano.test1
|
package com.dano.test1.network
|
||||||
|
|
||||||
import android.content.Context
|
import android.content.Context
|
||||||
import android.net.ConnectivityManager
|
import android.net.ConnectivityManager
|
||||||
@ -1,4 +1,4 @@
|
|||||||
package com.dano.test1
|
package com.dano.test1.network
|
||||||
|
|
||||||
import android.content.Context
|
import android.content.Context
|
||||||
|
|
||||||
@ -1,4 +1,4 @@
|
|||||||
package com.dano.test1
|
package com.dano.test1.questionnaire
|
||||||
import android.view.View
|
import android.view.View
|
||||||
|
|
||||||
interface QuestionHandler {
|
interface QuestionHandler {
|
||||||
@ -0,0 +1,554 @@
|
|||||||
|
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)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -1,10 +1,23 @@
|
|||||||
package com.dano.test1
|
package com.dano.test1.questionnaire
|
||||||
|
|
||||||
|
import android.R
|
||||||
import android.app.Activity
|
import android.app.Activity
|
||||||
import android.util.Log
|
import android.util.Log
|
||||||
import android.view.View
|
import android.view.View
|
||||||
import android.widget.*
|
import android.widget.*
|
||||||
|
import com.dano.test1.LanguageManager
|
||||||
|
import com.dano.test1.MainActivity
|
||||||
|
import com.dano.test1.MyApp
|
||||||
import com.dano.test1.data.*
|
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.Gson
|
||||||
import com.google.gson.JsonParser
|
import com.google.gson.JsonParser
|
||||||
import kotlinx.coroutines.*
|
import kotlinx.coroutines.*
|
||||||
@ -27,6 +40,17 @@ abstract class QuestionnaireBase<T> {
|
|||||||
abstract fun startQuestionnaire()
|
abstract fun startQuestionnaire()
|
||||||
abstract fun showCurrentQuestion()
|
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) {
|
fun attach(activity: Activity, language: String) {
|
||||||
this.context = activity
|
this.context = activity
|
||||||
this.languageID = language
|
this.languageID = language
|
||||||
@ -61,8 +85,8 @@ abstract class QuestionnaireBase<T> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
protected fun setupSpinner(spinner: Spinner, spinnerValues: List<Any>, selectedValue: Any?) {
|
protected fun setupSpinner(spinner: Spinner, spinnerValues: List<Any>, selectedValue: Any?) {
|
||||||
val adapter = ArrayAdapter(context, android.R.layout.simple_spinner_item, spinnerValues).apply {
|
val adapter = ArrayAdapter(context, R.layout.simple_spinner_item, spinnerValues).apply {
|
||||||
setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item)
|
setDropDownViewResource(R.layout.simple_spinner_dropdown_item)
|
||||||
}
|
}
|
||||||
spinner.adapter = adapter
|
spinner.adapter = adapter
|
||||||
selectedValue?.let { value ->
|
selectedValue?.let { value ->
|
||||||
@ -75,7 +99,7 @@ abstract class QuestionnaireBase<T> {
|
|||||||
|
|
||||||
protected fun navigateTo(layoutResId: Int, setup: (View) -> Unit) {
|
protected fun navigateTo(layoutResId: Int, setup: (View) -> Unit) {
|
||||||
context.setContentView(layoutResId)
|
context.setContentView(layoutResId)
|
||||||
val rootView = context.findViewById<View>(android.R.id.content)
|
val rootView = context.findViewById<View>(R.id.content)
|
||||||
setup(rootView)
|
setup(rootView)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -85,7 +109,7 @@ abstract class QuestionnaireBase<T> {
|
|||||||
|
|
||||||
protected fun showEmptyScreen() {
|
protected fun showEmptyScreen() {
|
||||||
navigateTo(getLayoutResId("empty")) {
|
navigateTo(getLayoutResId("empty")) {
|
||||||
setupPrevButton(R.id.Qprev) { goToPreviousQuestion() }
|
setupPrevButton(com.dano.test1.R.id.Qprev) { goToPreviousQuestion() }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -0,0 +1,86 @@
|
|||||||
|
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.R
|
||||||
|
import com.dano.test1.utils.ViewUtils
|
||||||
|
|
||||||
|
open class QuestionnaireGeneric(private val questionnaireFileName: String) : QuestionnaireBase<Unit>() {
|
||||||
|
|
||||||
|
override fun startQuestionnaire() {
|
||||||
|
val (meta, questionsList) = loadQuestionnaireFromJson(questionnaireFileName)
|
||||||
|
questionnaireMeta = meta
|
||||||
|
questions = questionsList
|
||||||
|
currentIndex = 0
|
||||||
|
showCurrentQuestion()
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun showCurrentQuestion() {
|
||||||
|
val question = questions[currentIndex]
|
||||||
|
|
||||||
|
val layoutResId = getLayoutResId(question.layout ?: "default_layout")
|
||||||
|
|
||||||
|
if (layoutResId == 0) {
|
||||||
|
showEmptyScreen()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
navigateTo(layoutResId) { layout ->
|
||||||
|
|
||||||
|
LocalizationHelper.localizeViewTree(layout, languageID)
|
||||||
|
|
||||||
|
layout.findViewById<Button>(R.id.Qprev)?.setOnClickListener {
|
||||||
|
goToPreviousQuestion()
|
||||||
|
}
|
||||||
|
|
||||||
|
val handler = createHandlerForQuestion(question)
|
||||||
|
|
||||||
|
if (handler != null) {
|
||||||
|
handler.bind(layout, question)
|
||||||
|
} 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,4 +1,4 @@
|
|||||||
package com.dano.test1
|
package com.dano.test1.questionnaire
|
||||||
|
|
||||||
data class Option(
|
data class Option(
|
||||||
val key: String, // Must always be set
|
val key: String, // Must always be set
|
||||||
@ -0,0 +1,9 @@
|
|||||||
|
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,9 +1,15 @@
|
|||||||
package com.dano.test1
|
package com.dano.test1.questionnaire.handlers
|
||||||
|
|
||||||
import android.view.View
|
import android.view.View
|
||||||
import android.widget.*
|
import android.widget.*
|
||||||
import android.util.TypedValue
|
import com.dano.test1.questionnaire.GlobalValues
|
||||||
import androidx.core.widget.TextViewCompat
|
import com.dano.test1.LanguageManager
|
||||||
|
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 kotlinx.coroutines.CoroutineScope
|
import kotlinx.coroutines.CoroutineScope
|
||||||
import kotlinx.coroutines.Dispatchers
|
import kotlinx.coroutines.Dispatchers
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
@ -38,10 +44,10 @@ class HandlerClientCoachCode(
|
|||||||
|
|
||||||
questionTextView.text = question.question?.let { LanguageManager.getText(languageID, it) } ?: ""
|
questionTextView.text = question.question?.let { LanguageManager.getText(languageID, it) } ?: ""
|
||||||
|
|
||||||
setTextSizePercentOfScreenHeight(titleTextView, 0.03f)
|
ViewUtils.setTextSizePercentOfScreenHeight(titleTextView, 0.03f)
|
||||||
setTextSizePercentOfScreenHeight(questionTextView, 0.03f)
|
ViewUtils.setTextSizePercentOfScreenHeight(questionTextView, 0.03f)
|
||||||
setTextSizePercentOfScreenHeight(clientCodeField, 0.025f)
|
ViewUtils.setTextSizePercentOfScreenHeight(clientCodeField, 0.025f)
|
||||||
setTextSizePercentOfScreenHeight(coachCodeField, 0.025f)
|
ViewUtils.setTextSizePercentOfScreenHeight(coachCodeField, 0.025f)
|
||||||
|
|
||||||
// Client-Code: nur verwenden, wenn bereits geladen
|
// Client-Code: nur verwenden, wenn bereits geladen
|
||||||
val loadedClientCode = GlobalValues.LOADED_CLIENT_CODE
|
val loadedClientCode = GlobalValues.LOADED_CLIENT_CODE
|
||||||
@ -57,7 +63,7 @@ class HandlerClientCoachCode(
|
|||||||
val coachFromLogin = TokenStore.getUsername(layout.context)
|
val coachFromLogin = TokenStore.getUsername(layout.context)
|
||||||
if (!coachFromLogin.isNullOrBlank()) {
|
if (!coachFromLogin.isNullOrBlank()) {
|
||||||
coachCodeField.setText(coachFromLogin)
|
coachCodeField.setText(coachFromLogin)
|
||||||
lockCoachField(coachCodeField) // optisch & technisch gesperrt
|
ViewUtils.lockEditField(coachCodeField) // optisch & technisch gesperrt
|
||||||
} else {
|
} else {
|
||||||
// Falls (theoretisch) kein Login-Username vorhanden ist, verhalten wie bisher
|
// Falls (theoretisch) kein Login-Username vorhanden ist, verhalten wie bisher
|
||||||
coachCodeField.setText(answers["coach_code"] as? String ?: "")
|
coachCodeField.setText(answers["coach_code"] as? String ?: "")
|
||||||
@ -72,13 +78,6 @@ class HandlerClientCoachCode(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun setTextSizePercentOfScreenHeight(view: TextView, percentOfHeight: Float) {
|
|
||||||
val dm = layout.resources.displayMetrics
|
|
||||||
val sp = (dm.heightPixels * percentOfHeight) / dm.scaledDensity
|
|
||||||
TextViewCompat.setAutoSizeTextTypeWithDefaults(view, TextViewCompat.AUTO_SIZE_TEXT_TYPE_NONE)
|
|
||||||
view.setTextSize(TypedValue.COMPLEX_UNIT_SP, sp)
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun onNextClicked(clientCodeField: EditText, coachCodeField: EditText) {
|
private fun onNextClicked(clientCodeField: EditText, coachCodeField: EditText) {
|
||||||
val loadedClientCode = GlobalValues.LOADED_CLIENT_CODE
|
val loadedClientCode = GlobalValues.LOADED_CLIENT_CODE
|
||||||
|
|
||||||
@ -143,19 +142,4 @@ class HandlerClientCoachCode(
|
|||||||
// Not used
|
// 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,7 +1,11 @@
|
|||||||
package com.dano.test1
|
package com.dano.test1.questionnaire.handlers
|
||||||
|
|
||||||
import android.view.View
|
import android.view.View
|
||||||
import android.widget.*
|
import android.widget.*
|
||||||
|
import com.dano.test1.LanguageManager
|
||||||
|
import com.dano.test1.questionnaire.QuestionHandler
|
||||||
|
import com.dano.test1.questionnaire.QuestionItem
|
||||||
|
import com.dano.test1.R
|
||||||
|
|
||||||
/*
|
/*
|
||||||
Zweck:
|
Zweck:
|
||||||
@ -1,15 +1,21 @@
|
|||||||
package com.dano.test1
|
package com.dano.test1.questionnaire.handlers
|
||||||
|
|
||||||
import android.content.Context
|
import android.content.Context
|
||||||
import android.view.View
|
import android.view.View
|
||||||
import android.view.ViewGroup
|
|
||||||
import android.widget.*
|
import android.widget.*
|
||||||
import kotlinx.coroutines.*
|
import kotlinx.coroutines.*
|
||||||
import java.text.SimpleDateFormat
|
import java.text.SimpleDateFormat
|
||||||
import java.util.*
|
import java.util.*
|
||||||
import android.util.TypedValue
|
import com.dano.test1.questionnaire.GlobalValues
|
||||||
import androidx.core.widget.TextViewCompat
|
import com.dano.test1.LanguageManager
|
||||||
import android.widget.AbsListView
|
import com.dano.test1.questionnaire.MAX_VALUE_YEAR
|
||||||
|
import com.dano.test1.ui.Month
|
||||||
|
import com.dano.test1.ui.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:
|
Zweck:
|
||||||
@ -52,12 +58,11 @@ class HandlerDateSpinner(
|
|||||||
textView.text = question.textKey?.let { LanguageManager.getText(languageID, it) } ?: ""
|
textView.text = question.textKey?.let { LanguageManager.getText(languageID, it) } ?: ""
|
||||||
|
|
||||||
// Schriftgrößen pro Bildschirmhöhe
|
// Schriftgrößen pro Bildschirmhöhe
|
||||||
setTextSizePercentOfScreenHeight(textView, 0.03f) // oben
|
ViewUtils.setTextSizePercentOfScreenHeight(textView, 0.03f)
|
||||||
setTextSizePercentOfScreenHeight(questionTextView, 0.03f) // frage
|
ViewUtils.setTextSizePercentOfScreenHeight(questionTextView, 0.03f)
|
||||||
setTextSizePercentOfScreenHeight(labelDay, 0.025f)
|
ViewUtils.setTextSizePercentOfScreenHeight(labelDay, 0.025f)
|
||||||
setTextSizePercentOfScreenHeight(labelMonth, 0.025f)
|
ViewUtils.setTextSizePercentOfScreenHeight(labelMonth, 0.025f)
|
||||||
setTextSizePercentOfScreenHeight(labelYear, 0.025f)
|
ViewUtils.setTextSizePercentOfScreenHeight(labelYear, 0.025f)
|
||||||
//
|
|
||||||
|
|
||||||
// gespeicherte Antwort (YYYY-MM-DD) lesen
|
// gespeicherte Antwort (YYYY-MM-DD) lesen
|
||||||
val (savedYear, savedMonthIndex, savedDay) = question.question?.let {
|
val (savedYear, savedMonthIndex, savedDay) = question.question?.let {
|
||||||
@ -75,9 +80,9 @@ class HandlerDateSpinner(
|
|||||||
val defaultYear = savedYear ?: today.get(Calendar.YEAR)
|
val defaultYear = savedYear ?: today.get(Calendar.YEAR)
|
||||||
|
|
||||||
// Spinner responsiv aufsetzen (Schrift + Zeilenhöhe ohne Abschneiden)
|
// Spinner responsiv aufsetzen (Schrift + Zeilenhöhe ohne Abschneiden)
|
||||||
setupSpinner(spinnerDay, days, defaultDay)
|
ViewUtils.setupResponsiveSpinner(context, spinnerDay, days, defaultDay)
|
||||||
setupSpinner(spinnerMonth, months, defaultMonth)
|
ViewUtils.setupResponsiveSpinner(context, spinnerMonth, months, defaultMonth)
|
||||||
setupSpinner(spinnerYear, years, defaultYear)
|
ViewUtils.setupResponsiveSpinner(context, spinnerYear, years, defaultYear)
|
||||||
|
|
||||||
// DB-Abfrage, falls noch nicht im answers-Map
|
// DB-Abfrage, falls noch nicht im answers-Map
|
||||||
val answerMapKey = question.question ?: (question.id ?: "")
|
val answerMapKey = question.question ?: (question.id ?: "")
|
||||||
@ -207,71 +212,4 @@ class HandlerDateSpinner(
|
|||||||
return sdf.parse(dateString)
|
return sdf.parse(dateString)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Textgröße prozentual zur Bildschirmhöhe (in sp)
|
|
||||||
private fun setTextSizePercentOfScreenHeight(view: TextView, percentOfHeight: Float) {
|
|
||||||
val dm = (view.context ?: layout.context).resources.displayMetrics
|
|
||||||
val sp = (dm.heightPixels * percentOfHeight) / dm.scaledDensity
|
|
||||||
TextViewCompat.setAutoSizeTextTypeWithDefaults(view, TextViewCompat.AUTO_SIZE_TEXT_TYPE_NONE)
|
|
||||||
view.setTextSize(TypedValue.COMPLEX_UNIT_SP, sp)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Spinner-Adapter: Schrift & Zeilenhöhe dynamisch, kein Abschneiden
|
|
||||||
private fun <T> setupSpinner(spinner: Spinner, items: List<T>, defaultSelection: 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) // ~2.75% der Bildschirmhöhe
|
|
||||||
val textPx = pxFromSp(textSp)
|
|
||||||
val vPadPx = (textPx * 0.50f).toInt() // vertikales Padding
|
|
||||||
val rowHeight = (textPx * 2.20f + 2 * vPadPx).toInt() // feste Zeilenhöhe
|
|
||||||
|
|
||||||
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 android.view.Gravity.HORIZONTAL_GRAVITY_MASK) or android.view.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()
|
|
||||||
|
|
||||||
defaultSelection?.let {
|
|
||||||
val index = items.indexOf(it)
|
|
||||||
if (index >= 0) spinner.setSelection(index)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
@ -1,11 +1,16 @@
|
|||||||
package com.dano.test1
|
package com.dano.test1.questionnaire.handlers
|
||||||
|
|
||||||
import android.content.Context
|
import android.content.Context
|
||||||
import android.util.TypedValue
|
|
||||||
import android.view.Gravity
|
import android.view.Gravity
|
||||||
import android.view.View
|
import android.view.View
|
||||||
import android.widget.*
|
import android.widget.*
|
||||||
import androidx.core.widget.TextViewCompat
|
import com.dano.test1.questionnaire.GlobalValues
|
||||||
|
import com.dano.test1.LanguageManager
|
||||||
|
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.*
|
import kotlinx.coroutines.*
|
||||||
|
|
||||||
/*
|
/*
|
||||||
@ -67,8 +72,8 @@ class HandlerGlassScaleQuestion(
|
|||||||
titleTv.text = question.textKey?.let { LanguageManager.getText(languageID, it) } ?: ""
|
titleTv.text = question.textKey?.let { LanguageManager.getText(languageID, it) } ?: ""
|
||||||
questionTv.text = question.question?.let { LanguageManager.getText(languageID, it) } ?: ""
|
questionTv.text = question.question?.let { LanguageManager.getText(languageID, it) } ?: ""
|
||||||
|
|
||||||
setTextSizePercentOfScreenHeight(titleTv, 0.03f)
|
ViewUtils.setTextSizePercentOfScreenHeight(titleTv, 0.03f)
|
||||||
setTextSizePercentOfScreenHeight(questionTv, 0.03f)
|
ViewUtils.setTextSizePercentOfScreenHeight(questionTv, 0.03f)
|
||||||
|
|
||||||
// Header Icons
|
// Header Icons
|
||||||
val header = layout.findViewById<LinearLayout>(R.id.glass_header)
|
val header = layout.findViewById<LinearLayout>(R.id.glass_header)
|
||||||
@ -150,7 +155,7 @@ class HandlerGlassScaleQuestion(
|
|||||||
text = LanguageManager.getText(languageID, symptomKey)
|
text = LanguageManager.getText(languageID, symptomKey)
|
||||||
layoutParams = TableRow.LayoutParams(0, TableRow.LayoutParams.WRAP_CONTENT, 4f)
|
layoutParams = TableRow.LayoutParams(0, TableRow.LayoutParams.WRAP_CONTENT, 4f)
|
||||||
setPadding(4, 16, 4, 16)
|
setPadding(4, 16, 4, 16)
|
||||||
setTextSizePercentOfScreenHeight(this, 0.022f)
|
ViewUtils.setTextSizePercentOfScreenHeight(this, 0.022f)
|
||||||
}
|
}
|
||||||
row.addView(symptomText)
|
row.addView(symptomText)
|
||||||
|
|
||||||
@ -277,10 +282,4 @@ class HandlerGlassScaleQuestion(
|
|||||||
else -> null
|
else -> null
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun setTextSizePercentOfScreenHeight(view: TextView, percentOfHeight: Float) {
|
|
||||||
val dm = (view.context ?: layout.context).resources.displayMetrics
|
|
||||||
val sp = (dm.heightPixels * percentOfHeight) / dm.scaledDensity
|
|
||||||
TextViewCompat.setAutoSizeTextTypeWithDefaults(view, TextViewCompat.AUTO_SIZE_TEXT_TYPE_NONE)
|
|
||||||
view.setTextSize(TypedValue.COMPLEX_UNIT_SP, sp)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
@ -1,12 +1,18 @@
|
|||||||
package com.dano.test1
|
package com.dano.test1.questionnaire.handlers
|
||||||
|
|
||||||
|
import android.util.TypedValue
|
||||||
import android.view.View
|
import android.view.View
|
||||||
import android.widget.*
|
import android.widget.*
|
||||||
import android.text.Html
|
import android.text.Html
|
||||||
import kotlinx.coroutines.*
|
|
||||||
import android.util.TypedValue
|
|
||||||
import android.widget.TextView
|
|
||||||
import androidx.core.widget.TextViewCompat
|
import androidx.core.widget.TextViewCompat
|
||||||
|
import kotlinx.coroutines.*
|
||||||
|
import com.dano.test1.questionnaire.GlobalValues
|
||||||
|
import com.dano.test1.LanguageManager
|
||||||
|
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
|
import com.google.android.material.button.MaterialButton
|
||||||
|
|
||||||
/*
|
/*
|
||||||
@ -58,8 +64,8 @@ class HandlerLastPage(
|
|||||||
applyResponsiveTextSizing(finishBtn)
|
applyResponsiveTextSizing(finishBtn)
|
||||||
|
|
||||||
// Überschriften responsiv skalieren (wie zuvor)
|
// Überschriften responsiv skalieren (wie zuvor)
|
||||||
setTextSizePercentOfScreenHeight(titleTv, 0.03f)
|
ViewUtils.setTextSizePercentOfScreenHeight(titleTv, 0.03f)
|
||||||
setTextSizePercentOfScreenHeight(questionTv, 0.03f)
|
ViewUtils.setTextSizePercentOfScreenHeight(questionTv, 0.03f)
|
||||||
|
|
||||||
// Buttons
|
// Buttons
|
||||||
prevBtn.setOnClickListener { goToPreviousQuestion() }
|
prevBtn.setOnClickListener { goToPreviousQuestion() }
|
||||||
@ -128,14 +134,6 @@ class HandlerLastPage(
|
|||||||
}
|
}
|
||||||
// ----------------------------------------------------------------
|
// ----------------------------------------------------------------
|
||||||
|
|
||||||
// Helper: Textgröße prozentual zur Bildschirmhöhe setzen (in sp)
|
|
||||||
private fun setTextSizePercentOfScreenHeight(view: TextView, percentOfHeight: Float) {
|
|
||||||
val dm = (view.context ?: layout.context).resources.displayMetrics
|
|
||||||
val sp = (dm.heightPixels * percentOfHeight) / dm.scaledDensity
|
|
||||||
TextViewCompat.setAutoSizeTextTypeWithDefaults(view, TextViewCompat.AUTO_SIZE_TEXT_TYPE_NONE)
|
|
||||||
view.setTextSize(TypedValue.COMPLEX_UNIT_SP, sp)
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun sumPoints(): Int =
|
private fun sumPoints(): Int =
|
||||||
answers.filterKeys { it.endsWith("_points") }
|
answers.filterKeys { it.endsWith("_points") }
|
||||||
.values.mapNotNull { it as? Int }
|
.values.mapNotNull { it as? Int }
|
||||||
@ -1,11 +1,18 @@
|
|||||||
package com.dano.test1
|
package com.dano.test1.questionnaire.handlers
|
||||||
|
|
||||||
import android.content.Context
|
import android.content.Context
|
||||||
|
import android.util.TypedValue
|
||||||
import android.view.View
|
import android.view.View
|
||||||
import android.widget.*
|
import android.widget.*
|
||||||
import kotlinx.coroutines.*
|
|
||||||
import android.util.TypedValue
|
|
||||||
import androidx.core.widget.TextViewCompat
|
import androidx.core.widget.TextViewCompat
|
||||||
|
import kotlinx.coroutines.*
|
||||||
|
import com.dano.test1.questionnaire.GlobalValues
|
||||||
|
import com.dano.test1.LanguageManager
|
||||||
|
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:
|
Zweck:
|
||||||
@ -38,8 +45,8 @@ class HandlerMultiCheckboxQuestion(
|
|||||||
questionTitle.text = this.question.question?.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)
|
// Textgrößen pro Bildschirmhöhe (wie bei deinen anderen Handlern)
|
||||||
setTextSizePercentOfScreenHeight(questionTextView, 0.03f) // Überschrift
|
ViewUtils.setTextSizePercentOfScreenHeight(questionTextView, 0.03f)
|
||||||
setTextSizePercentOfScreenHeight(questionTitle, 0.03f) // Frage
|
ViewUtils.setTextSizePercentOfScreenHeight(questionTitle, 0.03f)
|
||||||
|
|
||||||
container.removeAllViews()
|
container.removeAllViews()
|
||||||
|
|
||||||
@ -204,10 +211,4 @@ class HandlerMultiCheckboxQuestion(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun setTextSizePercentOfScreenHeight(view: TextView, percentOfHeight: Float) {
|
|
||||||
val dm = (view.context ?: layout.context).resources.displayMetrics
|
|
||||||
val sp = (dm.heightPixels * percentOfHeight) / dm.scaledDensity
|
|
||||||
TextViewCompat.setAutoSizeTextTypeWithDefaults(view, TextViewCompat.AUTO_SIZE_TEXT_TYPE_NONE)
|
|
||||||
view.setTextSize(TypedValue.COMPLEX_UNIT_SP, sp)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
@ -1,12 +1,17 @@
|
|||||||
package com.dano.test1
|
package com.dano.test1.questionnaire.handlers
|
||||||
|
|
||||||
import android.content.Context
|
import android.content.Context
|
||||||
import android.view.View
|
import android.view.View
|
||||||
import android.text.Html
|
import android.text.Html
|
||||||
import android.widget.*
|
import android.widget.*
|
||||||
import kotlinx.coroutines.*
|
import kotlinx.coroutines.*
|
||||||
import android.util.TypedValue
|
import com.dano.test1.questionnaire.GlobalValues
|
||||||
import androidx.core.widget.TextViewCompat // <— hinzugefügt
|
import com.dano.test1.LanguageManager
|
||||||
|
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:
|
Zweck:
|
||||||
@ -41,11 +46,8 @@ class HandlerRadioQuestion(
|
|||||||
Html.fromHtml(LanguageManager.getText(languageID, it), Html.FROM_HTML_MODE_LEGACY)
|
Html.fromHtml(LanguageManager.getText(languageID, it), Html.FROM_HTML_MODE_LEGACY)
|
||||||
} ?: ""
|
} ?: ""
|
||||||
|
|
||||||
//
|
ViewUtils.setTextSizePercentOfScreenHeight(questionTextView, 0.03f)
|
||||||
// Titel/Frage: 3% der Bildschirmhöhe
|
ViewUtils.setTextSizePercentOfScreenHeight(questionTitle, 0.03f)
|
||||||
setTextSizePercentOfScreenHeight(questionTextView, 0.03f)
|
|
||||||
setTextSizePercentOfScreenHeight(questionTitle, 0.03f)
|
|
||||||
// ===================================================
|
|
||||||
|
|
||||||
radioGroup.removeAllViews()
|
radioGroup.removeAllViews()
|
||||||
|
|
||||||
@ -55,7 +57,7 @@ class HandlerRadioQuestion(
|
|||||||
tag = option.key
|
tag = option.key
|
||||||
|
|
||||||
// RadioButton-Text analog zu EditTexts: 2.5% der Bildschirmhöhe
|
// RadioButton-Text analog zu EditTexts: 2.5% der Bildschirmhöhe
|
||||||
setTextSizePercentOfScreenHeight(this, 0.025f)
|
ViewUtils.setTextSizePercentOfScreenHeight(this, 0.025f)
|
||||||
|
|
||||||
layoutParams = RadioGroup.LayoutParams(
|
layoutParams = RadioGroup.LayoutParams(
|
||||||
RadioGroup.LayoutParams.MATCH_PARENT,
|
RadioGroup.LayoutParams.MATCH_PARENT,
|
||||||
@ -137,15 +139,6 @@ class HandlerRadioQuestion(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// setzt Textgröße prozentual zur Bildschirmhöhe (in sp)
|
|
||||||
private fun setTextSizePercentOfScreenHeight(view: TextView, percentOfHeight: Float) {
|
|
||||||
val dm = (view.context ?: layout.context).resources.displayMetrics
|
|
||||||
val sp = (dm.heightPixels * percentOfHeight) / dm.scaledDensity
|
|
||||||
TextViewCompat.setAutoSizeTextTypeWithDefaults(view, TextViewCompat.AUTO_SIZE_TEXT_TYPE_NONE)
|
|
||||||
view.setTextSize(TypedValue.COMPLEX_UNIT_SP, sp)
|
|
||||||
}
|
|
||||||
// ————————————————————————————————————————————————————————————————
|
|
||||||
|
|
||||||
private fun restorePreviousAnswer(radioGroup: RadioGroup) {
|
private fun restorePreviousAnswer(radioGroup: RadioGroup) {
|
||||||
question.question?.let { questionKey ->
|
question.question?.let { questionKey ->
|
||||||
val savedAnswer = answers[questionKey] as? String
|
val savedAnswer = answers[questionKey] as? String
|
||||||
@ -1,13 +1,17 @@
|
|||||||
package com.dano.test1
|
package com.dano.test1.questionnaire.handlers
|
||||||
|
|
||||||
import android.content.Context
|
import android.content.Context
|
||||||
import android.view.View
|
import android.view.View
|
||||||
import android.view.ViewGroup
|
|
||||||
import android.widget.*
|
import android.widget.*
|
||||||
import kotlinx.coroutines.*
|
import kotlinx.coroutines.*
|
||||||
import android.util.TypedValue
|
import com.dano.test1.ui.Countries
|
||||||
import android.widget.TextView
|
import com.dano.test1.questionnaire.GlobalValues
|
||||||
import androidx.core.widget.TextViewCompat
|
import com.dano.test1.LanguageManager
|
||||||
|
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:
|
Zweck:
|
||||||
@ -42,9 +46,9 @@ class HandlerStringSpinner(
|
|||||||
questionTextView.text = question.question?.let { LanguageManager.getText(languageID, it) } ?: ""
|
questionTextView.text = question.question?.let { LanguageManager.getText(languageID, it) } ?: ""
|
||||||
textView.text = question.textKey?.let { LanguageManager.getText(languageID, it) } ?: ""
|
textView.text = question.textKey?.let { LanguageManager.getText(languageID, it) } ?: ""
|
||||||
|
|
||||||
// Textgrößen prozentual zur Bildschirmhöhe (wie im HandlerRadioQuestion)
|
// Textgrößen prozentual zur Bildschirmhöhe
|
||||||
setTextSizePercentOfScreenHeight(textView, 0.03f)
|
ViewUtils.setTextSizePercentOfScreenHeight(textView, 0.03f)
|
||||||
setTextSizePercentOfScreenHeight(questionTextView, 0.03f)
|
ViewUtils.setTextSizePercentOfScreenHeight(questionTextView, 0.03f)
|
||||||
|
|
||||||
val options = buildOptionsList()
|
val options = buildOptionsList()
|
||||||
|
|
||||||
@ -52,7 +56,7 @@ class HandlerStringSpinner(
|
|||||||
val savedSelection = question.question?.let { answers[it] as? String }
|
val savedSelection = question.question?.let { answers[it] as? String }
|
||||||
|
|
||||||
// Spinner aufsetzen
|
// Spinner aufsetzen
|
||||||
setupSpinner(spinner, options, savedSelection)
|
ViewUtils.setupResponsiveSpinner(context, spinner, options, savedSelection)
|
||||||
|
|
||||||
// Falls noch keine Antwort im Map: aus DB laden
|
// Falls noch keine Antwort im Map: aus DB laden
|
||||||
val answerMapKey = question.question ?: (question.id ?: "")
|
val answerMapKey = question.question ?: (question.id ?: "")
|
||||||
@ -119,73 +123,4 @@ class HandlerStringSpinner(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Textgröße prozentual zur Bildschirmhöhe setzen und AutoSize deaktivieren
|
|
||||||
private fun setTextSizePercentOfScreenHeight(view: TextView, percentOfHeight: Float) {
|
|
||||||
val dm = (view.context ?: layout.context).resources.displayMetrics
|
|
||||||
val sp = (dm.heightPixels * percentOfHeight) / dm.scaledDensity
|
|
||||||
TextViewCompat.setAutoSizeTextTypeWithDefaults(view, TextViewCompat.AUTO_SIZE_TEXT_TYPE_NONE)
|
|
||||||
view.setTextSize(TypedValue.COMPLEX_UNIT_SP, sp)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Spinner-Adapter mit dynamischer Schrift & stabiler Dropdown-Zeilenhöhe (kein Abschneiden)
|
|
||||||
private fun <T> setupSpinner(spinner: Spinner, 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()
|
|
||||||
|
|
||||||
// Schrift & abgeleitete Höhen (wie beim Value-Spinner-Fix)
|
|
||||||
val textSp = spFromScreenHeight(0.0275f) // ~2.75% der Bildschirmhöhe
|
|
||||||
val textPx = pxFromSp(textSp)
|
|
||||||
val vPadPx = (textPx * 0.50f).toInt() // vertikales Padding
|
|
||||||
val rowHeight = (textPx * 2.20f + 2 * vPadPx).toInt() // feste Zeilenhöhe, verhindert Abschneiden
|
|
||||||
|
|
||||||
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 android.view.Gravity.HORIZONTAL_GRAVITY_MASK) or android.view.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) // ausgewählte Ansicht
|
|
||||||
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) // Dropdown-Zeilen: Höhe erzwingen
|
|
||||||
return v
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
adapter.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item)
|
|
||||||
spinner.adapter = adapter
|
|
||||||
|
|
||||||
// Spinner selbst ausreichend hoch machen
|
|
||||||
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)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
@ -1,12 +1,16 @@
|
|||||||
package com.dano.test1
|
package com.dano.test1.questionnaire.handlers
|
||||||
|
|
||||||
import android.content.Context
|
import android.content.Context
|
||||||
import android.view.View
|
import android.view.View
|
||||||
import android.view.ViewGroup
|
|
||||||
import android.widget.*
|
import android.widget.*
|
||||||
import kotlinx.coroutines.*
|
import kotlinx.coroutines.*
|
||||||
import android.util.TypedValue
|
import com.dano.test1.questionnaire.GlobalValues
|
||||||
import androidx.core.widget.TextViewCompat // <- NEU
|
import com.dano.test1.LanguageManager
|
||||||
|
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:
|
Zweck:
|
||||||
@ -43,10 +47,8 @@ class HandlerValueSpinner(
|
|||||||
questionTextView.text = question.question?.let { LanguageManager.getText(languageID, it) } ?: ""
|
questionTextView.text = question.question?.let { LanguageManager.getText(languageID, it) } ?: ""
|
||||||
textView.text = question.textKey?.let { LanguageManager.getText(languageID, it) } ?: ""
|
textView.text = question.textKey?.let { LanguageManager.getText(languageID, it) } ?: ""
|
||||||
|
|
||||||
// Schriftgrößen wie im HandlerRadioQuestion
|
ViewUtils.setTextSizePercentOfScreenHeight(textView, 0.03f)
|
||||||
// Titel/Frage: 3% der Bildschirmhöhe
|
ViewUtils.setTextSizePercentOfScreenHeight(questionTextView, 0.03f)
|
||||||
setTextSizePercentOfScreenHeight(textView, 0.03f)
|
|
||||||
setTextSizePercentOfScreenHeight(questionTextView, 0.03f)
|
|
||||||
|
|
||||||
val prompt = LanguageManager.getText(languageID, "choose_answer")
|
val prompt = LanguageManager.getText(languageID, "choose_answer")
|
||||||
val spinnerItems: List<String> = listOf(prompt) + if (question.range != null) {
|
val spinnerItems: List<String> = listOf(prompt) + if (question.range != null) {
|
||||||
@ -56,7 +58,7 @@ class HandlerValueSpinner(
|
|||||||
}
|
}
|
||||||
|
|
||||||
val savedValue = question.question?.let { answers[it] as? String }
|
val savedValue = question.question?.let { answers[it] as? String }
|
||||||
setupSpinner(spinner, spinnerItems, savedValue)
|
ViewUtils.setupResponsiveSpinner(context, spinner, spinnerItems, savedValue)
|
||||||
|
|
||||||
//DB-Abfrage falls noch keine Antwort im Map existiert
|
//DB-Abfrage falls noch keine Antwort im Map existiert
|
||||||
val answerMapKey = question.question ?: (question.id ?: "")
|
val answerMapKey = question.question ?: (question.id ?: "")
|
||||||
@ -127,72 +129,4 @@ class HandlerValueSpinner(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// setzt Textgröße prozentual zur Bildschirmhöhe (in sp)
|
|
||||||
private fun setTextSizePercentOfScreenHeight(view: TextView, percentOfHeight: Float) {
|
|
||||||
val dm = (view.context ?: layout.context).resources.displayMetrics
|
|
||||||
val sp = (dm.heightPixels * percentOfHeight) / dm.scaledDensity
|
|
||||||
TextViewCompat.setAutoSizeTextTypeWithDefaults(view, TextViewCompat.AUTO_SIZE_TEXT_TYPE_NONE)
|
|
||||||
view.setTextSize(TypedValue.COMPLEX_UNIT_SP, sp)
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun <T> setupSpinner(spinner: Spinner, 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()
|
|
||||||
|
|
||||||
// Schrift & abgeleitete Höhen
|
|
||||||
val textSp = spFromScreenHeight(0.0275f) // ~2.75% der Bildschirmhöhe
|
|
||||||
val textPx = pxFromSp(textSp)
|
|
||||||
val vPadPx = (textPx * 0.50f).toInt() // vertikales Padding
|
|
||||||
val rowHeight = (textPx * 2.20f + 2 * vPadPx).toInt() // feste Zeilenhöhe
|
|
||||||
|
|
||||||
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 android.view.Gravity.HORIZONTAL_GRAVITY_MASK) or android.view.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) // ausgewählte Ansicht
|
|
||||||
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) // Dropdown-Zeilen: Höhe erzwingen
|
|
||||||
return v
|
|
||||||
}
|
|
||||||
}
|
|
||||||
adapter.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item)
|
|
||||||
spinner.adapter = adapter
|
|
||||||
|
|
||||||
// Spinner selbst ausreichend hoch machen
|
|
||||||
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)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
}
|
||||||
46
app/src/main/java/com/dano/test1/ui/AbTestSettingsStore.kt
Normal file
46
app/src/main/java/com/dano/test1/ui/AbTestSettingsStore.kt
Normal file
@ -0,0 +1,46 @@
|
|||||||
|
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
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -1,4 +1,6 @@
|
|||||||
package com.dano.test1
|
package com.dano.test1.ui
|
||||||
|
|
||||||
|
import com.dano.test1.LanguageManager
|
||||||
|
|
||||||
object Countries {
|
object Countries {
|
||||||
fun getAllCountries(languageID: String): List<String> {
|
fun getAllCountries(languageID: String): List<String> {
|
||||||
@ -1,17 +1,25 @@
|
|||||||
package com.dano.test1
|
package com.dano.test1.ui
|
||||||
|
|
||||||
|
import android.graphics.Color
|
||||||
|
import android.graphics.Typeface
|
||||||
import android.util.Log
|
import android.util.Log
|
||||||
import android.view.View
|
import android.view.View
|
||||||
import android.widget.*
|
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 com.dano.test1.MyApp
|
||||||
|
import com.dano.test1.R
|
||||||
import com.dano.test1.data.Client
|
import com.dano.test1.data.Client
|
||||||
|
import com.dano.test1.data.HeaderOrderRepository
|
||||||
import com.dano.test1.data.Question
|
import com.dano.test1.data.Question
|
||||||
import com.dano.test1.data.Questionnaire
|
import com.dano.test1.data.Questionnaire
|
||||||
import kotlinx.coroutines.*
|
import kotlinx.coroutines.*
|
||||||
import kotlin.math.roundToInt
|
|
||||||
import org.json.JSONArray
|
import org.json.JSONArray
|
||||||
|
|
||||||
class DatabaseButtonHandler(
|
class DatabaseButtonHandler(
|
||||||
private val activity: MainActivity,
|
private val activity: AppCompatActivity,
|
||||||
private val databaseButton: Button,
|
private val databaseButton: Button,
|
||||||
private val onClose: () -> Unit,
|
private val onClose: () -> Unit,
|
||||||
private val languageIDProvider: () -> String = { "GERMAN" }
|
private val languageIDProvider: () -> String = { "GERMAN" }
|
||||||
@ -361,7 +369,7 @@ class DatabaseButtonHandler(
|
|||||||
val row = TableRow(activity).apply {
|
val row = TableRow(activity).apply {
|
||||||
isClickable = true
|
isClickable = true
|
||||||
isFocusable = true
|
isFocusable = true
|
||||||
setBackgroundColor(android.graphics.Color.TRANSPARENT)
|
setBackgroundColor(Color.TRANSPARENT)
|
||||||
setOnClickListener { onClick() }
|
setOnClickListener { onClick() }
|
||||||
}
|
}
|
||||||
cells.forEachIndexed { index, text ->
|
cells.forEachIndexed { index, text ->
|
||||||
@ -396,7 +404,7 @@ class DatabaseButtonHandler(
|
|||||||
this.text = text
|
this.text = text
|
||||||
setPadding(dp(12), dp(10), dp(12), dp(10))
|
setPadding(dp(12), dp(10), dp(12), dp(10))
|
||||||
textSize = 16f
|
textSize = 16f
|
||||||
setTypeface(typeface, android.graphics.Typeface.BOLD)
|
setTypeface(typeface, Typeface.BOLD)
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun makeBodyCell(
|
private fun makeBodyCell(
|
||||||
@ -412,10 +420,7 @@ class DatabaseButtonHandler(
|
|||||||
bgColor?.let { setBackgroundColor(it) }
|
bgColor?.let { setBackgroundColor(it) }
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun dp(value: Int): Int {
|
private fun dp(value: Int): Int = ViewUtils.dp(activity, value)
|
||||||
val density = activity.resources.displayMetrics.density
|
|
||||||
return (value * density).roundToInt()
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun <T : View> requireView(id: Int, name: String): T {
|
private fun <T : View> requireView(id: Int, name: String): T {
|
||||||
val v = activity.findViewById<T>(id)
|
val v = activity.findViewById<T>(id)
|
||||||
92
app/src/main/java/com/dano/test1/ui/DevSettingsActivity.kt
Normal file
92
app/src/main/java/com/dano/test1/ui/DevSettingsActivity.kt
Normal file
@ -0,0 +1,92 @@
|
|||||||
|
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
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -1,19 +1,34 @@
|
|||||||
package com.dano.test1
|
package com.dano.test1.ui
|
||||||
|
|
||||||
|
import android.content.Context
|
||||||
import android.content.res.ColorStateList
|
import android.content.res.ColorStateList
|
||||||
import android.graphics.Color
|
import android.graphics.Color
|
||||||
|
import android.graphics.drawable.GradientDrawable
|
||||||
import android.os.Handler
|
import android.os.Handler
|
||||||
import android.os.Looper
|
import android.os.Looper
|
||||||
import android.util.TypedValue
|
import android.util.TypedValue
|
||||||
import android.view.Gravity
|
import android.view.Gravity
|
||||||
import android.view.View
|
import android.view.View
|
||||||
import android.widget.*
|
import android.widget.*
|
||||||
|
import com.dano.test1.LanguageManager
|
||||||
|
import com.dano.test1.MainActivity
|
||||||
|
import com.dano.test1.R
|
||||||
|
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 com.google.android.material.button.MaterialButton
|
||||||
import org.json.JSONArray
|
import org.json.JSONArray
|
||||||
import org.json.JSONObject
|
import org.json.JSONObject
|
||||||
import java.io.File
|
import java.io.File
|
||||||
import java.util.concurrent.TimeUnit
|
import java.util.concurrent.TimeUnit
|
||||||
import com.google.android.material.dialog.MaterialAlertDialogBuilder
|
import com.google.android.material.dialog.MaterialAlertDialogBuilder
|
||||||
|
import com.dano.test1.utils.ViewUtils
|
||||||
|
|
||||||
|
|
||||||
var RHS_POINTS: Int? = null
|
var RHS_POINTS: Int? = null
|
||||||
@ -28,10 +43,8 @@ class HandlerOpeningScreen(private val activity: MainActivity) {
|
|||||||
private lateinit var buttonContainer: LinearLayout
|
private lateinit var buttonContainer: LinearLayout
|
||||||
private lateinit var buttonLoad: Button
|
private lateinit var buttonLoad: Button
|
||||||
private lateinit var saveButton: Button
|
private lateinit var saveButton: Button
|
||||||
private lateinit var editButton: Button
|
|
||||||
private lateinit var uploadButton: Button
|
private lateinit var uploadButton: Button
|
||||||
private lateinit var downloadButton: Button
|
private lateinit var downloadButton: Button
|
||||||
private lateinit var databaseButton: Button
|
|
||||||
private lateinit var statusSession: TextView
|
private lateinit var statusSession: TextView
|
||||||
private lateinit var statusOnline: TextView
|
private lateinit var statusOnline: TextView
|
||||||
private val SESSION_WARN_AFTER_MS = 12 * 60 * 60 * 1000L // 12h
|
private val SESSION_WARN_AFTER_MS = 12 * 60 * 60 * 1000L // 12h
|
||||||
@ -80,10 +93,8 @@ class HandlerOpeningScreen(private val activity: MainActivity) {
|
|||||||
setupLanguageSpinner()
|
setupLanguageSpinner()
|
||||||
setupLoadButton()
|
setupLoadButton()
|
||||||
setupSaveButton()
|
setupSaveButton()
|
||||||
setupEditButtonHandler()
|
|
||||||
setupUploadButton()
|
setupUploadButton()
|
||||||
setupDownloadButton()
|
setupDownloadButton()
|
||||||
setupDatabaseButtonHandler()
|
|
||||||
|
|
||||||
uiHandler.removeCallbacks(statusTicker)
|
uiHandler.removeCallbacks(statusTicker)
|
||||||
updateStatusStrip()
|
updateStatusStrip()
|
||||||
@ -109,13 +120,11 @@ class HandlerOpeningScreen(private val activity: MainActivity) {
|
|||||||
buttonContainer = activity.findViewById(R.id.buttonContainer)
|
buttonContainer = activity.findViewById(R.id.buttonContainer)
|
||||||
buttonLoad = activity.findViewById(R.id.loadButton)
|
buttonLoad = activity.findViewById(R.id.loadButton)
|
||||||
saveButton = activity.findViewById(R.id.saveButton)
|
saveButton = activity.findViewById(R.id.saveButton)
|
||||||
editButton = activity.findViewById(R.id.editButton)
|
|
||||||
uploadButton = activity.findViewById(R.id.uploadButton)
|
uploadButton = activity.findViewById(R.id.uploadButton)
|
||||||
|
|
||||||
downloadButton = activity.findViewById(R.id.downloadButton)
|
downloadButton = activity.findViewById(R.id.downloadButton)
|
||||||
downloadButton.visibility = View.GONE
|
downloadButton.visibility = View.GONE
|
||||||
|
|
||||||
databaseButton = activity.findViewById(R.id.databaseButton)
|
|
||||||
statusSession = activity.findViewById(R.id.statusSession)
|
statusSession = activity.findViewById(R.id.statusSession)
|
||||||
statusOnline = activity.findViewById(R.id.statusOnline)
|
statusOnline = activity.findViewById(R.id.statusOnline)
|
||||||
val tag = editText.tag as? String ?: ""
|
val tag = editText.tag as? String ?: ""
|
||||||
@ -217,7 +226,12 @@ class HandlerOpeningScreen(private val activity: MainActivity) {
|
|||||||
setOnClickListener {
|
setOnClickListener {
|
||||||
GlobalValues.LAST_CLIENT_CODE = GlobalValues.LOADED_CLIENT_CODE
|
GlobalValues.LAST_CLIENT_CODE = GlobalValues.LOADED_CLIENT_CODE
|
||||||
val fileName = questionnaireFiles[this] ?: return@setOnClickListener
|
val fileName = questionnaireFiles[this] ?: return@setOnClickListener
|
||||||
val questionnaire = QuestionnaireGeneric(fileName)
|
val variant = AbTestSettingsStore.effectiveVariant(activity)
|
||||||
|
val questionnaire: QuestionnaireBase<*> = if (variant == "B") {
|
||||||
|
QuestionnaireAllInOne(fileName)
|
||||||
|
} else {
|
||||||
|
QuestionnaireGeneric(fileName)
|
||||||
|
}
|
||||||
startQuestionnaire(questionnaire)
|
startQuestionnaire(questionnaire)
|
||||||
applySetButtonsEnabled(dynamicButtons.filter { it == this }, allowCompleted = false, force = false)
|
applySetButtonsEnabled(dynamicButtons.filter { it == this }, allowCompleted = false, force = false)
|
||||||
}
|
}
|
||||||
@ -317,7 +331,7 @@ class HandlerOpeningScreen(private val activity: MainActivity) {
|
|||||||
dynamicButtonsProvider = { dynamicButtons },
|
dynamicButtonsProvider = { dynamicButtons },
|
||||||
buttonPoints = buttonPoints,
|
buttonPoints = buttonPoints,
|
||||||
updateButtonTexts = { applyUpdateButtonTexts(force = false) },
|
updateButtonTexts = { applyUpdateButtonTexts(force = false) },
|
||||||
setButtonsEnabled = { list -> applySetButtonsEnabled(list, allowCompleted = false, force = false) },
|
setButtonsEnabled = { list -> applySetButtonsEnabled(list, allowCompleted = true, force = false) },
|
||||||
updateMainButtonsState = { updateMainButtonsState(it) },
|
updateMainButtonsState = { updateMainButtonsState(it) },
|
||||||
).setup()
|
).setup()
|
||||||
}
|
}
|
||||||
@ -373,10 +387,8 @@ class HandlerOpeningScreen(private val activity: MainActivity) {
|
|||||||
}
|
}
|
||||||
buttonLoad.text = t("load")
|
buttonLoad.text = t("load")
|
||||||
saveButton.text = t("save")
|
saveButton.text = t("save")
|
||||||
editButton.text = t("edit")
|
|
||||||
uploadButton.text = t("upload")
|
uploadButton.text = t("upload")
|
||||||
downloadButton.text = t("download")
|
downloadButton.text = t("download")
|
||||||
databaseButton.text = t("database")
|
|
||||||
val hintTag = editText.tag as? String ?: ""
|
val hintTag = editText.tag as? String ?: ""
|
||||||
editText.hint = t(hintTag)
|
editText.hint = t(hintTag)
|
||||||
val coachTag = coachEditText.tag as? String ?: ""
|
val coachTag = coachEditText.tag as? String ?: ""
|
||||||
@ -441,23 +453,6 @@ class HandlerOpeningScreen(private val activity: MainActivity) {
|
|||||||
).setup()
|
).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() {
|
private fun setupUploadButton() {
|
||||||
uploadButton.text = t("upload")
|
uploadButton.text = t("upload")
|
||||||
uploadButton.setOnClickListener {
|
uploadButton.setOnClickListener {
|
||||||
@ -519,17 +514,8 @@ class HandlerOpeningScreen(private val activity: MainActivity) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun setupDatabaseButtonHandler() {
|
|
||||||
DatabaseButtonHandler(
|
|
||||||
activity = activity,
|
|
||||||
databaseButton = databaseButton,
|
|
||||||
onClose = { init() },
|
|
||||||
languageIDProvider = { languageID }
|
|
||||||
).setup()
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun updateMainButtonsState(isDatabaseAvailable: Boolean) {
|
private fun updateMainButtonsState(isDatabaseAvailable: Boolean) {
|
||||||
listOf(buttonLoad, saveButton, editButton, databaseButton).forEach { b ->
|
listOf(buttonLoad, saveButton).forEach { b ->
|
||||||
b.isEnabled = isDatabaseAvailable
|
b.isEnabled = isDatabaseAvailable
|
||||||
b.alpha = if (isDatabaseAvailable) 1.0f else 0.5f
|
b.alpha = if (isDatabaseAvailable) 1.0f else 0.5f
|
||||||
}
|
}
|
||||||
@ -559,7 +545,7 @@ class HandlerOpeningScreen(private val activity: MainActivity) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun dp(v: Int): Int = (v * activity.resources.displayMetrics.density).toInt()
|
private fun dp(v: Int): Int = ViewUtils.dp(activity, v)
|
||||||
|
|
||||||
private fun isCompleted(button: Button): Boolean {
|
private fun isCompleted(button: Button): Boolean {
|
||||||
val fileName = questionnaireFiles[button] ?: return false
|
val fileName = questionnaireFiles[button] ?: return false
|
||||||
@ -651,16 +637,7 @@ class HandlerOpeningScreen(private val activity: MainActivity) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private fun lockCoachCodeField() {
|
private fun lockCoachCodeField() {
|
||||||
coachEditText.isFocusable = false
|
ViewUtils.lockEditField(coachEditText)
|
||||||
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) {
|
private fun applySessionAgeHighlight(ageMs: Long) {
|
||||||
@ -682,22 +659,22 @@ class HandlerOpeningScreen(private val activity: MainActivity) {
|
|||||||
|
|
||||||
|
|
||||||
|
|
||||||
private fun showRedToast(ctx: android.content.Context, message: String) {
|
private fun showRedToast(ctx: Context, message: String) {
|
||||||
val tv = android.widget.TextView(ctx).apply {
|
val tv = TextView(ctx).apply {
|
||||||
text = message
|
text = message
|
||||||
setTextColor(android.graphics.Color.WHITE)
|
setTextColor(Color.WHITE)
|
||||||
textSize = 16f
|
textSize = 16f
|
||||||
setPadding(32, 20, 32, 20)
|
setPadding(32, 20, 32, 20)
|
||||||
background = android.graphics.drawable.GradientDrawable().apply {
|
background = GradientDrawable().apply {
|
||||||
shape = android.graphics.drawable.GradientDrawable.RECTANGLE
|
shape = GradientDrawable.RECTANGLE
|
||||||
cornerRadius = 24f
|
cornerRadius = 24f
|
||||||
setColor(android.graphics.Color.parseColor("#D32F2F")) // kräftiges Rot
|
setColor(Color.parseColor("#D32F2F")) // kräftiges Rot
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
android.widget.Toast(ctx).apply {
|
Toast(ctx).apply {
|
||||||
duration = android.widget.Toast.LENGTH_LONG
|
duration = Toast.LENGTH_LONG
|
||||||
view = tv
|
view = tv
|
||||||
setGravity(android.view.Gravity.TOP or android.view.Gravity.CENTER_HORIZONTAL, 0, 120)
|
setGravity(Gravity.TOP or Gravity.CENTER_HORIZONTAL, 0, 120)
|
||||||
}.show()
|
}.show()
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1,10 +1,16 @@
|
|||||||
package com.dano.test1
|
package com.dano.test1.ui
|
||||||
|
|
||||||
|
import android.content.Intent
|
||||||
import android.widget.Button
|
import android.widget.Button
|
||||||
import android.widget.EditText
|
import android.widget.EditText
|
||||||
import android.widget.Toast
|
import android.widget.Toast
|
||||||
|
import com.dano.test1.LanguageManager
|
||||||
|
import com.dano.test1.MainActivity
|
||||||
|
import com.dano.test1.MyApp
|
||||||
import kotlinx.coroutines.*
|
import kotlinx.coroutines.*
|
||||||
import com.dano.test1.data.CompletedQuestionnaire
|
import com.dano.test1.data.CompletedQuestionnaire
|
||||||
|
import com.dano.test1.questionnaire.GlobalValues
|
||||||
|
import com.dano.test1.questionnaire.QuestionItem
|
||||||
|
|
||||||
class LoadButtonHandler(
|
class LoadButtonHandler(
|
||||||
private val activity: MainActivity,
|
private val activity: MainActivity,
|
||||||
@ -19,6 +25,11 @@ class LoadButtonHandler(
|
|||||||
private val updateMainButtonsState: (Boolean) -> Unit,
|
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() {
|
fun setup() {
|
||||||
loadButton.text = LanguageManager.getText(languageIDProvider(), "load")
|
loadButton.text = LanguageManager.getText(languageIDProvider(), "load")
|
||||||
loadButton.setOnClickListener { handleLoadButton() }
|
loadButton.setOnClickListener { handleLoadButton() }
|
||||||
@ -32,6 +43,12 @@ class LoadButtonHandler(
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (inputText == DEV_SETTINGS_SECRET) {
|
||||||
|
editText.text.clear()
|
||||||
|
activity.startActivity(Intent(activity, DevSettingsActivity::class.java))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
buttonPoints.clear()
|
buttonPoints.clear()
|
||||||
setButtonsEnabled(emptyList()) // temporär sperren
|
setButtonsEnabled(emptyList()) // temporär sperren
|
||||||
updateButtonTexts() // Chips zeigen vorläufig „Gesperrt“
|
updateButtonTexts() // Chips zeigen vorläufig „Gesperrt“
|
||||||
@ -143,10 +160,9 @@ class LoadButtonHandler(
|
|||||||
(completedNorm.contains(targetNorm) || targetNorm.contains(completedNorm)) && completed.isDone
|
(completedNorm.contains(targetNorm) || targetNorm.contains(completedNorm)) && completed.isDone
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (isCompleted) continue
|
|
||||||
|
|
||||||
val condMet = evaluateCondition(entry.condition, clientCode, completedEntries)
|
val condMet = evaluateCondition(entry.condition, clientCode, completedEntries)
|
||||||
if (condMet) enabledButtons.add(button)
|
if (condMet || isCompleted) enabledButtons.add(button)
|
||||||
}
|
}
|
||||||
|
|
||||||
withContext(Dispatchers.Main) {
|
withContext(Dispatchers.Main) {
|
||||||
@ -1,4 +1,6 @@
|
|||||||
package com.dano.test1
|
package com.dano.test1.ui
|
||||||
|
|
||||||
|
import com.dano.test1.LanguageManager
|
||||||
|
|
||||||
data class Month(val name: String) {
|
data class Month(val name: String) {
|
||||||
override fun toString(): String = name
|
override fun toString(): String = name
|
||||||
@ -1,12 +1,21 @@
|
|||||||
package com.dano.test1
|
package com.dano.test1.ui
|
||||||
|
|
||||||
|
import android.content.ActivityNotFoundException
|
||||||
|
import android.content.ContentUris
|
||||||
|
import android.content.ContentValues
|
||||||
|
import android.content.Intent
|
||||||
import android.graphics.Canvas
|
import android.graphics.Canvas
|
||||||
import android.graphics.Paint
|
import android.graphics.Paint
|
||||||
import android.graphics.pdf.PdfDocument
|
import android.graphics.pdf.PdfDocument
|
||||||
|
import android.provider.MediaStore
|
||||||
import android.util.Log
|
import android.util.Log
|
||||||
import android.widget.Button
|
import android.widget.Button
|
||||||
import android.widget.EditText
|
import android.widget.EditText
|
||||||
import android.widget.Toast
|
import android.widget.Toast
|
||||||
|
import com.dano.test1.LanguageManager
|
||||||
|
import com.dano.test1.MainActivity
|
||||||
|
import com.dano.test1.MyApp
|
||||||
|
import com.dano.test1.questionnaire.GlobalValues
|
||||||
import kotlinx.coroutines.*
|
import kotlinx.coroutines.*
|
||||||
|
|
||||||
class SaveButtonHandler(
|
class SaveButtonHandler(
|
||||||
@ -105,19 +114,19 @@ class SaveButtonHandler(
|
|||||||
val resolver = activity.contentResolver
|
val resolver = activity.contentResolver
|
||||||
|
|
||||||
val deleteIfExists: (String) -> Unit = { name ->
|
val deleteIfExists: (String) -> Unit = { name ->
|
||||||
val projection = arrayOf(android.provider.MediaStore.MediaColumns._ID)
|
val projection = arrayOf(MediaStore.MediaColumns._ID)
|
||||||
val selection = "${android.provider.MediaStore.MediaColumns.DISPLAY_NAME} = ?"
|
val selection = "${MediaStore.MediaColumns.DISPLAY_NAME} = ?"
|
||||||
val selectionArgs = arrayOf(name)
|
val selectionArgs = arrayOf(name)
|
||||||
val query = resolver.query(
|
val query = resolver.query(
|
||||||
android.provider.MediaStore.Downloads.EXTERNAL_CONTENT_URI,
|
MediaStore.Downloads.EXTERNAL_CONTENT_URI,
|
||||||
projection, selection, selectionArgs, null
|
projection, selection, selectionArgs, null
|
||||||
)
|
)
|
||||||
query?.use { cursor ->
|
query?.use { cursor ->
|
||||||
if (cursor.moveToFirst()) {
|
if (cursor.moveToFirst()) {
|
||||||
val idColumn = cursor.getColumnIndexOrThrow(android.provider.MediaStore.MediaColumns._ID)
|
val idColumn = cursor.getColumnIndexOrThrow(MediaStore.MediaColumns._ID)
|
||||||
val id = cursor.getLong(idColumn)
|
val id = cursor.getLong(idColumn)
|
||||||
val deleteUri = android.content.ContentUris.withAppendedId(
|
val deleteUri = ContentUris.withAppendedId(
|
||||||
android.provider.MediaStore.Downloads.EXTERNAL_CONTENT_URI, id
|
MediaStore.Downloads.EXTERNAL_CONTENT_URI, id
|
||||||
)
|
)
|
||||||
resolver.delete(deleteUri, null, null)
|
resolver.delete(deleteUri, null, null)
|
||||||
}
|
}
|
||||||
@ -129,20 +138,20 @@ class SaveButtonHandler(
|
|||||||
|
|
||||||
try {
|
try {
|
||||||
val pdfUri = resolver.insert(
|
val pdfUri = resolver.insert(
|
||||||
android.provider.MediaStore.Downloads.EXTERNAL_CONTENT_URI,
|
MediaStore.Downloads.EXTERNAL_CONTENT_URI,
|
||||||
android.content.ContentValues().apply {
|
ContentValues().apply {
|
||||||
put(android.provider.MediaStore.MediaColumns.DISPLAY_NAME, pdfFileName)
|
put(MediaStore.MediaColumns.DISPLAY_NAME, pdfFileName)
|
||||||
put(android.provider.MediaStore.MediaColumns.MIME_TYPE, "application/pdf")
|
put(MediaStore.MediaColumns.MIME_TYPE, "application/pdf")
|
||||||
put(android.provider.MediaStore.MediaColumns.RELATIVE_PATH, "Download/")
|
put(MediaStore.MediaColumns.RELATIVE_PATH, "Download/")
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
val csvUri = resolver.insert(
|
val csvUri = resolver.insert(
|
||||||
android.provider.MediaStore.Downloads.EXTERNAL_CONTENT_URI,
|
MediaStore.Downloads.EXTERNAL_CONTENT_URI,
|
||||||
android.content.ContentValues().apply {
|
ContentValues().apply {
|
||||||
put(android.provider.MediaStore.MediaColumns.DISPLAY_NAME, csvFileName)
|
put(MediaStore.MediaColumns.DISPLAY_NAME, csvFileName)
|
||||||
put(android.provider.MediaStore.MediaColumns.MIME_TYPE, "text/csv")
|
put(MediaStore.MediaColumns.MIME_TYPE, "text/csv")
|
||||||
put(android.provider.MediaStore.MediaColumns.RELATIVE_PATH, "Download/")
|
put(MediaStore.MediaColumns.RELATIVE_PATH, "Download/")
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -162,13 +171,13 @@ class SaveButtonHandler(
|
|||||||
Toast.makeText(activity, msg, Toast.LENGTH_LONG).show()
|
Toast.makeText(activity, msg, Toast.LENGTH_LONG).show()
|
||||||
|
|
||||||
pdfUri?.let {
|
pdfUri?.let {
|
||||||
val intent = android.content.Intent(android.content.Intent.ACTION_VIEW).apply {
|
val intent = Intent(Intent.ACTION_VIEW).apply {
|
||||||
setDataAndType(it, "application/pdf")
|
setDataAndType(it, "application/pdf")
|
||||||
addFlags(android.content.Intent.FLAG_GRANT_READ_URI_PERMISSION or android.content.Intent.FLAG_ACTIVITY_NO_HISTORY)
|
addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION or Intent.FLAG_ACTIVITY_NO_HISTORY)
|
||||||
}
|
}
|
||||||
try {
|
try {
|
||||||
activity.startActivity(intent)
|
activity.startActivity(intent)
|
||||||
} catch (e: android.content.ActivityNotFoundException) {
|
} catch (e: ActivityNotFoundException) {
|
||||||
val noViewer = LanguageManager.getText(languageIDProvider(), "no_pdf_viewer")
|
val noViewer = LanguageManager.getText(languageIDProvider(), "no_pdf_viewer")
|
||||||
Toast.makeText(activity, noViewer, Toast.LENGTH_SHORT).show()
|
Toast.makeText(activity, noViewer, Toast.LENGTH_SHORT).show()
|
||||||
}
|
}
|
||||||
@ -1,6 +1,8 @@
|
|||||||
package com.dano.test1
|
package com.dano.test1
|
||||||
|
|
||||||
import java.util.Calendar
|
import com.dano.test1.questionnaire.MAX_VALUE_AGE
|
||||||
|
import com.dano.test1.questionnaire.MAX_VALUE_YEAR
|
||||||
|
import com.dano.test1.ui.RHS_POINTS
|
||||||
|
|
||||||
object LanguageManager {
|
object LanguageManager {
|
||||||
fun getTextFormatted(languageId: String, key: String, vararg args: Any): String {
|
fun getTextFormatted(languageId: String, key: String, vararg args: Any): String {
|
||||||
@ -61,14 +63,14 @@ object LanguageManager {
|
|||||||
"once" to "einmal",
|
"once" to "einmal",
|
||||||
"year_after_2000" to "Das Jahr muss nach 2000 liegen!",
|
"year_after_2000" to "Das Jahr muss nach 2000 liegen!",
|
||||||
"year_after_departure" to "Das Jahr muss nach dem Verlassen des Herkunftslandes liegen!",
|
"year_after_departure" to "Das Jahr muss nach dem Verlassen des Herkunftslandes liegen!",
|
||||||
"year_max" to "Das Jahr muss kleiner oder gleich $MAX_VALUE_YEAR sein!",
|
"year_max" to "Das Jahr muss kleiner oder gleich ${MAX_VALUE_YEAR} sein!",
|
||||||
"data_final_warning" to "<b><font color='#FF0000'>Wichtig:</font></b> Die Daten können nach dem Abschluss nicht mehr verändert oder bearbeitet werden!",
|
"data_final_warning" to "<b><font color='#FF0000'>Wichtig:</font></b> Die Daten können nach dem Abschluss nicht mehr verändert oder bearbeitet werden!",
|
||||||
"multiple_times" to "mehrmals",
|
"multiple_times" to "mehrmals",
|
||||||
"more_than_15_years" to "mehr als 15 Jahre",
|
"more_than_15_years" to "mehr als 15 Jahre",
|
||||||
"no" to "Nein",
|
"no" to "Nein",
|
||||||
"no_answer" to "keine Angabe",
|
"no_answer" to "keine Angabe",
|
||||||
"other_country" to "anderes Land",
|
"other_country" to "anderes Land",
|
||||||
"value_must_be_less_equal_max" to "Der Wert muss kleiner oder gleich $MAX_VALUE_AGE sein!",
|
"value_must_be_less_equal_max" to "Der Wert muss kleiner oder gleich ${MAX_VALUE_AGE} sein!",
|
||||||
"value_between_1_and_15" to "Der Wert muss zwischen 1 und 15 liegen!",
|
"value_between_1_and_15" to "Der Wert muss zwischen 1 und 15 liegen!",
|
||||||
"invalid_month" to "Ungültige Monatsangabe!",
|
"invalid_month" to "Ungültige Monatsangabe!",
|
||||||
"invalid_year" to "Ungültige Jahresangabe!",
|
"invalid_year" to "Ungültige Jahresangabe!",
|
||||||
@ -438,14 +440,14 @@ object LanguageManager {
|
|||||||
"once" to "once",
|
"once" to "once",
|
||||||
"year_after_2000" to "The year must be after 2000!",
|
"year_after_2000" to "The year must be after 2000!",
|
||||||
"year_after_departure" to "The year must be after leaving the country of origin!",
|
"year_after_departure" to "The year must be after leaving the country of origin!",
|
||||||
"year_max" to "The year must be less than or equal to $MAX_VALUE_YEAR!",
|
"year_max" to "The year must be less than or equal to ${MAX_VALUE_YEAR}!",
|
||||||
"data_final_warning" to "<b><font color='#FF0000'>Important:</font></b> The data cannot be changed or edited after completion!",
|
"data_final_warning" to "<b><font color='#FF0000'>Important:</font></b> The data cannot be changed or edited after completion!",
|
||||||
"multiple_times" to "multiple times",
|
"multiple_times" to "multiple times",
|
||||||
"more_than_15_years" to "more than 15 years",
|
"more_than_15_years" to "more than 15 years",
|
||||||
"no" to "No",
|
"no" to "No",
|
||||||
"no_answer" to "No answer",
|
"no_answer" to "No answer",
|
||||||
"other_country" to "Other country",
|
"other_country" to "Other country",
|
||||||
"value_must_be_less_equal_max" to "The value must be less than or equal to $MAX_VALUE_AGE!",
|
"value_must_be_less_equal_max" to "The value must be less than or equal to ${MAX_VALUE_AGE}!",
|
||||||
"value_between_1_and_15" to "The value must be between 1 and 15!",
|
"value_between_1_and_15" to "The value must be between 1 and 15!",
|
||||||
"invalid_month" to "Invalid month!",
|
"invalid_month" to "Invalid month!",
|
||||||
"invalid_year" to "Invalid year!",
|
"invalid_year" to "Invalid year!",
|
||||||
@ -814,14 +816,14 @@ object LanguageManager {
|
|||||||
"once" to "une fois",
|
"once" to "une fois",
|
||||||
"year_after_2000" to "L’année doit être après 2000 !",
|
"year_after_2000" to "L’année doit être après 2000 !",
|
||||||
"year_after_departure" to "L’année doit être après le départ du pays d’origine !",
|
"year_after_departure" to "L’année doit être après le départ du pays d’origine !",
|
||||||
"year_max" to "L’année doit être inférieure ou égale à $MAX_VALUE_YEAR !",
|
"year_max" to "L’année doit être inférieure ou égale à ${MAX_VALUE_YEAR} !",
|
||||||
"data_final_warning" to "<b><font color='#FF0000'>Important :</font></b> Les données ne peuvent plus être modifiées ou éditées après la validation !",
|
"data_final_warning" to "<b><font color='#FF0000'>Important :</font></b> Les données ne peuvent plus être modifiées ou éditées après la validation !",
|
||||||
"multiple_times" to "plusieurs fois",
|
"multiple_times" to "plusieurs fois",
|
||||||
"more_than_15_years" to "plus de 15 ans",
|
"more_than_15_years" to "plus de 15 ans",
|
||||||
"no" to "Non",
|
"no" to "Non",
|
||||||
"no_answer" to "pas de réponse",
|
"no_answer" to "pas de réponse",
|
||||||
"other_country" to "autre pays",
|
"other_country" to "autre pays",
|
||||||
"value_must_be_less_equal_max" to "La valeur doit être inférieure ou égale à $MAX_VALUE_AGE !",
|
"value_must_be_less_equal_max" to "La valeur doit être inférieure ou égale à ${MAX_VALUE_AGE} !",
|
||||||
"value_between_1_and_15" to "La valeur doit être comprise entre 1 et 15 !",
|
"value_between_1_and_15" to "La valeur doit être comprise entre 1 et 15 !",
|
||||||
"invalid_month" to "Mois invalide !",
|
"invalid_month" to "Mois invalide !",
|
||||||
"invalid_year" to "Année invalide !",
|
"invalid_year" to "Année invalide !",
|
||||||
@ -1194,14 +1196,14 @@ object LanguageManager {
|
|||||||
"once" to "один раз",
|
"once" to "один раз",
|
||||||
"year_after_2000" to "Год должен быть после 2000!",
|
"year_after_2000" to "Год должен быть после 2000!",
|
||||||
"year_after_departure" to "Год должен быть после даты выезда из страны происхождения!",
|
"year_after_departure" to "Год должен быть после даты выезда из страны происхождения!",
|
||||||
"year_max" to "Год должен быть меньше или равен $MAX_VALUE_YEAR!",
|
"year_max" to "Год должен быть меньше или равен ${MAX_VALUE_YEAR}!",
|
||||||
"data_final_warning" to "<b><font color='#FF0000'>Внимание:</font></b> Данные нельзя изменять после завершения!",
|
"data_final_warning" to "<b><font color='#FF0000'>Внимание:</font></b> Данные нельзя изменять после завершения!",
|
||||||
"multiple_times" to "несколько раз",
|
"multiple_times" to "несколько раз",
|
||||||
"more_than_15_years" to "более 15 лет",
|
"more_than_15_years" to "более 15 лет",
|
||||||
"no" to "Нет",
|
"no" to "Нет",
|
||||||
"no_answer" to "без ответа",
|
"no_answer" to "без ответа",
|
||||||
"other_country" to "другая страна",
|
"other_country" to "другая страна",
|
||||||
"value_must_be_less_equal_max" to "Значение должно быть меньше или равно $MAX_VALUE_AGE!",
|
"value_must_be_less_equal_max" to "Значение должно быть меньше или равно ${MAX_VALUE_AGE}!",
|
||||||
"value_between_1_and_15" to "Значение должно быть между 1 и 15!",
|
"value_between_1_and_15" to "Значение должно быть между 1 и 15!",
|
||||||
"invalid_month" to "Недопустимый месяц!",
|
"invalid_month" to "Недопустимый месяц!",
|
||||||
"invalid_year" to "Недопустимый год!",
|
"invalid_year" to "Недопустимый год!",
|
||||||
@ -1570,14 +1572,14 @@ object LanguageManager {
|
|||||||
"once" to "один раз",
|
"once" to "один раз",
|
||||||
"year_after_2000" to "Рік має бути після 2000!",
|
"year_after_2000" to "Рік має бути після 2000!",
|
||||||
"year_after_departure" to "Рік має бути після виїзду з країни походження!",
|
"year_after_departure" to "Рік має бути після виїзду з країни походження!",
|
||||||
"year_max" to "Рік має бути меншим або рівним $MAX_VALUE_YEAR!",
|
"year_max" to "Рік має бути меншим або рівним ${MAX_VALUE_YEAR}!",
|
||||||
"data_final_warning" to "<b><font color='#FF0000'>Важливо:</font></b> Дані після завершення не можна змінити або редагувати!",
|
"data_final_warning" to "<b><font color='#FF0000'>Важливо:</font></b> Дані після завершення не можна змінити або редагувати!",
|
||||||
"multiple_times" to "багато разів",
|
"multiple_times" to "багато разів",
|
||||||
"more_than_15_years" to "більше 15 років",
|
"more_than_15_years" to "більше 15 років",
|
||||||
"no" to "Ні",
|
"no" to "Ні",
|
||||||
"no_answer" to "немає відповіді",
|
"no_answer" to "немає відповіді",
|
||||||
"other_country" to "інша країна",
|
"other_country" to "інша країна",
|
||||||
"value_must_be_less_equal_max" to "Значення має бути меншим або рівним $MAX_VALUE_AGE!",
|
"value_must_be_less_equal_max" to "Значення має бути меншим або рівним ${MAX_VALUE_AGE}!",
|
||||||
"value_between_1_and_15" to "Значення має бути від 1 до 15!",
|
"value_between_1_and_15" to "Значення має бути від 1 до 15!",
|
||||||
"invalid_month" to "Неправильний місяць!",
|
"invalid_month" to "Неправильний місяць!",
|
||||||
"invalid_year" to "Неправильний рік!",
|
"invalid_year" to "Неправильний рік!",
|
||||||
@ -1950,14 +1952,14 @@ object LanguageManager {
|
|||||||
"once" to "bir kez",
|
"once" to "bir kez",
|
||||||
"year_after_2000" to "Yıl 2000’den sonra olmalıdır!",
|
"year_after_2000" to "Yıl 2000’den sonra olmalıdır!",
|
||||||
"year_after_departure" to "Yıl, menşe ülkeyi terk ettikten sonra olmalıdır!",
|
"year_after_departure" to "Yıl, menşe ülkeyi terk ettikten sonra olmalıdır!",
|
||||||
"year_max" to "Yıl $MAX_VALUE_YEAR’den küçük veya ona eşit olmalıdır!",
|
"year_max" to "Yıl ${MAX_VALUE_YEAR}’den küçük veya ona eşit olmalıdır!",
|
||||||
"data_final_warning" to "<b><font color='#FF0000'>Önemli:</font></b> Veriler tamamlandıktan sonra değiştirilemez veya düzenlenemez!",
|
"data_final_warning" to "<b><font color='#FF0000'>Önemli:</font></b> Veriler tamamlandıktan sonra değiştirilemez veya düzenlenemez!",
|
||||||
"multiple_times" to "birden fazla kez",
|
"multiple_times" to "birden fazla kez",
|
||||||
"more_than_15_years" to "15 yıldan fazla",
|
"more_than_15_years" to "15 yıldan fazla",
|
||||||
"no" to "Hayır",
|
"no" to "Hayır",
|
||||||
"no_answer" to "Cevap yok",
|
"no_answer" to "Cevap yok",
|
||||||
"other_country" to "diğer ülke",
|
"other_country" to "diğer ülke",
|
||||||
"value_must_be_less_equal_max" to "Değer $MAX_VALUE_AGE’den küçük veya ona eşit olmalıdır!",
|
"value_must_be_less_equal_max" to "Değer ${MAX_VALUE_AGE}’den küçük veya ona eşit olmalıdır!",
|
||||||
"value_between_1_and_15" to "Değer 1 ile 15 arasında olmalıdır!",
|
"value_between_1_and_15" to "Değer 1 ile 15 arasında olmalıdır!",
|
||||||
"invalid_month" to "Geçersiz ay girişi!",
|
"invalid_month" to "Geçersiz ay girişi!",
|
||||||
"invalid_year" to "Geçersiz yıl girişi!",
|
"invalid_year" to "Geçersiz yıl girişi!",
|
||||||
@ -2330,14 +2332,14 @@ object LanguageManager {
|
|||||||
"once" to "jeden raz",
|
"once" to "jeden raz",
|
||||||
"year_after_2000" to "Rok musi być po 2000!",
|
"year_after_2000" to "Rok musi być po 2000!",
|
||||||
"year_after_departure" to "Rok musi być po opuszczeniu kraju pochodzenia!",
|
"year_after_departure" to "Rok musi być po opuszczeniu kraju pochodzenia!",
|
||||||
"year_max" to "Rok musi być mniejszy lub równy $MAX_VALUE_YEAR!",
|
"year_max" to "Rok musi być mniejszy lub równy ${MAX_VALUE_YEAR}!",
|
||||||
"data_final_warning" to "<b><font color='#FF0000'>Ważne:</font></b> Po zakończeniu dane nie mogą być zmienione ani edytowane!",
|
"data_final_warning" to "<b><font color='#FF0000'>Ważne:</font></b> Po zakończeniu dane nie mogą być zmienione ani edytowane!",
|
||||||
"multiple_times" to "kilka razy",
|
"multiple_times" to "kilka razy",
|
||||||
"more_than_15_years" to "więcej niż 15 lat",
|
"more_than_15_years" to "więcej niż 15 lat",
|
||||||
"no" to "Nie",
|
"no" to "Nie",
|
||||||
"no_answer" to "brak odpowiedzi",
|
"no_answer" to "brak odpowiedzi",
|
||||||
"other_country" to "inny kraj",
|
"other_country" to "inny kraj",
|
||||||
"value_must_be_less_equal_max" to "Wartość musi być mniejsza lub równa $MAX_VALUE_AGE!",
|
"value_must_be_less_equal_max" to "Wartość musi być mniejsza lub równa ${MAX_VALUE_AGE}!",
|
||||||
"value_between_1_and_15" to "Wartość musi być między 1 a 15!",
|
"value_between_1_and_15" to "Wartość musi być między 1 a 15!",
|
||||||
"invalid_month" to "Nieprawidłowy miesiąc!",
|
"invalid_month" to "Nieprawidłowy miesiąc!",
|
||||||
"invalid_year" to "Nieprawidłowy rok!",
|
"invalid_year" to "Nieprawidłowy rok!",
|
||||||
@ -2710,14 +2712,14 @@ object LanguageManager {
|
|||||||
"once" to "مرة واحدة",
|
"once" to "مرة واحدة",
|
||||||
"year_after_2000" to "يجب أن تكون السنة بعد 2000!",
|
"year_after_2000" to "يجب أن تكون السنة بعد 2000!",
|
||||||
"year_after_departure" to "يجب أن تكون السنة بعد مغادرة بلد المنشأ!",
|
"year_after_departure" to "يجب أن تكون السنة بعد مغادرة بلد المنشأ!",
|
||||||
"year_max" to "يجب أن تكون السنة أقل من أو تساوي $MAX_VALUE_YEAR!",
|
"year_max" to "يجب أن تكون السنة أقل من أو تساوي ${MAX_VALUE_YEAR}!",
|
||||||
"data_final_warning" to "<b><font color='#FF0000'>هام:</font></b> لا يمكن تعديل البيانات بعد الانتهاء!",
|
"data_final_warning" to "<b><font color='#FF0000'>هام:</font></b> لا يمكن تعديل البيانات بعد الانتهاء!",
|
||||||
"multiple_times" to "عدة مرات",
|
"multiple_times" to "عدة مرات",
|
||||||
"more_than_15_years" to "أكثر من 15 سنة",
|
"more_than_15_years" to "أكثر من 15 سنة",
|
||||||
"no" to "لا",
|
"no" to "لا",
|
||||||
"no_answer" to "لا يوجد إجابة",
|
"no_answer" to "لا يوجد إجابة",
|
||||||
"other_country" to "بلد آخر",
|
"other_country" to "بلد آخر",
|
||||||
"value_must_be_less_equal_max" to "يجب أن تكون القيمة أقل من أو تساوي $MAX_VALUE_AGE!",
|
"value_must_be_less_equal_max" to "يجب أن تكون القيمة أقل من أو تساوي ${MAX_VALUE_AGE}!",
|
||||||
"value_between_1_and_15" to "يجب أن تكون القيمة بين 1 و15!",
|
"value_between_1_and_15" to "يجب أن تكون القيمة بين 1 و15!",
|
||||||
"invalid_month" to "شهر غير صالح!",
|
"invalid_month" to "شهر غير صالح!",
|
||||||
"invalid_year" to "سنة غير صالحة!",
|
"invalid_year" to "سنة غير صالحة!",
|
||||||
@ -3090,14 +3092,14 @@ object LanguageManager {
|
|||||||
"once" to "o dată",
|
"once" to "o dată",
|
||||||
"year_after_2000" to "Anul trebuie să fie după 2000!",
|
"year_after_2000" to "Anul trebuie să fie după 2000!",
|
||||||
"year_after_departure" to "Anul trebuie să fie după plecarea din țara de origine!",
|
"year_after_departure" to "Anul trebuie să fie după plecarea din țara de origine!",
|
||||||
"year_max" to "Anul trebuie să fie mai mic sau egal cu $MAX_VALUE_YEAR!",
|
"year_max" to "Anul trebuie să fie mai mic sau egal cu ${MAX_VALUE_YEAR}!",
|
||||||
"data_final_warning" to "<b><font color='#FF0000'>Important:</font></b> Datele nu mai pot fi modificate după finalizare!",
|
"data_final_warning" to "<b><font color='#FF0000'>Important:</font></b> Datele nu mai pot fi modificate după finalizare!",
|
||||||
"multiple_times" to "de mai multe ori",
|
"multiple_times" to "de mai multe ori",
|
||||||
"more_than_15_years" to "mai mult de 15 ani",
|
"more_than_15_years" to "mai mult de 15 ani",
|
||||||
"no" to "Nu",
|
"no" to "Nu",
|
||||||
"no_answer" to "fără răspuns",
|
"no_answer" to "fără răspuns",
|
||||||
"other_country" to "altă țară",
|
"other_country" to "altă țară",
|
||||||
"value_must_be_less_equal_max" to "Valoarea trebuie să fie mai mică sau egală cu $MAX_VALUE_AGE!",
|
"value_must_be_less_equal_max" to "Valoarea trebuie să fie mai mică sau egală cu ${MAX_VALUE_AGE}!",
|
||||||
"value_between_1_and_15" to "Valoarea trebuie să fie între 1 și 15!",
|
"value_between_1_and_15" to "Valoarea trebuie să fie între 1 și 15!",
|
||||||
"invalid_month" to "Lună invalidă!",
|
"invalid_month" to "Lună invalidă!",
|
||||||
"invalid_year" to "An invalid!",
|
"invalid_year" to "An invalid!",
|
||||||
@ -3470,14 +3472,14 @@ object LanguageManager {
|
|||||||
"once" to "una vez",
|
"once" to "una vez",
|
||||||
"year_after_2000" to "¡El año debe ser posterior al 2000!",
|
"year_after_2000" to "¡El año debe ser posterior al 2000!",
|
||||||
"year_after_departure" to "¡El año debe ser posterior a la salida de su país de origen!",
|
"year_after_departure" to "¡El año debe ser posterior a la salida de su país de origen!",
|
||||||
"year_max" to "¡El año debe ser menor o igual que $MAX_VALUE_YEAR!",
|
"year_max" to "¡El año debe ser menor o igual que ${MAX_VALUE_YEAR}!",
|
||||||
"data_final_warning" to "<b><font color='#FF0000'>Importante:</font></b> Después de finalizar, los datos no podrán cambiarse o editarse.",
|
"data_final_warning" to "<b><font color='#FF0000'>Importante:</font></b> Después de finalizar, los datos no podrán cambiarse o editarse.",
|
||||||
"multiple_times" to "varias veces",
|
"multiple_times" to "varias veces",
|
||||||
"more_than_15_years" to "más de 15 años",
|
"more_than_15_years" to "más de 15 años",
|
||||||
"no" to "No",
|
"no" to "No",
|
||||||
"no_answer" to "sin respuesta",
|
"no_answer" to "sin respuesta",
|
||||||
"other_country" to "otro país",
|
"other_country" to "otro país",
|
||||||
"value_must_be_less_equal_max" to "¡El valor debe ser menor o igual que $MAX_VALUE_AGE!",
|
"value_must_be_less_equal_max" to "¡El valor debe ser menor o igual que ${MAX_VALUE_AGE}!",
|
||||||
"value_between_1_and_15" to "¡El valor debe estar entre 1 y 15!",
|
"value_between_1_and_15" to "¡El valor debe estar entre 1 y 15!",
|
||||||
"invalid_month" to "¡Mes no válido!",
|
"invalid_month" to "¡Mes no válido!",
|
||||||
"invalid_year" to "¡Año no válido!",
|
"invalid_year" to "¡Año no válido!",
|
||||||
112
app/src/main/java/com/dano/test1/utils/ViewUtils.kt
Normal file
112
app/src/main/java/com/dano/test1/utils/ViewUtils.kt
Normal file
@ -0,0 +1,112 @@
|
|||||||
|
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()
|
||||||
|
}
|
||||||
96
app/src/main/res/layout/activity_dev_settings.xml
Normal file
96
app/src/main/res/layout/activity_dev_settings.xml
Normal file
@ -0,0 +1,96 @@
|
|||||||
|
<?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,21 +195,6 @@
|
|||||||
app:cornerRadius="@dimen/pill_radius"
|
app:cornerRadius="@dimen/pill_radius"
|
||||||
app:backgroundTint="@color/brand_purple"/>
|
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
|
<com.google.android.material.button.MaterialButton
|
||||||
android:id="@+id/saveButton"
|
android:id="@+id/saveButton"
|
||||||
android:layout_width="0dp"
|
android:layout_width="0dp"
|
||||||
@ -253,19 +238,6 @@
|
|||||||
style="@style/Widget.MaterialComponents.Button.OutlinedButton"
|
style="@style/Widget.MaterialComponents.Button.OutlinedButton"
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="@dimen/pill_height"
|
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:textAllCaps="false"
|
||||||
android:textColor="@color/brand_purple"
|
android:textColor="@color/brand_purple"
|
||||||
app:cornerRadius="@dimen/pill_radius"
|
app:cornerRadius="@dimen/pill_radius"
|
||||||
|
|||||||
95
app/src/main/res/layout/questionnaire_all_in_one.xml
Normal file
95
app/src/main/res/layout/questionnaire_all_in_one.xml
Normal file
@ -0,0 +1,95 @@
|
|||||||
|
<?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