Compare commits

..

2 Commits

35 changed files with 345 additions and 428 deletions

View File

@ -13,7 +13,12 @@ import android.widget.ProgressBar
import android.widget.Toast
import androidx.appcompat.app.AlertDialog
import androidx.appcompat.app.AppCompatActivity
import java.io.File
import com.dano.test1.auth.LoginManager
import com.dano.test1.auth.TokenStore
import com.dano.test1.network.DatabaseDownloader
import com.dano.test1.questionnaire.QuestionnaireBase
import com.dano.test1.ui.HandlerOpeningScreen
import com.dano.test1.util.LanguageManager
class MainActivity : AppCompatActivity() {

View File

@ -1,9 +1,9 @@
package com.dano.test1
package com.dano.test1.auth
import android.app.AlertDialog
import android.content.Context
import android.text.InputType
import android.util.Log
import android.view.LayoutInflater
import android.widget.EditText
import android.widget.LinearLayout
import android.widget.Toast
@ -96,13 +96,13 @@ object LoginManager {
}
val etNew = EditText(context).apply {
hint = "Neues Passwort"
inputType = android.text.InputType.TYPE_CLASS_TEXT or
android.text.InputType.TYPE_TEXT_VARIATION_PASSWORD
inputType = InputType.TYPE_CLASS_TEXT or
InputType.TYPE_TEXT_VARIATION_PASSWORD
}
val etRepeat = EditText(context).apply {
hint = "Neues Passwort (wiederholen)"
inputType = android.text.InputType.TYPE_CLASS_TEXT or
android.text.InputType.TYPE_TEXT_VARIATION_PASSWORD
inputType = InputType.TYPE_CLASS_TEXT or
InputType.TYPE_TEXT_VARIATION_PASSWORD
}
container.addView(etNew)
container.addView(etRepeat)

View File

@ -1,4 +1,4 @@
package com.dano.test1
package com.dano.test1.auth
import android.content.Context

View File

@ -1,4 +1,4 @@
package com.dano.test1
package com.dano.test1.export
import android.content.ContentValues
import android.content.Context
@ -7,8 +7,12 @@ import android.net.Uri
import android.os.Build
import android.os.Environment
import android.provider.MediaStore
import com.dano.test1.util.LanguageManager
import com.dano.test1.MyApp
import org.apache.poi.ss.usermodel.Row
import org.apache.poi.xssf.usermodel.XSSFWorkbook
import java.io.ByteArrayOutputStream
import java.io.File
/*
Aufgabe:
@ -37,8 +41,8 @@ class ExcelExportService(
val orderedIds = headerRepo.loadOrderedIds()
if (orderedIds.isEmpty()) return null
val clients = MyApp.database.clientDao().getAllClients()
val questionnaires = MyApp.database.questionnaireDao().getAll()
val clients = MyApp.Companion.database.clientDao().getAllClients()
val questionnaires = MyApp.Companion.database.questionnaireDao().getAll()
val questionnaireIdSet = questionnaires.map { it.id }.toSet()
val wb = XSSFWorkbook()
@ -67,9 +71,9 @@ class ExcelExportService(
var c = 0
row.createCell(c++).setCellValue((rowIdx + 1).toDouble())
val completedForClient = MyApp.database.completedQuestionnaireDao().getAllForClient(client.clientCode)
val completedForClient = MyApp.Companion.database.completedQuestionnaireDao().getAllForClient(client.clientCode)
val statusMap = completedForClient.associate { it.questionnaireId to it.isDone }
val answers = MyApp.database.answerDao().getAnswersForClient(client.clientCode)
val answers = MyApp.Companion.database.answerDao().getAnswersForClient(client.clientCode)
val answerMap = answers.associate { it.questionId to it.answerValue }
orderedIds.forEach { id ->
@ -83,7 +87,7 @@ class ExcelExportService(
}
}
val bytes = java.io.ByteArrayOutputStream().use { bos ->
val bytes = ByteArrayOutputStream().use { bos ->
wb.write(bos); bos.toByteArray()
}
wb.close()
@ -112,7 +116,7 @@ class ExcelExportService(
} else {
val downloadsDir = Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DOWNLOADS)
if (!downloadsDir.exists()) downloadsDir.mkdirs()
val outFile = java.io.File(downloadsDir, filename)
val outFile = File(downloadsDir, filename)
outFile.writeBytes(bytes)
MediaScannerConnection.scanFile(
context,

View File

@ -1,8 +1,11 @@
package com.dano.test1
package com.dano.test1.export
import android.content.Context
import android.util.Log
import android.widget.Toast
import com.dano.test1.util.LanguageManager
import org.apache.poi.ss.usermodel.CellType
import org.apache.poi.ss.usermodel.DateUtil
import org.apache.poi.xssf.usermodel.XSSFWorkbook
import org.json.JSONArray
import java.nio.charset.Charset
@ -63,16 +66,16 @@ class HeaderOrderRepository(
for (i in first until last) {
val cell = row.getCell(i) ?: continue
val value = when (cell.cellType) {
org.apache.poi.ss.usermodel.CellType.STRING -> cell.stringCellValue
org.apache.poi.ss.usermodel.CellType.NUMERIC ->
if (org.apache.poi.ss.usermodel.DateUtil.isCellDateFormatted(cell))
CellType.STRING -> cell.stringCellValue
CellType.NUMERIC ->
if (DateUtil.isCellDateFormatted(cell))
cell.dateCellValue.time.toString()
else {
val n = cell.numericCellValue
if (n % 1.0 == 0.0) n.toLong().toString() else n.toString()
}
org.apache.poi.ss.usermodel.CellType.BOOLEAN -> cell.booleanCellValue.toString()
org.apache.poi.ss.usermodel.CellType.FORMULA -> cell.richStringCellValue.string
CellType.BOOLEAN -> cell.booleanCellValue.toString()
CellType.FORMULA -> cell.richStringCellValue.string
else -> ""
}.trim()

View File

@ -1,4 +1,4 @@
package com.dano.test1
package com.dano.test1.network
import java.io.File
import java.security.SecureRandom
@ -68,4 +68,4 @@ object AES256Helper {
cipher.init(Cipher.DECRYPT_MODE, SecretKeySpec(key, "AES"), IvParameterSpec(iv))
return cipher.doFinal(ct)
}
}
}

View File

@ -1,4 +1,4 @@
package com.dano.test1
package com.dano.test1.network
import android.content.Context
import android.util.Log

View File

@ -1,4 +1,4 @@
package com.dano.test1
package com.dano.test1.network
import android.content.Context
import android.database.sqlite.SQLiteDatabase

View File

@ -1,4 +1,4 @@
package com.dano.test1
package com.dano.test1.network
import android.content.Context
import android.net.ConnectivityManager

View File

@ -1,9 +1,12 @@
package com.dano.test1
package com.dano.test1.questionnaire
import android.view.View
import android.widget.*
import android.util.TypedValue
import androidx.core.widget.TextViewCompat
import com.dano.test1.util.LanguageManager
import com.dano.test1.util.setTextSizePercentOfScreenHeight
import com.dano.test1.MyApp
import com.dano.test1.R
import com.dano.test1.auth.TokenStore
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch
@ -38,10 +41,10 @@ class HandlerClientCoachCode(
questionTextView.text = question.question?.let { LanguageManager.getText(languageID, it) } ?: ""
setTextSizePercentOfScreenHeight(titleTextView, 0.03f)
setTextSizePercentOfScreenHeight(questionTextView, 0.03f)
setTextSizePercentOfScreenHeight(clientCodeField, 0.025f)
setTextSizePercentOfScreenHeight(coachCodeField, 0.025f)
titleTextView.setTextSizePercentOfScreenHeight(0.03f)
questionTextView.setTextSizePercentOfScreenHeight(0.03f)
clientCodeField.setTextSizePercentOfScreenHeight(0.025f)
coachCodeField.setTextSizePercentOfScreenHeight(0.025f)
// Client-Code: nur verwenden, wenn bereits geladen
val loadedClientCode = GlobalValues.LOADED_CLIENT_CODE
@ -72,13 +75,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) {
val loadedClientCode = GlobalValues.LOADED_CLIENT_CODE
@ -97,7 +93,7 @@ class HandlerClientCoachCode(
val dbExistedBefore = dbPath.exists()
CoroutineScope(Dispatchers.IO).launch {
val existingClient = MyApp.database.clientDao().getClientByCode(clientCode)
val existingClient = MyApp.Companion.database.clientDao().getClientByCode(clientCode)
withContext(Dispatchers.Main) {
if (existingClient != null && clientCodeField.isEnabled) {
@ -108,7 +104,7 @@ class HandlerClientCoachCode(
goToNextQuestion()
if (!dbExistedBefore) {
MyApp.database.close()
MyApp.Companion.database.close()
dbPath.delete()
val journalFile = layout.context.getDatabasePath("questionnaire_database-journal")
journalFile.delete()

View File

@ -1,7 +1,9 @@
package com.dano.test1
package com.dano.test1.questionnaire
import android.view.View
import android.widget.*
import com.dano.test1.util.LanguageManager
import com.dano.test1.R
/*
Zweck:

View File

@ -1,15 +1,18 @@
package com.dano.test1
package com.dano.test1.questionnaire
import android.content.Context
import android.view.View
import android.view.ViewGroup
import android.widget.*
import kotlinx.coroutines.*
import java.text.SimpleDateFormat
import java.util.*
import android.util.TypedValue
import androidx.core.widget.TextViewCompat
import android.widget.AbsListView
import com.dano.test1.util.LanguageManager
import com.dano.test1.util.setTextSizePercentOfScreenHeight
import com.dano.test1.util.setupSpinner
import com.dano.test1.util.Month
import com.dano.test1.util.Months
import com.dano.test1.MyApp
import com.dano.test1.R
/*
Zweck:
@ -51,13 +54,11 @@ class HandlerDateSpinner(
questionTextView.text = question.question?.let { LanguageManager.getText(languageID, it) } ?: ""
textView.text = question.textKey?.let { LanguageManager.getText(languageID, it) } ?: ""
// Schriftgrößen pro Bildschirmhöhe
setTextSizePercentOfScreenHeight(textView, 0.03f) // oben
setTextSizePercentOfScreenHeight(questionTextView, 0.03f) // frage
setTextSizePercentOfScreenHeight(labelDay, 0.025f)
setTextSizePercentOfScreenHeight(labelMonth, 0.025f)
setTextSizePercentOfScreenHeight(labelYear, 0.025f)
//
textView.setTextSizePercentOfScreenHeight(0.03f)
questionTextView.setTextSizePercentOfScreenHeight(0.03f)
labelDay.setTextSizePercentOfScreenHeight(0.025f)
labelMonth.setTextSizePercentOfScreenHeight(0.025f)
labelYear.setTextSizePercentOfScreenHeight(0.025f)
// gespeicherte Antwort (YYYY-MM-DD) lesen
val (savedYear, savedMonthIndex, savedDay) = question.question?.let {
@ -74,10 +75,9 @@ class HandlerDateSpinner(
?: months[today.get(Calendar.MONTH)]
val defaultYear = savedYear ?: today.get(Calendar.YEAR)
// Spinner responsiv aufsetzen (Schrift + Zeilenhöhe ohne Abschneiden)
setupSpinner(spinnerDay, days, defaultDay)
setupSpinner(spinnerMonth, months, defaultMonth)
setupSpinner(spinnerYear, years, defaultYear)
spinnerDay.setupSpinner(days, defaultDay)
spinnerMonth.setupSpinner(months, defaultMonth)
spinnerYear.setupSpinner(years, defaultYear)
// DB-Abfrage, falls noch nicht im answers-Map
val answerMapKey = question.question ?: (question.id ?: "")
@ -87,7 +87,7 @@ class HandlerDateSpinner(
val clientCode = GlobalValues.LAST_CLIENT_CODE
if (clientCode.isNullOrBlank()) return@launch
val allAnswersForClient = MyApp.database.answerDao().getAnswersForClient(clientCode)
val allAnswersForClient = MyApp.Companion.database.answerDao().getAnswersForClient(clientCode)
val myQuestionId = questionnaireMeta + "-" + question.question
val dbAnswer = allAnswersForClient.find { it.questionId == myQuestionId }?.answerValue
@ -207,71 +207,4 @@ class HandlerDateSpinner(
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)
}
}
}

View File

@ -1,11 +1,14 @@
package com.dano.test1
package com.dano.test1.questionnaire
import android.content.Context
import android.util.TypedValue
import android.view.Gravity
import android.view.View
import android.widget.*
import androidx.core.widget.TextViewCompat
import com.dano.test1.util.LanguageManager
import com.dano.test1.util.setTextSizePercentOfScreenHeight
import com.dano.test1.MyApp
import com.dano.test1.R
import kotlinx.coroutines.*
/*
@ -67,8 +70,8 @@ class HandlerGlassScaleQuestion(
titleTv.text = question.textKey?.let { LanguageManager.getText(languageID, it) } ?: ""
questionTv.text = question.question?.let { LanguageManager.getText(languageID, it) } ?: ""
setTextSizePercentOfScreenHeight(titleTv, 0.03f)
setTextSizePercentOfScreenHeight(questionTv, 0.03f)
titleTv.setTextSizePercentOfScreenHeight(0.03f)
questionTv.setTextSizePercentOfScreenHeight(0.03f)
// Header Icons
val header = layout.findViewById<LinearLayout>(R.id.glass_header)
@ -102,7 +105,7 @@ class HandlerGlassScaleQuestion(
CoroutineScope(Dispatchers.IO).launch {
try {
val clientCode = GlobalValues.LAST_CLIENT_CODE ?: return@launch
val allAnswersForClient = MyApp.database.answerDao().getAnswersForClient(clientCode)
val allAnswersForClient = MyApp.Companion.database.answerDao().getAnswersForClient(clientCode)
val answerMap = allAnswersForClient.associateBy({ it.questionId }, { it.answerValue })
withContext(Dispatchers.Main) {
@ -150,7 +153,7 @@ class HandlerGlassScaleQuestion(
text = LanguageManager.getText(languageID, symptomKey)
layoutParams = TableRow.LayoutParams(0, TableRow.LayoutParams.WRAP_CONTENT, 4f)
setPadding(4, 16, 4, 16)
setTextSizePercentOfScreenHeight(this, 0.022f)
setTextSizePercentOfScreenHeight(0.022f)
}
row.addView(symptomText)
@ -277,10 +280,4 @@ class HandlerGlassScaleQuestion(
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,12 +1,15 @@
package com.dano.test1
package com.dano.test1.questionnaire
import android.view.View
import android.widget.*
import android.text.Html
import kotlinx.coroutines.*
import android.util.TypedValue
import android.widget.TextView
import androidx.core.widget.TextViewCompat
import com.dano.test1.util.LanguageManager
import com.dano.test1.util.setTextSizePercentOfScreenHeight
import com.dano.test1.MainActivity
import com.dano.test1.R
import com.google.android.material.button.MaterialButton
/*
@ -57,9 +60,8 @@ class HandlerLastPage(
finishBtn.isAllCaps = false
applyResponsiveTextSizing(finishBtn)
// Überschriften responsiv skalieren (wie zuvor)
setTextSizePercentOfScreenHeight(titleTv, 0.03f)
setTextSizePercentOfScreenHeight(questionTv, 0.03f)
titleTv.setTextSizePercentOfScreenHeight(0.03f)
questionTv.setTextSizePercentOfScreenHeight(0.03f)
// Buttons
prevBtn.setOnClickListener { goToPreviousQuestion() }
@ -128,14 +130,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 =
answers.filterKeys { it.endsWith("_points") }
.values.mapNotNull { it as? Int }

View File

@ -1,4 +1,4 @@
package com.dano.test1
package com.dano.test1.questionnaire
import android.content.Context
import android.view.View
@ -6,6 +6,10 @@ import android.widget.*
import kotlinx.coroutines.*
import android.util.TypedValue
import androidx.core.widget.TextViewCompat
import com.dano.test1.util.LanguageManager
import com.dano.test1.util.setTextSizePercentOfScreenHeight
import com.dano.test1.MyApp
import com.dano.test1.R
/*
Zweck:
@ -37,9 +41,8 @@ class HandlerMultiCheckboxQuestion(
questionTextView.text = this.question.textKey?.let { LanguageManager.getText(languageID, it) } ?: ""
questionTitle.text = this.question.question?.let { LanguageManager.getText(languageID, it) } ?: ""
// Textgrößen pro Bildschirmhöhe (wie bei deinen anderen Handlern)
setTextSizePercentOfScreenHeight(questionTextView, 0.03f) // Überschrift
setTextSizePercentOfScreenHeight(questionTitle, 0.03f) // Frage
questionTextView.setTextSizePercentOfScreenHeight(0.03f)
questionTitle.setTextSizePercentOfScreenHeight(0.03f)
container.removeAllViews()
@ -88,7 +91,7 @@ class HandlerMultiCheckboxQuestion(
val clientCode = GlobalValues.LAST_CLIENT_CODE
if (clientCode.isNullOrBlank()) return@launch
val allAnswersForClient = MyApp.database.answerDao().getAnswersForClient(clientCode)
val allAnswersForClient = MyApp.Companion.database.answerDao().getAnswersForClient(clientCode)
val myQuestionId = questionnaireMeta + "-" + question.question
val dbAnswer = allAnswersForClient.find { it.questionId == myQuestionId }?.answerValue
@ -204,10 +207,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

@ -1,12 +1,14 @@
package com.dano.test1
package com.dano.test1.questionnaire
import android.content.Context
import android.view.View
import android.text.Html
import android.widget.*
import kotlinx.coroutines.*
import android.util.TypedValue
import androidx.core.widget.TextViewCompat // <— hinzugefügt
import com.dano.test1.util.LanguageManager
import com.dano.test1.util.setTextSizePercentOfScreenHeight
import com.dano.test1.MyApp
import com.dano.test1.R
/*
Zweck:
@ -41,11 +43,8 @@ class HandlerRadioQuestion(
Html.fromHtml(LanguageManager.getText(languageID, it), Html.FROM_HTML_MODE_LEGACY)
} ?: ""
//
// Titel/Frage: 3% der Bildschirmhöhe
setTextSizePercentOfScreenHeight(questionTextView, 0.03f)
setTextSizePercentOfScreenHeight(questionTitle, 0.03f)
// ===================================================
questionTextView.setTextSizePercentOfScreenHeight(0.03f)
questionTitle.setTextSizePercentOfScreenHeight(0.03f)
radioGroup.removeAllViews()
@ -54,8 +53,7 @@ class HandlerRadioQuestion(
text = LanguageManager.getText(languageID, option.key)
tag = option.key
// RadioButton-Text analog zu EditTexts: 2.5% der Bildschirmhöhe
setTextSizePercentOfScreenHeight(this, 0.025f)
setTextSizePercentOfScreenHeight(0.025f)
layoutParams = RadioGroup.LayoutParams(
RadioGroup.LayoutParams.MATCH_PARENT,
@ -81,7 +79,7 @@ class HandlerRadioQuestion(
val clientCode = GlobalValues.LAST_CLIENT_CODE
if (clientCode.isNullOrBlank()) return@launch
val allAnswersForClient = MyApp.database.answerDao().getAnswersForClient(clientCode)
val allAnswersForClient = MyApp.Companion.database.answerDao().getAnswersForClient(clientCode)
val myQuestionId = questionnaireMeta + "-" + question.question
val dbAnswer = allAnswersForClient.find { it.questionId == myQuestionId }?.answerValue
@ -137,15 +135,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) {
question.question?.let { questionKey ->
val savedAnswer = answers[questionKey] as? String

View File

@ -1,13 +1,15 @@
package com.dano.test1
package com.dano.test1.questionnaire
import android.content.Context
import android.view.View
import android.view.ViewGroup
import android.widget.*
import kotlinx.coroutines.*
import android.util.TypedValue
import android.widget.TextView
import androidx.core.widget.TextViewCompat
import com.dano.test1.util.LanguageManager
import com.dano.test1.util.setTextSizePercentOfScreenHeight
import com.dano.test1.util.setupSpinner
import com.dano.test1.MyApp
import com.dano.test1.R
import com.dano.test1.util.Countries
/*
Zweck:
@ -42,17 +44,15 @@ class HandlerStringSpinner(
questionTextView.text = question.question?.let { LanguageManager.getText(languageID, it) } ?: ""
textView.text = question.textKey?.let { LanguageManager.getText(languageID, it) } ?: ""
// Textgrößen prozentual zur Bildschirmhöhe (wie im HandlerRadioQuestion)
setTextSizePercentOfScreenHeight(textView, 0.03f)
setTextSizePercentOfScreenHeight(questionTextView, 0.03f)
textView.setTextSizePercentOfScreenHeight(0.03f)
questionTextView.setTextSizePercentOfScreenHeight(0.03f)
val options = buildOptionsList()
// vorhandene Auswahl (falls vorhanden)
val savedSelection = question.question?.let { answers[it] as? String }
// Spinner aufsetzen
setupSpinner(spinner, options, savedSelection)
spinner.setupSpinner(options, savedSelection)
// Falls noch keine Antwort im Map: aus DB laden
val answerMapKey = question.question ?: (question.id ?: "")
@ -62,7 +62,7 @@ class HandlerStringSpinner(
val clientCode = GlobalValues.LAST_CLIENT_CODE
if (clientCode.isNullOrBlank()) return@launch
val allAnswersForClient = MyApp.database.answerDao().getAnswersForClient(clientCode)
val allAnswersForClient = MyApp.Companion.database.answerDao().getAnswersForClient(clientCode)
val myQuestionId = questionnaireMeta + "-" + question.question
val dbAnswer = allAnswersForClient.find { it.questionId == myQuestionId }?.answerValue
@ -119,73 +119,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)
}
}
}

View File

@ -1,12 +1,14 @@
package com.dano.test1
package com.dano.test1.questionnaire
import android.content.Context
import android.view.View
import android.view.ViewGroup
import android.widget.*
import kotlinx.coroutines.*
import android.util.TypedValue
import androidx.core.widget.TextViewCompat // <- NEU
import com.dano.test1.util.LanguageManager
import com.dano.test1.util.setTextSizePercentOfScreenHeight
import com.dano.test1.util.setupSpinner
import com.dano.test1.MyApp
import com.dano.test1.R
/*
Zweck:
@ -43,10 +45,8 @@ class HandlerValueSpinner(
questionTextView.text = question.question?.let { LanguageManager.getText(languageID, it) } ?: ""
textView.text = question.textKey?.let { LanguageManager.getText(languageID, it) } ?: ""
// Schriftgrößen wie im HandlerRadioQuestion
// Titel/Frage: 3% der Bildschirmhöhe
setTextSizePercentOfScreenHeight(textView, 0.03f)
setTextSizePercentOfScreenHeight(questionTextView, 0.03f)
textView.setTextSizePercentOfScreenHeight(0.03f)
questionTextView.setTextSizePercentOfScreenHeight(0.03f)
val prompt = LanguageManager.getText(languageID, "choose_answer")
val spinnerItems: List<String> = listOf(prompt) + if (question.range != null) {
@ -56,7 +56,7 @@ class HandlerValueSpinner(
}
val savedValue = question.question?.let { answers[it] as? String }
setupSpinner(spinner, spinnerItems, savedValue)
spinner.setupSpinner(spinnerItems, savedValue)
//DB-Abfrage falls noch keine Antwort im Map existiert
val answerMapKey = question.question ?: (question.id ?: "")
@ -66,7 +66,7 @@ class HandlerValueSpinner(
val clientCode = GlobalValues.LAST_CLIENT_CODE
if (clientCode.isNullOrBlank()) return@launch
val allAnswersForClient = MyApp.database.answerDao().getAnswersForClient(clientCode)
val allAnswersForClient = MyApp.Companion.database.answerDao().getAnswersForClient(clientCode)
val myQuestionId = questionnaireMeta + "-" + question.question
val dbAnswer = allAnswersForClient.find { it.questionId == myQuestionId }?.answerValue
@ -127,72 +127,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)
}
}
}

View File

@ -1,4 +1,4 @@
package com.dano.test1
package com.dano.test1.questionnaire
import android.view.View
interface QuestionHandler {

View File

@ -1,9 +1,13 @@
package com.dano.test1
package com.dano.test1.questionnaire
import android.R
import android.app.Activity
import android.util.Log
import android.view.View
import android.widget.*
import com.dano.test1.util.LanguageManager
import com.dano.test1.MainActivity
import com.dano.test1.MyApp
import com.dano.test1.data.*
import com.google.gson.Gson
import com.google.gson.JsonParser
@ -61,8 +65,8 @@ abstract class QuestionnaireBase<T> {
}
protected fun setupSpinner(spinner: Spinner, spinnerValues: List<Any>, selectedValue: Any?) {
val adapter = ArrayAdapter(context, android.R.layout.simple_spinner_item, spinnerValues).apply {
setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item)
val adapter = ArrayAdapter(context, R.layout.simple_spinner_item, spinnerValues).apply {
setDropDownViewResource(R.layout.simple_spinner_dropdown_item)
}
spinner.adapter = adapter
selectedValue?.let { value ->
@ -75,7 +79,7 @@ abstract class QuestionnaireBase<T> {
protected fun navigateTo(layoutResId: Int, setup: (View) -> Unit) {
context.setContentView(layoutResId)
val rootView = context.findViewById<View>(android.R.id.content)
val rootView = context.findViewById<View>(R.id.content)
setup(rootView)
}
@ -85,7 +89,7 @@ abstract class QuestionnaireBase<T> {
protected fun showEmptyScreen() {
navigateTo(getLayoutResId("empty")) {
setupPrevButton(R.id.Qprev) { goToPreviousQuestion() }
setupPrevButton(com.dano.test1.R.id.Qprev) { goToPreviousQuestion() }
}
}
@ -172,7 +176,7 @@ abstract class QuestionnaireBase<T> {
suspend fun saveAnswersToDatabase(answers: Map<String, Any>, questionnaireId: String) {
Log.d("AnswersMap", answers.toString())
val db = MyApp.database
val db = MyApp.Companion.database
val clientCode = answers["client_code"] as? String ?: return

View File

@ -1,6 +1,8 @@
package com.dano.test1
package com.dano.test1.questionnaire
import android.widget.Button
import com.dano.test1.util.LocalizationHelper
import com.dano.test1.R
open class QuestionnaireGeneric(private val questionnaireFileName: String) : QuestionnaireBase<Unit>() {

View File

@ -1,4 +1,4 @@
package com.dano.test1
package com.dano.test1.questionnaire
data class Option(
val key: String, // Must always be set

View File

@ -1,14 +1,31 @@
package com.dano.test1
package com.dano.test1.ui
import android.graphics.Color
import android.graphics.Typeface
import android.util.Log
import android.view.View
import android.widget.*
import android.widget.Button
import android.widget.ProgressBar
import android.widget.TableLayout
import android.widget.TableRow
import android.widget.TextView
import android.widget.Toast
import com.dano.test1.export.ExcelExportService
import com.dano.test1.export.HeaderOrderRepository
import com.dano.test1.util.LanguageManager
import com.dano.test1.MainActivity
import com.dano.test1.MyApp
import com.dano.test1.R
import com.dano.test1.data.Client
import com.dano.test1.data.Question
import com.dano.test1.data.Questionnaire
import kotlinx.coroutines.*
import kotlin.math.roundToInt
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.SupervisorJob
import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext
import org.json.JSONArray
import kotlin.math.roundToInt
class DatabaseButtonHandler(
private val activity: MainActivity,
@ -59,7 +76,7 @@ class DatabaseButtonHandler(
uiScope.launch {
val clients: List<Client> = withContext(Dispatchers.IO) {
MyApp.database.clientDao().getAllClients()
MyApp.Companion.database.clientDao().getAllClients()
}
progress.visibility = View.GONE
@ -146,9 +163,11 @@ class DatabaseButtonHandler(
uiScope.launch {
val result = withContext(Dispatchers.IO) {
val allQuestionnairesDb = MyApp.database.questionnaireDao().getAll()
val completedForClient = MyApp.database.completedQuestionnaireDao().getAllForClient(clientCode)
val allAnswersForClient = MyApp.database.answerDao().getAnswersForClient(clientCode)
val allQuestionnairesDb = MyApp.Companion.database.questionnaireDao().getAll()
val completedForClient =
MyApp.Companion.database.completedQuestionnaireDao().getAllForClient(clientCode)
val allAnswersForClient =
MyApp.Companion.database.answerDao().getAnswersForClient(clientCode)
Triple(allQuestionnairesDb, completedForClient, allAnswersForClient)
}
@ -267,8 +286,9 @@ class DatabaseButtonHandler(
uiScope.launch {
val (questions, answersForClient) = withContext(Dispatchers.IO) {
val qs = MyApp.database.questionDao().getQuestionsForQuestionnaire(questionnaireId)
val ans = MyApp.database.answerDao()
val qs = MyApp.Companion.database.questionDao()
.getQuestionsForQuestionnaire(questionnaireId)
val ans = MyApp.Companion.database.answerDao()
.getAnswersForClientAndQuestionnaire(clientCode, questionnaireId)
qs to ans
}
@ -361,7 +381,7 @@ class DatabaseButtonHandler(
val row = TableRow(activity).apply {
isClickable = true
isFocusable = true
setBackgroundColor(android.graphics.Color.TRANSPARENT)
setBackgroundColor(Color.TRANSPARENT)
setOnClickListener { onClick() }
}
cells.forEachIndexed { index, text ->
@ -396,7 +416,7 @@ class DatabaseButtonHandler(
this.text = text
setPadding(dp(12), dp(10), dp(12), dp(10))
textSize = 16f
setTypeface(typeface, android.graphics.Typeface.BOLD)
setTypeface(typeface, Typeface.BOLD)
}
private fun makeBodyCell(
@ -462,4 +482,4 @@ class DatabaseButtonHandler(
} catch (_: Exception) {
emptyList()
}
}
}

View File

@ -1,10 +1,14 @@
package com.dano.test1
package com.dano.test1.ui
import android.widget.Button
import android.widget.EditText
import android.widget.Toast
import com.dano.test1.util.LanguageManager
import com.dano.test1.MainActivity
import com.dano.test1.MyApp
import kotlinx.coroutines.*
import com.dano.test1.data.CompletedQuestionnaire
import com.dano.test1.questionnaire.GlobalValues
class EditButtonHandler(
private val activity: MainActivity,
@ -58,7 +62,7 @@ class EditButtonHandler(
}
val completedEntries: List<CompletedQuestionnaire> =
MyApp.database.completedQuestionnaireDao().getAllForClient(desiredCode)
MyApp.Companion.database.completedQuestionnaireDao().getAllForClient(desiredCode)
val completedFiles = completedEntries.filter { it.isDone }.map { it.questionnaireId.lowercase() }

View File

@ -1,13 +1,26 @@
package com.dano.test1
package com.dano.test1.ui
import android.content.Context
import android.content.res.ColorStateList
import android.graphics.Color
import android.graphics.drawable.GradientDrawable
import android.os.Handler
import android.os.Looper
import android.util.TypedValue
import android.view.Gravity
import android.view.View
import android.widget.*
import com.dano.test1.util.LanguageManager
import com.dano.test1.MainActivity
import com.dano.test1.R
import com.dano.test1.auth.LoginManager
import com.dano.test1.auth.TokenStore
import com.dano.test1.network.DatabaseUploader
import com.dano.test1.network.NetworkUtils
import com.dano.test1.questionnaire.GlobalValues
import com.dano.test1.questionnaire.QuestionItem
import com.dano.test1.questionnaire.QuestionnaireBase
import com.dano.test1.questionnaire.QuestionnaireGeneric
import com.google.android.material.button.MaterialButton
import org.json.JSONArray
import org.json.JSONObject
@ -682,22 +695,22 @@ class HandlerOpeningScreen(private val activity: MainActivity) {
private fun showRedToast(ctx: android.content.Context, message: String) {
val tv = android.widget.TextView(ctx).apply {
private fun showRedToast(ctx: Context, message: String) {
val tv = TextView(ctx).apply {
text = message
setTextColor(android.graphics.Color.WHITE)
setTextColor(Color.WHITE)
textSize = 16f
setPadding(32, 20, 32, 20)
background = android.graphics.drawable.GradientDrawable().apply {
shape = android.graphics.drawable.GradientDrawable.RECTANGLE
background = GradientDrawable().apply {
shape = GradientDrawable.RECTANGLE
cornerRadius = 24f
setColor(android.graphics.Color.parseColor("#D32F2F")) // kräftiges Rot
setColor(Color.parseColor("#D32F2F")) // kräftiges Rot
}
}
android.widget.Toast(ctx).apply {
duration = android.widget.Toast.LENGTH_LONG
Toast(ctx).apply {
duration = Toast.LENGTH_LONG
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()
}

View File

@ -1,10 +1,15 @@
package com.dano.test1
package com.dano.test1.ui
import android.widget.Button
import android.widget.EditText
import android.widget.Toast
import com.dano.test1.util.LanguageManager
import com.dano.test1.MainActivity
import com.dano.test1.MyApp
import kotlinx.coroutines.*
import com.dano.test1.data.CompletedQuestionnaire
import com.dano.test1.questionnaire.GlobalValues
import com.dano.test1.questionnaire.QuestionItem
class LoadButtonHandler(
private val activity: MainActivity,
@ -40,7 +45,7 @@ class LoadButtonHandler(
GlobalValues.LAST_CLIENT_CODE = clientCode
CoroutineScope(Dispatchers.IO).launch {
val client = MyApp.database.clientDao().getClientByCode(clientCode)
val client = MyApp.Companion.database.clientDao().getClientByCode(clientCode)
if (client == null) {
GlobalValues.LOADED_CLIENT_CODE = null
withContext(Dispatchers.Main) {
@ -80,7 +85,7 @@ class LoadButtonHandler(
}
}
is QuestionItem.Condition.QuestionCondition -> {
val answers = MyApp.database.answerDao().getAnswersForClientAndQuestionnaire(clientCode, condition.questionnaire)
val answers = MyApp.Companion.database.answerDao().getAnswersForClientAndQuestionnaire(clientCode, condition.questionnaire)
val relevant = answers.find { it.questionId.endsWith(condition.questionId, ignoreCase = true) }
val answerValue = relevant?.answerValue ?: ""
when (condition.operator) {
@ -97,7 +102,7 @@ class LoadButtonHandler(
}
if (!reqOk) return false
val q = condition.questionCheck ?: return true
val answers = MyApp.database.answerDao().getAnswersForClientAndQuestionnaire(clientCode, q.questionnaire)
val answers = MyApp.Companion.database.answerDao().getAnswersForClientAndQuestionnaire(clientCode, q.questionnaire)
val relevant = answers.find { it.questionId.endsWith(q.questionId, ignoreCase = true) }
val answerValue = relevant?.answerValue ?: ""
when (q.operator) {
@ -117,7 +122,7 @@ class LoadButtonHandler(
private suspend fun handleNormalLoad(clientCode: String) {
val completedEntries = withContext(Dispatchers.IO) {
MyApp.database.completedQuestionnaireDao().getAllForClient(clientCode)
MyApp.Companion.database.completedQuestionnaireDao().getAllForClient(clientCode)
}
buttonPoints.clear()

View File

@ -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.Paint
import android.graphics.pdf.PdfDocument
import android.provider.MediaStore
import android.util.Log
import android.widget.Button
import android.widget.EditText
import android.widget.Toast
import com.dano.test1.util.LanguageManager
import com.dano.test1.MainActivity
import com.dano.test1.MyApp
import com.dano.test1.questionnaire.GlobalValues
import kotlinx.coroutines.*
class SaveButtonHandler(
@ -35,7 +44,7 @@ class SaveButtonHandler(
private fun showCompletedQuestionnaires(clientCode: String) {
CoroutineScope(Dispatchers.IO).launch {
val actualClientCode = clientCode.removeSuffix("_database")
val completedEntries = MyApp.database.completedQuestionnaireDao().getAllForClient(actualClientCode)
val completedEntries = MyApp.Companion.database.completedQuestionnaireDao().getAllForClient(actualClientCode)
Log.d("PDF_DEBUG", "Completed entries for client $actualClientCode:")
for (entry in completedEntries) {
@ -65,7 +74,7 @@ class SaveButtonHandler(
canvas.drawText("Points: ${entry.sumPoints ?: "N/A"}", 20f, yPosition, paint)
yPosition += 30f
val answers = MyApp.database.answerDao().getAnswersForClientAndQuestionnaire(actualClientCode, entry.questionnaireId)
val answers = MyApp.Companion.database.answerDao().getAnswersForClientAndQuestionnaire(actualClientCode, entry.questionnaireId)
for (answer in answers) {
val questionKey = answer.questionId.substringAfter("-")
@ -105,19 +114,19 @@ class SaveButtonHandler(
val resolver = activity.contentResolver
val deleteIfExists: (String) -> Unit = { name ->
val projection = arrayOf(android.provider.MediaStore.MediaColumns._ID)
val selection = "${android.provider.MediaStore.MediaColumns.DISPLAY_NAME} = ?"
val projection = arrayOf(MediaStore.MediaColumns._ID)
val selection = "${MediaStore.MediaColumns.DISPLAY_NAME} = ?"
val selectionArgs = arrayOf(name)
val query = resolver.query(
android.provider.MediaStore.Downloads.EXTERNAL_CONTENT_URI,
MediaStore.Downloads.EXTERNAL_CONTENT_URI,
projection, selection, selectionArgs, null
)
query?.use { cursor ->
if (cursor.moveToFirst()) {
val idColumn = cursor.getColumnIndexOrThrow(android.provider.MediaStore.MediaColumns._ID)
val idColumn = cursor.getColumnIndexOrThrow(MediaStore.MediaColumns._ID)
val id = cursor.getLong(idColumn)
val deleteUri = android.content.ContentUris.withAppendedId(
android.provider.MediaStore.Downloads.EXTERNAL_CONTENT_URI, id
val deleteUri = ContentUris.withAppendedId(
MediaStore.Downloads.EXTERNAL_CONTENT_URI, id
)
resolver.delete(deleteUri, null, null)
}
@ -129,20 +138,20 @@ class SaveButtonHandler(
try {
val pdfUri = resolver.insert(
android.provider.MediaStore.Downloads.EXTERNAL_CONTENT_URI,
android.content.ContentValues().apply {
put(android.provider.MediaStore.MediaColumns.DISPLAY_NAME, pdfFileName)
put(android.provider.MediaStore.MediaColumns.MIME_TYPE, "application/pdf")
put(android.provider.MediaStore.MediaColumns.RELATIVE_PATH, "Download/")
MediaStore.Downloads.EXTERNAL_CONTENT_URI,
ContentValues().apply {
put(MediaStore.MediaColumns.DISPLAY_NAME, pdfFileName)
put(MediaStore.MediaColumns.MIME_TYPE, "application/pdf")
put(MediaStore.MediaColumns.RELATIVE_PATH, "Download/")
}
)
val csvUri = resolver.insert(
android.provider.MediaStore.Downloads.EXTERNAL_CONTENT_URI,
android.content.ContentValues().apply {
put(android.provider.MediaStore.MediaColumns.DISPLAY_NAME, csvFileName)
put(android.provider.MediaStore.MediaColumns.MIME_TYPE, "text/csv")
put(android.provider.MediaStore.MediaColumns.RELATIVE_PATH, "Download/")
MediaStore.Downloads.EXTERNAL_CONTENT_URI,
ContentValues().apply {
put(MediaStore.MediaColumns.DISPLAY_NAME, csvFileName)
put(MediaStore.MediaColumns.MIME_TYPE, "text/csv")
put(MediaStore.MediaColumns.RELATIVE_PATH, "Download/")
}
)
@ -162,13 +171,13 @@ class SaveButtonHandler(
Toast.makeText(activity, msg, Toast.LENGTH_LONG).show()
pdfUri?.let {
val intent = android.content.Intent(android.content.Intent.ACTION_VIEW).apply {
val intent = Intent(Intent.ACTION_VIEW).apply {
setDataAndType(it, "application/pdf")
addFlags(android.content.Intent.FLAG_GRANT_READ_URI_PERMISSION or android.content.Intent.FLAG_ACTIVITY_NO_HISTORY)
addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION or Intent.FLAG_ACTIVITY_NO_HISTORY)
}
try {
activity.startActivity(intent)
} catch (e: android.content.ActivityNotFoundException) {
} catch (e: ActivityNotFoundException) {
val noViewer = LanguageManager.getText(languageIDProvider(), "no_pdf_viewer")
Toast.makeText(activity, noViewer, Toast.LENGTH_SHORT).show()
}

View File

@ -1,4 +1,6 @@
package com.dano.test1
package com.dano.test1.util
import com.dano.test1.util.LanguageManager
object Countries {
fun getAllCountries(languageID: String): List<String> {

View File

@ -1,6 +1,8 @@
package com.dano.test1
package com.dano.test1.util
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 {
fun getTextFormatted(languageId: String, key: String, vararg args: Any): String {
@ -61,14 +63,14 @@ object LanguageManager {
"once" to "einmal",
"year_after_2000" to "Das Jahr muss nach 2000 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!",
"multiple_times" to "mehrmals",
"more_than_15_years" to "mehr als 15 Jahre",
"no" to "Nein",
"no_answer" to "keine Angabe",
"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!",
"invalid_month" to "Ungültige Monatsangabe!",
"invalid_year" to "Ungültige Jahresangabe!",
@ -438,14 +440,14 @@ object LanguageManager {
"once" to "once",
"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_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!",
"multiple_times" to "multiple times",
"more_than_15_years" to "more than 15 years",
"no" to "No",
"no_answer" to "No answer",
"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!",
"invalid_month" to "Invalid month!",
"invalid_year" to "Invalid year!",
@ -814,14 +816,14 @@ object LanguageManager {
"once" to "une fois",
"year_after_2000" to "Lannée doit être après 2000 !",
"year_after_departure" to "Lannée doit être après le départ du pays dorigine !",
"year_max" to "Lannée doit être inférieure ou égale à $MAX_VALUE_YEAR !",
"year_max" to "Lanné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 !",
"multiple_times" to "plusieurs fois",
"more_than_15_years" to "plus de 15 ans",
"no" to "Non",
"no_answer" to "pas de réponse",
"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 !",
"invalid_month" to "Mois invalide !",
"invalid_year" to "Année invalide !",
@ -1194,14 +1196,14 @@ object LanguageManager {
"once" to "один раз",
"year_after_2000" to "Год должен быть после 2000!",
"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> Данные нельзя изменять после завершения!",
"multiple_times" to "несколько раз",
"more_than_15_years" to "более 15 лет",
"no" to "Нет",
"no_answer" 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!",
"invalid_month" to "Недопустимый месяц!",
"invalid_year" to "Недопустимый год!",
@ -1570,14 +1572,14 @@ object LanguageManager {
"once" to "один раз",
"year_after_2000" to "Рік має бути після 2000!",
"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> Дані після завершення не можна змінити або редагувати!",
"multiple_times" to "багато разів",
"more_than_15_years" to "більше 15 років",
"no" to "Ні",
"no_answer" 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!",
"invalid_month" to "Неправильний місяць!",
"invalid_year" to "Неправильний рік!",
@ -1950,14 +1952,14 @@ object LanguageManager {
"once" to "bir kez",
"year_after_2000" to "Yıl 2000den 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_YEARden 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!",
"multiple_times" to "birden fazla kez",
"more_than_15_years" to "15 yıldan fazla",
"no" to "Hayır",
"no_answer" to "Cevap yok",
"other_country" to "diğer ülke",
"value_must_be_less_equal_max" to "Değer $MAX_VALUE_AGEden 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!",
"invalid_month" to "Geçersiz ay girişi!",
"invalid_year" to "Geçersiz yıl girişi!",
@ -2330,14 +2332,14 @@ object LanguageManager {
"once" to "jeden raz",
"year_after_2000" to "Rok musi być po 2000!",
"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!",
"multiple_times" to "kilka razy",
"more_than_15_years" to "więcej niż 15 lat",
"no" to "Nie",
"no_answer" to "brak odpowiedzi",
"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!",
"invalid_month" to "Nieprawidłowy miesiąc!",
"invalid_year" to "Nieprawidłowy rok!",
@ -2710,14 +2712,14 @@ object LanguageManager {
"once" to "مرة واحدة",
"year_after_2000" to "يجب أن تكون السنة بعد 2000!",
"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> لا يمكن تعديل البيانات بعد الانتهاء!",
"multiple_times" to "عدة مرات",
"more_than_15_years" to "أكثر من 15 سنة",
"no" to "لا",
"no_answer" 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!",
"invalid_month" to "شهر غير صالح!",
"invalid_year" to "سنة غير صالحة!",
@ -3090,14 +3092,14 @@ object LanguageManager {
"once" to "o dată",
"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_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!",
"multiple_times" to "de mai multe ori",
"more_than_15_years" to "mai mult de 15 ani",
"no" to "Nu",
"no_answer" to "fără răspuns",
"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!",
"invalid_month" to "Lună invalidă!",
"invalid_year" to "An invalid!",
@ -3470,14 +3472,14 @@ object LanguageManager {
"once" to "una vez",
"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_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.",
"multiple_times" to "varias veces",
"more_than_15_years" to "más de 15 años",
"no" to "No",
"no_answer" to "sin respuesta",
"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!",
"invalid_month" to "¡Mes no válido!",
"invalid_year" to "¡Año no válido!",

View File

@ -1,4 +1,4 @@
package com.dano.test1
package com.dano.test1.util
import android.view.View
import android.view.ViewGroup

View File

@ -1,11 +1,11 @@
package com.dano.test1
package com.dano.test1.util
data class Month(val name: String) {
override fun toString(): String = name
}
object Months {
fun getAllMonths(languageID: String): List<Any> {
fun getAllMonths(languageID: String): List<Month> {
return listOf(
Month(LanguageManager.getText(languageID, "january")),
Month(LanguageManager.getText(languageID, "february")),

View File

@ -0,0 +1,73 @@
package com.dano.test1.util
import android.util.TypedValue
import android.view.Gravity
import android.view.View
import android.view.ViewGroup
import android.widget.AbsListView
import android.widget.ArrayAdapter
import android.widget.Spinner
import android.widget.TextView
import androidx.core.widget.TextViewCompat
fun TextView.setTextSizePercentOfScreenHeight(percentOfHeight: Float) {
val dm = context.resources.displayMetrics
val sp = (dm.heightPixels * percentOfHeight) / dm.scaledDensity
TextViewCompat.setAutoSizeTextTypeWithDefaults(this, TextViewCompat.AUTO_SIZE_TEXT_TYPE_NONE)
setTextSize(TypedValue.COMPLEX_UNIT_SP, sp)
}
fun <T> Spinner.setupSpinner(items: List<T>, selectedItem: T?) {
val dm = context.resources.displayMetrics
fun spFromScreenHeight(percent: Float): Float = (dm.heightPixels * percent) / dm.scaledDensity
fun pxFromSp(sp: Float): Int = (sp * dm.scaledDensity).toInt()
val textSp = spFromScreenHeight(0.0275f)
val textPx = pxFromSp(textSp)
val vPadPx = (textPx * 0.50f).toInt()
val rowHeight = (textPx * 2.20f + 2 * vPadPx).toInt()
val adapter = object : ArrayAdapter<T>(context, android.R.layout.simple_spinner_item, items) {
private fun styleRow(tv: TextView, forceHeight: Boolean) {
tv.setTextSize(TypedValue.COMPLEX_UNIT_SP, textSp)
tv.includeFontPadding = true
tv.setLineSpacing(0f, 1.2f)
tv.gravity = (tv.gravity and Gravity.HORIZONTAL_GRAVITY_MASK) or Gravity.CENTER_VERTICAL
tv.setPadding(tv.paddingLeft, vPadPx, tv.paddingRight, vPadPx)
tv.minHeight = rowHeight
tv.isSingleLine = true
if (forceHeight) {
val lp = tv.layoutParams
if (lp == null || lp.height <= 0) {
tv.layoutParams = AbsListView.LayoutParams(AbsListView.LayoutParams.MATCH_PARENT, rowHeight)
} else {
lp.height = rowHeight
}
}
}
override fun getView(position: Int, convertView: View?, parent: ViewGroup): View {
val v = super.getView(position, convertView, parent) as TextView
styleRow(v, forceHeight = false)
return v
}
override fun getDropDownView(position: Int, convertView: View?, parent: ViewGroup): View {
val v = super.getDropDownView(position, convertView, parent) as TextView
styleRow(v, forceHeight = true)
return v
}
}
adapter.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item)
this.adapter = adapter
setPadding(paddingLeft, vPadPx, paddingRight, vPadPx)
minimumHeight = rowHeight
requestLayout()
selectedItem?.let {
val index = items.indexOf(it)
if (index >= 0) setSelection(index)
}
}