created shared functions in ViewUtils.kt

This commit is contained in:
2026-03-02 13:20:51 +01:00
parent cc89c77186
commit b95977e28d
11 changed files with 157 additions and 332 deletions

View File

@ -2,8 +2,6 @@ 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 androidx.core.widget.TextViewCompat
import com.dano.test1.questionnaire.GlobalValues import com.dano.test1.questionnaire.GlobalValues
import com.dano.test1.LanguageManager import com.dano.test1.LanguageManager
import com.dano.test1.MyApp import com.dano.test1.MyApp
@ -11,6 +9,7 @@ import com.dano.test1.questionnaire.QuestionHandler
import com.dano.test1.questionnaire.QuestionItem import com.dano.test1.questionnaire.QuestionItem
import com.dano.test1.R import com.dano.test1.R
import com.dano.test1.network.TokenStore 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
@ -45,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
@ -64,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 ?: "")
@ -79,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
@ -150,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()
} }

View File

@ -2,15 +2,10 @@ 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 android.view.Gravity
import androidx.core.widget.TextViewCompat
import android.widget.AbsListView
import com.dano.test1.questionnaire.GlobalValues import com.dano.test1.questionnaire.GlobalValues
import com.dano.test1.LanguageManager import com.dano.test1.LanguageManager
import com.dano.test1.questionnaire.MAX_VALUE_YEAR import com.dano.test1.questionnaire.MAX_VALUE_YEAR
@ -20,6 +15,7 @@ import com.dano.test1.MyApp
import com.dano.test1.questionnaire.QuestionHandler import com.dano.test1.questionnaire.QuestionHandler
import com.dano.test1.questionnaire.QuestionItem import com.dano.test1.questionnaire.QuestionItem
import com.dano.test1.R import com.dano.test1.R
import com.dano.test1.utils.ViewUtils
/* /*
Zweck: Zweck:
@ -62,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 {
@ -85,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 ?: "")
@ -217,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 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()
defaultSelection?.let {
val index = items.indexOf(it)
if (index >= 0) spinner.setSelection(index)
}
}
} }

View File

@ -1,17 +1,16 @@
package com.dano.test1.questionnaire.handlers 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.questionnaire.GlobalValues
import com.dano.test1.LanguageManager import com.dano.test1.LanguageManager
import com.dano.test1.MyApp import com.dano.test1.MyApp
import com.dano.test1.questionnaire.QuestionHandler import com.dano.test1.questionnaire.QuestionHandler
import com.dano.test1.questionnaire.QuestionItem import com.dano.test1.questionnaire.QuestionItem
import com.dano.test1.R import com.dano.test1.R
import com.dano.test1.utils.ViewUtils
import kotlinx.coroutines.* import kotlinx.coroutines.*
/* /*
@ -73,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)
@ -156,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)
@ -283,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)
}
} }

View File

@ -1,18 +1,18 @@
package com.dano.test1.questionnaire.handlers 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.questionnaire.GlobalValues
import com.dano.test1.LanguageManager import com.dano.test1.LanguageManager
import com.dano.test1.MainActivity import com.dano.test1.MainActivity
import com.dano.test1.questionnaire.QuestionHandler import com.dano.test1.questionnaire.QuestionHandler
import com.dano.test1.questionnaire.QuestionItem import com.dano.test1.questionnaire.QuestionItem
import com.dano.test1.R 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
/* /*
@ -64,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() }
@ -134,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 }

View File

@ -1,17 +1,18 @@
package com.dano.test1.questionnaire.handlers 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.questionnaire.GlobalValues
import com.dano.test1.LanguageManager import com.dano.test1.LanguageManager
import com.dano.test1.MyApp import com.dano.test1.MyApp
import com.dano.test1.questionnaire.QuestionHandler import com.dano.test1.questionnaire.QuestionHandler
import com.dano.test1.questionnaire.QuestionItem import com.dano.test1.questionnaire.QuestionItem
import com.dano.test1.R import com.dano.test1.R
import com.dano.test1.utils.ViewUtils
/* /*
Zweck: Zweck:
@ -44,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()
@ -210,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)
}
} }

View File

@ -5,14 +5,13 @@ 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 androidx.core.widget.TextViewCompat // <— hinzugefügt
import com.dano.test1.questionnaire.GlobalValues import com.dano.test1.questionnaire.GlobalValues
import com.dano.test1.LanguageManager import com.dano.test1.LanguageManager
import com.dano.test1.MyApp import com.dano.test1.MyApp
import com.dano.test1.questionnaire.QuestionHandler import com.dano.test1.questionnaire.QuestionHandler
import com.dano.test1.questionnaire.QuestionItem import com.dano.test1.questionnaire.QuestionItem
import com.dano.test1.R import com.dano.test1.R
import com.dano.test1.utils.ViewUtils
/* /*
Zweck: Zweck:
@ -47,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()
@ -61,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,
@ -143,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

View File

@ -2,13 +2,8 @@ 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 android.view.Gravity
import android.widget.TextView
import androidx.core.widget.TextViewCompat
import com.dano.test1.ui.Countries import com.dano.test1.ui.Countries
import com.dano.test1.questionnaire.GlobalValues import com.dano.test1.questionnaire.GlobalValues
import com.dano.test1.LanguageManager import com.dano.test1.LanguageManager
@ -16,6 +11,7 @@ import com.dano.test1.MyApp
import com.dano.test1.questionnaire.QuestionHandler import com.dano.test1.questionnaire.QuestionHandler
import com.dano.test1.questionnaire.QuestionItem import com.dano.test1.questionnaire.QuestionItem
import com.dano.test1.R import com.dano.test1.R
import com.dano.test1.utils.ViewUtils
/* /*
Zweck: Zweck:
@ -50,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()
@ -60,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 ?: "")
@ -127,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 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) // 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)
}
}
} }

View File

@ -2,18 +2,15 @@ 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 android.view.Gravity
import androidx.core.widget.TextViewCompat // <- NEU
import com.dano.test1.questionnaire.GlobalValues import com.dano.test1.questionnaire.GlobalValues
import com.dano.test1.LanguageManager import com.dano.test1.LanguageManager
import com.dano.test1.MyApp import com.dano.test1.MyApp
import com.dano.test1.questionnaire.QuestionHandler import com.dano.test1.questionnaire.QuestionHandler
import com.dano.test1.questionnaire.QuestionItem import com.dano.test1.questionnaire.QuestionItem
import com.dano.test1.R import com.dano.test1.R
import com.dano.test1.utils.ViewUtils
/* /*
Zweck: Zweck:
@ -50,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) {
@ -63,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 ?: "")
@ -134,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 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) // 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)
}
}
} }

View File

@ -6,6 +6,7 @@ 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.data.ExcelExportService
import com.dano.test1.utils.ViewUtils
import com.dano.test1.LanguageManager import com.dano.test1.LanguageManager
import com.dano.test1.MainActivity import com.dano.test1.MainActivity
import com.dano.test1.MyApp import com.dano.test1.MyApp
@ -15,7 +16,6 @@ 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(
@ -420,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)

View File

@ -27,6 +27,7 @@ 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
@ -572,7 +573,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
@ -664,16 +665,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) {

View File

@ -0,0 +1,110 @@
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 sp = (dm.heightPixels * 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
fun spFromScreenHeight(percent: Float): Float = (dm.heightPixels * percent) / dm.scaledDensity
fun pxFromSp(sp: Float): Int = (sp * dm.scaledDensity).toInt()
val textSp = spFromScreenHeight(0.0275f)
val textPx = pxFromSp(textSp)
val vPadPx = (textPx * 0.50f).toInt()
val rowHeight = (textPx * 2.20f + 2 * vPadPx).toInt()
val adapter = object : ArrayAdapter<T>(context, android.R.layout.simple_spinner_item, items) {
private fun styleRow(tv: TextView, forceHeight: Boolean) {
tv.setTextSize(TypedValue.COMPLEX_UNIT_SP, textSp)
tv.includeFontPadding = true
tv.setLineSpacing(0f, 1.2f)
tv.gravity = (tv.gravity and Gravity.HORIZONTAL_GRAVITY_MASK) or Gravity.CENTER_VERTICAL
tv.setPadding(tv.paddingLeft, vPadPx, tv.paddingRight, vPadPx)
tv.minHeight = rowHeight
tv.isSingleLine = true
if (forceHeight) {
val lp = tv.layoutParams
if (lp == null || lp.height <= 0) {
tv.layoutParams = AbsListView.LayoutParams(AbsListView.LayoutParams.MATCH_PARENT, rowHeight)
} else {
lp.height = rowHeight
}
}
}
override fun getView(position: Int, convertView: View?, parent: ViewGroup): View {
val v = super.getView(position, convertView, parent) as TextView
styleRow(v, forceHeight = false)
return v
}
override fun getDropDownView(position: Int, convertView: View?, parent: ViewGroup): View {
val v = super.getDropDownView(position, convertView, parent) as TextView
styleRow(v, forceHeight = true)
return v
}
}
adapter.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item)
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()
}