languagemanager update
This commit is contained in:
@ -1,11 +1,5 @@
|
||||
package com.dano.test1
|
||||
|
||||
import android.content.ContentValues
|
||||
import android.media.MediaScannerConnection
|
||||
import android.net.Uri
|
||||
import android.os.Build
|
||||
import android.os.Environment
|
||||
import android.provider.MediaStore
|
||||
import android.util.Log
|
||||
import android.view.View
|
||||
import android.widget.*
|
||||
@ -13,25 +7,21 @@ import com.dano.test1.data.Client
|
||||
import com.dano.test1.data.Question
|
||||
import com.dano.test1.data.Questionnaire
|
||||
import kotlinx.coroutines.*
|
||||
import org.json.JSONArray
|
||||
import java.io.File
|
||||
import java.nio.charset.Charset
|
||||
import kotlin.math.roundToInt
|
||||
import org.apache.poi.ss.usermodel.Row
|
||||
import org.apache.poi.xssf.usermodel.XSSFWorkbook
|
||||
import org.json.JSONArray
|
||||
|
||||
class DatabaseButtonHandler(
|
||||
private val activity: MainActivity,
|
||||
private val databaseButton: Button,
|
||||
private val onClose: () -> Unit,
|
||||
// Aktuelle UI-Sprache für die Bildschirm-Anzeige (header-Tabelle)
|
||||
private val languageIDProvider: () -> String = { "GERMAN" }
|
||||
) {
|
||||
private val uiScope = CoroutineScope(SupervisorJob() + Dispatchers.Main)
|
||||
private val tag = "DatabaseButtonHandler"
|
||||
|
||||
// Cache für geladene IDs aus assets/header_order.json oder header_order.xlsx
|
||||
private var orderedIdsCache: List<String>? = null
|
||||
// Services (wie bei dir)
|
||||
private val headerRepo = HeaderOrderRepository(activity)
|
||||
private val exporter = ExcelExportService(activity, headerRepo)
|
||||
|
||||
fun setup() {
|
||||
databaseButton.text = "Datenbank"
|
||||
@ -44,12 +34,21 @@ class DatabaseButtonHandler(
|
||||
private fun openDatabaseScreen() {
|
||||
activity.setContentView(R.layout.database_screen)
|
||||
|
||||
val lang = safeLang() // ✨
|
||||
|
||||
val titleTv: TextView = requireView(R.id.title, "title") // ✨
|
||||
val table: TableLayout = requireView(R.id.tableClients, "tableClients")
|
||||
val progress: ProgressBar = requireView(R.id.progressBar, "progressBar")
|
||||
val emptyView: TextView = requireView(R.id.emptyView, "emptyView")
|
||||
val backButton: Button = requireView(R.id.backButton, "backButton")
|
||||
val btnDownloadHeader: Button = requireView(R.id.btnDownloadHeader, "btnDownloadHeader")
|
||||
|
||||
// ✨ statische Texte lokalisieren
|
||||
titleTv.text = t(lang, "database_clients_title") ?: "Datenbank – Clients"
|
||||
emptyView.text = t(lang, "no_clients_available") ?: "Keine Clients vorhanden."
|
||||
backButton.text = t(lang, "previous") ?: "Zurück"
|
||||
btnDownloadHeader.text = t(lang, "download_header") ?: "Download Header"
|
||||
|
||||
backButton.setOnClickListener { onClose() }
|
||||
btnDownloadHeader.setOnClickListener { onDownloadHeadersClicked(progress) }
|
||||
|
||||
@ -57,7 +56,8 @@ class DatabaseButtonHandler(
|
||||
emptyView.visibility = View.GONE
|
||||
table.removeAllViews()
|
||||
|
||||
addHeaderRow(table, listOf("#", "Client-Code"))
|
||||
// ✨ Spaltenkopf lokalisieren
|
||||
addHeaderRow(table, listOf("#", t(lang, "client_code") ?: "Client-Code"))
|
||||
|
||||
uiScope.launch {
|
||||
val clients: List<Client> = withContext(Dispatchers.IO) {
|
||||
@ -88,17 +88,18 @@ class DatabaseButtonHandler(
|
||||
uiScope.launch {
|
||||
try {
|
||||
progress.visibility = View.VISIBLE
|
||||
val savedUri = exportHeadersForAllClients() // -> speichert NUR in Downloads
|
||||
val savedUri = exporter.exportHeadersForAllClients()
|
||||
progress.visibility = View.GONE
|
||||
|
||||
val lang = safeLang() // ✨
|
||||
if (savedUri != null) {
|
||||
Toast.makeText(
|
||||
activity,
|
||||
"Export erfolgreich: Downloads/ClientHeaders.xlsx",
|
||||
t(lang, "export_success_downloads") ?: "Export erfolgreich: Downloads/ClientHeaders.xlsx",
|
||||
Toast.LENGTH_LONG
|
||||
).show()
|
||||
} else {
|
||||
Toast.makeText(activity, "Export fehlgeschlagen.", Toast.LENGTH_LONG).show()
|
||||
Toast.makeText(activity, t(lang, "export_failed") ?: "Export fehlgeschlagen.", Toast.LENGTH_LONG).show()
|
||||
}
|
||||
} catch (e: Exception) {
|
||||
progress.visibility = View.GONE
|
||||
@ -108,165 +109,27 @@ class DatabaseButtonHandler(
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Erzeugt eine Excel-Datei (ClientHeaders.xlsx) mit:
|
||||
* - Zeile 1: Spalten-IDs (header_order.json / header_order.xlsx)
|
||||
* - Zeile 2: ENGLISCHE Fragen/Beschriftungen zu jeder ID (rein EN, keine IDs mehr)
|
||||
* - Ab Zeile 3: pro Client die Werte ("Done"/"Not Done"/Antwort oder "None")
|
||||
* - Speichert AUSSCHLIESSLICH in den öffentlichen Geräte-Ordner "Downloads"
|
||||
*/
|
||||
private suspend fun exportHeadersForAllClients(): Uri? {
|
||||
val orderedIds = loadOrderedIds()
|
||||
if (orderedIds.isEmpty()) return null
|
||||
|
||||
val clients = MyApp.database.clientDao().getAllClients()
|
||||
val questionnaires = MyApp.database.questionnaireDao().getAll()
|
||||
val questionnaireIdSet = questionnaires.map { it.id }.toSet()
|
||||
|
||||
val wb = XSSFWorkbook()
|
||||
val sheet = wb.createSheet("Headers")
|
||||
|
||||
sheet.setColumnWidth(0, 8 * 256) // #
|
||||
for (i in 1..orderedIds.size) sheet.setColumnWidth(i, 36 * 256)
|
||||
|
||||
// Zeile 1: IDs
|
||||
var col = 0
|
||||
val headerRow: Row = sheet.createRow(0)
|
||||
headerRow.createCell(col++).setCellValue("#")
|
||||
orderedIds.forEach { id -> headerRow.createCell(col++).setCellValue(id) }
|
||||
|
||||
// Zeile 2: Fragen/Titel konsequent auf EN
|
||||
val questionRow: Row = sheet.createRow(1)
|
||||
var qc = 0
|
||||
questionRow.createCell(qc++).setCellValue("Question (EN)")
|
||||
for (id in orderedIds) {
|
||||
val englishQuestion = englishQuestionForId(id, questionnaireIdSet)
|
||||
questionRow.createCell(qc++).setCellValue(englishQuestion)
|
||||
}
|
||||
|
||||
// Ab Zeile 3: Werte je Client
|
||||
clients.forEachIndexed { rowIdx, client ->
|
||||
val row: Row = sheet.createRow(rowIdx + 2)
|
||||
var c = 0
|
||||
row.createCell(c++).setCellValue((rowIdx + 1).toDouble())
|
||||
|
||||
val completedForClient = MyApp.database.completedQuestionnaireDao().getAllForClient(client.clientCode)
|
||||
val statusMap = completedForClient.associate { it.questionnaireId to it.isDone }
|
||||
val answers = MyApp.database.answerDao().getAnswersForClient(client.clientCode)
|
||||
val answerMap = answers.associate { it.questionId to it.answerValue }
|
||||
|
||||
orderedIds.forEach { id ->
|
||||
val raw = when {
|
||||
id == "client_code" -> client.clientCode
|
||||
id in questionnaireIdSet -> if (statusMap[id] == true) "Done" else "Not Done"
|
||||
else -> answerMap[id]?.takeIf { it.isNotBlank() } ?: "None"
|
||||
}
|
||||
val out = localizeForExportEn(id, raw) // Export immer EN
|
||||
row.createCell(c++).setCellValue(out)
|
||||
}
|
||||
}
|
||||
|
||||
// Bytes erzeugen
|
||||
val bytes = java.io.ByteArrayOutputStream().use { bos ->
|
||||
wb.write(bos); bos.toByteArray()
|
||||
}
|
||||
wb.close()
|
||||
|
||||
// -> nur in Downloads speichern (Q+ via MediaStore, sonst Public Downloads)
|
||||
return saveToDownloads(
|
||||
filename = "ClientHeaders.xlsx",
|
||||
mimeType = "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet",
|
||||
bytes = bytes
|
||||
)
|
||||
}
|
||||
|
||||
/** Speichert Bytes nach "Downloads". */
|
||||
private fun saveToDownloads(filename: String, mimeType: String, bytes: ByteArray): Uri? {
|
||||
return if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
|
||||
// Android 10+ (Scoped Storage)
|
||||
val resolver = activity.contentResolver
|
||||
val values = ContentValues().apply {
|
||||
put(MediaStore.MediaColumns.DISPLAY_NAME, filename)
|
||||
put(MediaStore.MediaColumns.MIME_TYPE, mimeType)
|
||||
put(MediaStore.MediaColumns.RELATIVE_PATH, Environment.DIRECTORY_DOWNLOADS)
|
||||
// optional: put(MediaStore.MediaColumns.IS_PENDING, 1)
|
||||
}
|
||||
val collection = MediaStore.Downloads.getContentUri(MediaStore.VOLUME_EXTERNAL_PRIMARY)
|
||||
val uri = resolver.insert(collection, values)
|
||||
if (uri != null) {
|
||||
resolver.openOutputStream(uri)?.use { it.write(bytes) } ?: return null
|
||||
// optional: values.clear(); values.put(MediaStore.MediaColumns.IS_PENDING, 0); resolver.update(uri, values, null, null)
|
||||
uri
|
||||
} else null
|
||||
} else {
|
||||
// Android 9 und älter – public Downloads (WRITE_EXTERNAL_STORAGE kann nötig sein)
|
||||
val downloadsDir = Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DOWNLOADS)
|
||||
if (!downloadsDir.exists()) downloadsDir.mkdirs()
|
||||
val outFile = File(downloadsDir, filename)
|
||||
outFile.writeBytes(bytes)
|
||||
// im System sichtbar machen
|
||||
MediaScannerConnection.scanFile(
|
||||
activity,
|
||||
arrayOf(outFile.absolutePath),
|
||||
arrayOf(mimeType),
|
||||
null
|
||||
)
|
||||
Uri.fromFile(outFile)
|
||||
}
|
||||
}
|
||||
|
||||
// Liefert einen englischen, lesbaren Fragetext/Titel zu einer ID (nur EN, keine IDs).
|
||||
private suspend fun englishQuestionForId(id: String, questionnaireIdSet: Set<String>): String {
|
||||
if (id == "client_code") return "Client code"
|
||||
if (id in questionnaireIdSet && !id.contains('-')) return "Questionnaire status"
|
||||
|
||||
localizeEnglishNoBrackets(id)?.let { lm ->
|
||||
if (!looksLikeId(lm, id)) return lm
|
||||
}
|
||||
|
||||
val fieldPart = id.substringAfterLast('-', id)
|
||||
localizeEnglishNoBrackets(fieldPart)?.let { lm ->
|
||||
if (!looksLikeId(lm, fieldPart)) return lm
|
||||
}
|
||||
|
||||
val pretty = humanizeIdToEnglish(fieldPart)
|
||||
if (pretty.isNotBlank()) return pretty
|
||||
return "Question"
|
||||
}
|
||||
|
||||
private fun looksLikeId(text: String, originalId: String): Boolean {
|
||||
val normText = text.lowercase().replace(Regex("[^a-z0-9]+"), "_").trim('_')
|
||||
val normId = originalId.lowercase().replace(Regex("[^a-z0-9]+"), "_").trim('_')
|
||||
return normText == normId
|
||||
}
|
||||
|
||||
private fun humanizeIdToEnglish(source: String): String {
|
||||
val s = source
|
||||
.replace(Regex("^questionnaire_\\d+_"), "")
|
||||
.replace('_', ' ')
|
||||
.trim()
|
||||
if (s.isBlank()) return s
|
||||
return s.split(Regex("\\s+")).joinToString(" ") { it.lowercase().replaceFirstChar { c -> c.titlecase() } }
|
||||
}
|
||||
|
||||
// ---------------------------
|
||||
// SCREEN 2: Fragebogen-Übersicht + "header"-Liste für einen Client
|
||||
// SCREEN 2: Fragebogen-Übersicht + "header"-Liste
|
||||
// ---------------------------
|
||||
private fun openClientOverviewScreen(clientCode: String) {
|
||||
activity.setContentView(R.layout.client_overview_screen)
|
||||
|
||||
val lang = safeLang() // ✨
|
||||
|
||||
val title: TextView = requireView(R.id.titleClientOverview, "titleClientOverview")
|
||||
val tableQ: TableLayout = requireView(R.id.tableQuestionnaires, "tableQuestionnaires")
|
||||
val progress: ProgressBar = requireView(R.id.progressBarClient, "progressBarClient")
|
||||
val emptyView: TextView = requireView(R.id.emptyViewClient, "emptyViewClient")
|
||||
val backButton: Button = requireView(R.id.backButtonClient, "backButtonClient")
|
||||
|
||||
// "header" Sektion
|
||||
val headerLabel: TextView = requireView(R.id.headerLabel, "headerLabel")
|
||||
val tableOrdered: TableLayout = requireView(R.id.tableOrdered, "tableOrdered")
|
||||
|
||||
title.text = "Client: $clientCode – Fragebögen"
|
||||
headerLabel.text = "header"
|
||||
// ✨
|
||||
title.text = "${t(lang, "client") ?: "Client"}: $clientCode – ${t(lang, "questionnaires") ?: "Fragebögen"}"
|
||||
headerLabel.text = t(lang, "headers") ?: "header"
|
||||
backButton.text = t(lang, "previous") ?: "Zurück"
|
||||
backButton.setOnClickListener { openDatabaseScreen() }
|
||||
|
||||
progress.visibility = View.VISIBLE
|
||||
@ -274,8 +137,15 @@ class DatabaseButtonHandler(
|
||||
tableQ.removeAllViews()
|
||||
tableOrdered.removeAllViews()
|
||||
|
||||
addHeaderRow(tableQ, listOf("#", "Fragebogen-ID", "Status"))
|
||||
addHeaderRow(tableOrdered, listOf("#", "ID", "Wert"))
|
||||
// ✨ Tabellenköpfe lokalisieren
|
||||
addHeaderRow(
|
||||
tableQ,
|
||||
listOf("#", t(lang, "questionnaire_id") ?: "Fragebogen-ID", t(lang, "status") ?: "Status")
|
||||
)
|
||||
addHeaderRow(
|
||||
tableOrdered,
|
||||
listOf("#", t(lang, "id") ?: "ID", t(lang, "value") ?: "Wert")
|
||||
)
|
||||
|
||||
uiScope.launch {
|
||||
val result = withContext(Dispatchers.IO) {
|
||||
@ -292,7 +162,7 @@ class DatabaseButtonHandler(
|
||||
val allAnswersForClient = result.third
|
||||
|
||||
if (allQuestionnaires.isEmpty()) {
|
||||
emptyView.text = "Keine Fragebögen vorhanden."
|
||||
emptyView.text = t(lang, "no_questionnaires") ?: "Keine Fragebögen vorhanden."
|
||||
emptyView.visibility = View.VISIBLE
|
||||
}
|
||||
|
||||
@ -300,7 +170,7 @@ class DatabaseButtonHandler(
|
||||
val questionnaireIdSet = allQuestionnaires.map { it.id }.toSet()
|
||||
val answerMap = allAnswersForClient.associate { it.questionId to it.answerValue }
|
||||
|
||||
// Tabelle 1 (nur Statusfarbe als Textfarbe)
|
||||
// Tabelle 1 (Status)
|
||||
allQuestionnaires
|
||||
.sortedWith(compareBy({ extractQuestionnaireNumber(it.id) ?: Int.MAX_VALUE }, { it.id }))
|
||||
.forEachIndexed { idx, q ->
|
||||
@ -313,52 +183,44 @@ class DatabaseButtonHandler(
|
||||
table = tableQ,
|
||||
cells = listOf((idx + 1).toString(), q.id, statusText),
|
||||
onClick = { openQuestionnaireDetailScreen(clientCode, q.id) },
|
||||
colorOverrides = mapOf(2 to statusTextColor),
|
||||
cellBgOverrides = emptyMap()
|
||||
colorOverrides = mapOf(2 to statusTextColor)
|
||||
)
|
||||
} else {
|
||||
addDisabledRow(
|
||||
table = tableQ,
|
||||
cells = listOf((idx + 1).toString(), q.id, statusText),
|
||||
colorOverrides = mapOf(2 to statusTextColor),
|
||||
cellBgOverrides = emptyMap()
|
||||
colorOverrides = mapOf(2 to statusTextColor)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
// Farben (Material-like)
|
||||
val lightGreen = 0xFFC8E6C9.toInt() // beantwortet (nicht-Fragebogen)
|
||||
val lightRed = 0xFFFFCDD2.toInt() // unbeantwortet (nicht-Fragebogen)
|
||||
val doneGreen = 0xFF4CAF50.toInt() // Fragebogen: Done (BLEIBT)
|
||||
val notRed = 0xFFF44336.toInt() // Fragebogen: Not Done (BLEIBT)
|
||||
// Farben
|
||||
val lightGreen = 0xFFC8E6C9.toInt()
|
||||
val lightRed = 0xFFFFCDD2.toInt()
|
||||
val doneGreen = 0xFF4CAF50.toInt()
|
||||
val notRed = 0xFFF44336.toInt()
|
||||
|
||||
// Tabelle 2 (Header-Liste)
|
||||
val orderedIds = loadOrderedIds()
|
||||
val orderedIds = headerRepo.loadOrderedIds()
|
||||
orderedIds.forEachIndexed { idx, id ->
|
||||
var rowBgColor: Int? = null
|
||||
|
||||
val raw: String
|
||||
val cellBgForQuestionnaire: Int?
|
||||
|
||||
if (id == "client_code") {
|
||||
// client_code bleibt unmarkiert
|
||||
raw = clientCode
|
||||
cellBgForQuestionnaire = null
|
||||
} else if (id in questionnaireIdSet) {
|
||||
// Fragebogenstatus: wie bisher grün/rot NUR für #, ID, Wert
|
||||
raw = if (statusMap[id] == true) "Done" else "Not Done"
|
||||
cellBgForQuestionnaire = if (raw == "Done") doneGreen else notRed
|
||||
} else {
|
||||
// Normale Frage:
|
||||
raw = answerMap[id]?.takeIf { it.isNotBlank() } ?: "None"
|
||||
cellBgForQuestionnaire = null
|
||||
// NEU: hellgrün wenn beantwortet, hellrot wenn None
|
||||
rowBgColor = if (raw == "None") lightRed else lightGreen
|
||||
}
|
||||
|
||||
val display = localizeHeaderValue(id, raw)
|
||||
val display = localizeHeaderValue(id, raw, lang) // ✨
|
||||
|
||||
// Für Fragebögen: "#"(0), ID(1), Wert(2) farbig hinterlegen – wie gehabt
|
||||
val cellBgOverrides =
|
||||
if (cellBgForQuestionnaire != null)
|
||||
mapOf(0 to cellBgForQuestionnaire, 1 to cellBgForQuestionnaire, 2 to cellBgForQuestionnaire)
|
||||
@ -367,34 +229,39 @@ class DatabaseButtonHandler(
|
||||
addRow(
|
||||
table = tableOrdered,
|
||||
cells = listOf((idx + 1).toString(), id, display),
|
||||
colorOverrides = emptyMap(),
|
||||
rowBgColor = rowBgColor, // greift für normale Fragen (ganze Zeile inkl. #)
|
||||
cellBgOverrides = cellBgOverrides // greift für Fragebögen (nur 3 Zellen)
|
||||
rowBgColor = rowBgColor,
|
||||
cellBgOverrides = cellBgOverrides
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// ---------------------------
|
||||
// SCREEN 3: Fragen & Antworten zu einem Fragebogen
|
||||
// SCREEN 3: Fragen & Antworten eines Fragebogens
|
||||
// ---------------------------
|
||||
private fun openQuestionnaireDetailScreen(clientCode: String, questionnaireId: String) {
|
||||
activity.setContentView(R.layout.questionnaire_detail_screen)
|
||||
|
||||
val lang = safeLang() // ✨
|
||||
|
||||
val title: TextView = requireView(R.id.titleQuestionnaireDetail, "titleQuestionnaireDetail")
|
||||
val table: TableLayout = requireView(R.id.tableQA, "tableQA")
|
||||
val progress: ProgressBar = requireView(R.id.progressBarQA, "progressBarQA")
|
||||
val emptyView: TextView = requireView(R.id.emptyViewQA, "emptyViewQA")
|
||||
val backButton: Button = requireView(R.id.backButtonQA, "backButtonQA")
|
||||
|
||||
title.text = "Client: $clientCode – Fragebogen: $questionnaireId"
|
||||
// ✨ Titel + Back + Empty übersetzen
|
||||
title.text = "${t(lang, "client") ?: "Client"}: $clientCode – ${t(lang, "questionnaire") ?: "Fragebogen"}: $questionnaireId"
|
||||
backButton.text = t(lang, "previous") ?: "Zurück"
|
||||
emptyView.text = t(lang, "no_questions_available") ?: "Keine Fragen vorhanden."
|
||||
backButton.setOnClickListener { openClientOverviewScreen(clientCode) }
|
||||
|
||||
progress.visibility = View.VISIBLE
|
||||
emptyView.visibility = View.GONE
|
||||
table.removeAllViews()
|
||||
|
||||
addHeaderRow(table, listOf("#", "Frage", "Antwort"))
|
||||
// ✨ Kopf: # | Frage | Antwort
|
||||
addHeaderRow(table, listOf("#", t(lang, "question") ?: "Frage", t(lang, "answer") ?: "Antwort"))
|
||||
|
||||
uiScope.launch {
|
||||
val (questions, answersForClient) = withContext(Dispatchers.IO) {
|
||||
@ -407,7 +274,6 @@ class DatabaseButtonHandler(
|
||||
progress.visibility = View.GONE
|
||||
|
||||
if (questions.isEmpty()) {
|
||||
emptyView.text = "Keine Fragen vorhanden."
|
||||
emptyView.visibility = View.VISIBLE
|
||||
return@launch
|
||||
}
|
||||
@ -415,148 +281,68 @@ class DatabaseButtonHandler(
|
||||
val answerMap = answersForClient.associate { it.questionId to it.answerValue }
|
||||
|
||||
questions.forEachIndexed { idx, q: Question ->
|
||||
val qText = q.question.takeIf { it.isNotBlank() } ?: q.questionId
|
||||
val aText = answerMap[q.questionId]?.takeIf { it.isNotBlank() } ?: "—"
|
||||
val baseId = q.questionId.substringAfterLast('-', q.questionId) // z.B. consent_signed, gender_male
|
||||
val qText = localizeQuestionLabel(q.questionId, q.question, lang) // ✨
|
||||
val raw = answerMap[q.questionId]?.takeIf { it.isNotBlank() } ?: "—"
|
||||
val aText = localizeAnswerValue(baseId, raw, lang) // ✨
|
||||
addRow(table, listOf((idx + 1).toString(), qText, aText))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// ---------------------------
|
||||
// ordered_ids aus XLSX/JSON laden (mit Cache)
|
||||
// Lokalisierungshilfen (Anzeige)
|
||||
// ---------------------------
|
||||
private fun loadOrderedIds(): List<String> {
|
||||
orderedIdsCache?.let { return it }
|
||||
|
||||
val fromXlsx = runCatching { loadOrderedIdsFromExcel("header_order.xlsx") }
|
||||
.onFailure { e -> Log.w(tag, "header_order.xlsx konnte nicht gelesen werden: ${e.message}") }
|
||||
.getOrElse { emptyList() }
|
||||
private fun safeLang(): String = try { languageIDProvider() } catch (_: Exception) { "GERMAN" }
|
||||
|
||||
if (fromXlsx.isNotEmpty()) {
|
||||
orderedIdsCache = fromXlsx
|
||||
return fromXlsx
|
||||
}
|
||||
|
||||
return try {
|
||||
val stream = activity.assets.open("header_order.json")
|
||||
val json = stream.readBytes().toString(Charset.forName("UTF-8"))
|
||||
val arr = JSONArray(json)
|
||||
val list = MutableList(arr.length()) { i -> arr.getString(i) }
|
||||
orderedIdsCache = list
|
||||
list
|
||||
} catch (e: Exception) {
|
||||
Log.e(tag, "Weder header_order.xlsx noch header_order.json verfügbar/gültig: ${e.message}")
|
||||
Toast.makeText(activity, "Keine Header-Vorlage gefunden", Toast.LENGTH_LONG).show()
|
||||
emptyList()
|
||||
}
|
||||
private fun stripBrackets(s: String): String {
|
||||
val m = Regex("^\\[(.*)]$").matchEntire(s.trim())
|
||||
return m?.groupValues?.get(1) ?: s
|
||||
}
|
||||
|
||||
private fun loadOrderedIdsFromExcel(assetFileName: String): List<String> {
|
||||
activity.assets.open(assetFileName).use { input ->
|
||||
XSSFWorkbook(input).use { wb ->
|
||||
val sheet = wb.getSheetAt(0) ?: return emptyList()
|
||||
val row = sheet.getRow(0) ?: return emptyList()
|
||||
|
||||
val first = row.firstCellNum.toInt()
|
||||
val last = row.lastCellNum.toInt() // exklusiv
|
||||
val out = mutableListOf<String>()
|
||||
|
||||
for (i in first until last) {
|
||||
val cell = row.getCell(i) ?: continue
|
||||
val value = getCellAsString(cell).trim()
|
||||
if (value.isEmpty()) continue
|
||||
if (i == first && value == "#") continue // „#“ in Spalte 0 ignorieren
|
||||
out.add(value)
|
||||
}
|
||||
return out
|
||||
}
|
||||
}
|
||||
private fun t(lang: String, key: String): String? {
|
||||
val txt = try { LanguageManager.getText(lang, key) } catch (_: Exception) { null } ?: return null
|
||||
val out = stripBrackets(txt).trim()
|
||||
return if (out.equals(key, true) || out.isBlank()) null else out
|
||||
}
|
||||
|
||||
private fun getCellAsString(cell: org.apache.poi.ss.usermodel.Cell): String =
|
||||
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))
|
||||
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
|
||||
else -> ""
|
||||
}
|
||||
|
||||
// ---------------------------
|
||||
// Hilfsfunktionen (Lokalisierung & UI)
|
||||
// ---------------------------
|
||||
private fun extractQuestionnaireNumber(id: String): Int? {
|
||||
val m = Regex("^questionnaire_(\\d+)").find(id.lowercase())
|
||||
return m?.groupValues?.get(1)?.toIntOrNull()
|
||||
// ✨ Frage-Label für die UI
|
||||
private fun localizeQuestionLabel(questionId: String, fallbackQuestionText: String?, lang: String): String {
|
||||
val field = questionId.substringAfterLast('-', questionId)
|
||||
t(lang, field)?.let { return it } // z.B. consent_instruction
|
||||
t(lang, questionId)?.let { return it } // kompletter key
|
||||
fallbackQuestionText?.takeIf { it.isNotBlank() }?.let { return it }
|
||||
return field.replace('_', ' ').replaceFirstChar { it.titlecase() }
|
||||
}
|
||||
|
||||
// Anzeige im Header (App) – aktuelle UI-Sprache
|
||||
private fun localizeHeaderValue(id: String, raw: String): String {
|
||||
if (id == "client_code") return raw
|
||||
|
||||
val lang = try { languageIDProvider() } catch (_: Exception) { "GERMAN" }
|
||||
|
||||
fun strip(s: String): String {
|
||||
val m = Regex("^\\[(.*)]$").matchEntire(s.trim())
|
||||
return m?.groupValues?.get(1) ?: s
|
||||
}
|
||||
|
||||
fun tryKey(key: String): String? {
|
||||
val t = try { LanguageManager.getText(lang, key) } catch (_: Exception) { key }
|
||||
val out = strip(t)
|
||||
if (out.isBlank() || out.equals(key, ignoreCase = true)) return null
|
||||
return out
|
||||
}
|
||||
// ✨ Antwort-Wert für die UI
|
||||
private fun localizeAnswerValue(fieldId: String, raw: String, lang: String): String {
|
||||
// Zahlen/Datum/Strich beibehalten
|
||||
if (raw == "—") return raw
|
||||
if (raw.matches(Regex("^\\d{1,4}([./-]\\d{1,2}([./-]\\d{1,4})?)?\$"))) return raw
|
||||
|
||||
// 1) direkter Key
|
||||
t(lang, raw)?.let { return it }
|
||||
// 2) normalisiert
|
||||
val norm = raw.lowercase().replace(Regex("[^a-z0-9]+"), "_").trim('_')
|
||||
val candidates = buildList {
|
||||
when (raw) { "Done" -> add("done"); "Not Done" -> add("not_done"); "None" -> add("none") }
|
||||
add(raw)
|
||||
if (norm.isNotBlank() && norm != raw) add(norm)
|
||||
if (norm.isNotBlank()) { add("${id}_$norm"); add("${id}-$norm") }
|
||||
if (norm.isNotBlank()) t(lang, norm)?.let { return it }
|
||||
// 3) kombiniere Feld + Wert (häufiges Muster)
|
||||
if (norm.isNotBlank()) {
|
||||
t(lang, "${fieldId}_$norm")?.let { return it }
|
||||
t(lang, "${fieldId}-${norm}")?.let { return it }
|
||||
}
|
||||
|
||||
for (key in candidates) tryKey(key)?.let { return it }
|
||||
return raw
|
||||
}
|
||||
|
||||
private fun localizeEnglishNoBrackets(key: String): String? {
|
||||
val t = try { LanguageManager.getText("ENGLISH", key) } catch (_: Exception) { null }
|
||||
val m = Regex("^\\[(.*)]$").matchEntire(t?.trim() ?: "")
|
||||
val stripped = m?.groupValues?.get(1) ?: t
|
||||
if (stripped == null || stripped.isBlank() || stripped.equals(key, ignoreCase = true)) return null
|
||||
return stripped
|
||||
}
|
||||
|
||||
// Englisch für Export; belässt Done/Not Done/None.
|
||||
private fun localizeForExportEn(id: String, raw: String): String {
|
||||
if (id == "client_code") return raw
|
||||
if (raw == "Done" || raw == "Not Done" || raw == "None") return raw
|
||||
|
||||
val norm = raw.lowercase().replace(Regex("[^a-z0-9]+"), "_").trim('_')
|
||||
val candidates = buildList {
|
||||
add(raw)
|
||||
if (norm.isNotBlank() && norm != raw) add(norm)
|
||||
if (norm.isNotBlank()) { add("${id}_$norm"); add("${id}-$norm") }
|
||||
}
|
||||
for (key in candidates) localizeEnglishNoBrackets(key)?.let { return it }
|
||||
return raw
|
||||
// Klammern entfernen, falls aus LanguageManager so geliefert
|
||||
return stripBrackets(raw)
|
||||
}
|
||||
|
||||
// ---------------------------
|
||||
// UI-Helfer
|
||||
// Anzeige-Helfer
|
||||
// ---------------------------
|
||||
private fun addHeaderRow(table: TableLayout, labels: List<String>) {
|
||||
val row = TableRow(activity)
|
||||
labels.forEach { label -> row.addView(makeHeaderCell(label)) }
|
||||
table.addView(row)
|
||||
addDivider(table)
|
||||
table.addView(row); addDivider(table)
|
||||
}
|
||||
|
||||
private fun addRow(
|
||||
@ -569,11 +355,9 @@ class DatabaseButtonHandler(
|
||||
val row = TableRow(activity)
|
||||
rowBgColor?.let { row.setBackgroundColor(it) }
|
||||
cells.forEachIndexed { index, text ->
|
||||
val tv = makeBodyCell(text, colorOverrides[index], cellBgOverrides[index])
|
||||
row.addView(tv)
|
||||
val tv = makeBodyCell(text, colorOverrides[index], cellBgOverrides[index]); row.addView(tv)
|
||||
}
|
||||
table.addView(row)
|
||||
addDivider(table)
|
||||
table.addView(row); addDivider(table)
|
||||
}
|
||||
|
||||
private fun addClickableRow(
|
||||
@ -590,11 +374,9 @@ class DatabaseButtonHandler(
|
||||
setOnClickListener { onClick() }
|
||||
}
|
||||
cells.forEachIndexed { index, text ->
|
||||
val tv = makeBodyCell(text, colorOverrides[index], cellBgOverrides[index])
|
||||
row.addView(tv)
|
||||
val tv = makeBodyCell(text, colorOverrides[index], cellBgOverrides[index]); row.addView(tv)
|
||||
}
|
||||
table.addView(row)
|
||||
addDivider(table)
|
||||
table.addView(row); addDivider(table)
|
||||
}
|
||||
|
||||
private fun addDisabledRow(
|
||||
@ -603,25 +385,16 @@ class DatabaseButtonHandler(
|
||||
colorOverrides: Map<Int, Int> = emptyMap(),
|
||||
cellBgOverrides: Map<Int, Int> = emptyMap()
|
||||
) {
|
||||
val row = TableRow(activity).apply {
|
||||
isClickable = false
|
||||
isEnabled = false
|
||||
alpha = 0.6f
|
||||
}
|
||||
val row = TableRow(activity).apply { isClickable = false; isEnabled = false; alpha = 0.6f }
|
||||
cells.forEachIndexed { index, text ->
|
||||
val tv = makeBodyCell(text, colorOverrides[index], cellBgOverrides[index])
|
||||
row.addView(tv)
|
||||
val tv = makeBodyCell(text, colorOverrides[index], cellBgOverrides[index]); row.addView(tv)
|
||||
}
|
||||
table.addView(row)
|
||||
addDivider(table)
|
||||
table.addView(row); addDivider(table)
|
||||
}
|
||||
|
||||
private fun addDivider(table: TableLayout) {
|
||||
val divider = View(activity)
|
||||
val params = TableLayout.LayoutParams(
|
||||
TableLayout.LayoutParams.MATCH_PARENT,
|
||||
dp(1)
|
||||
)
|
||||
val params = TableLayout.LayoutParams(TableLayout.LayoutParams.MATCH_PARENT, dp(1))
|
||||
divider.layoutParams = params
|
||||
divider.setBackgroundColor(0xFFDDDDDD.toInt())
|
||||
table.addView(divider)
|
||||
@ -663,4 +436,25 @@ class DatabaseButtonHandler(
|
||||
}
|
||||
return v
|
||||
}
|
||||
|
||||
private fun extractQuestionnaireNumber(id: String): Int? {
|
||||
val m = Regex("^questionnaire_(\\d+)").find(id.lowercase())
|
||||
return m?.groupValues?.get(1)?.toIntOrNull()
|
||||
}
|
||||
|
||||
// ✨ alte localizeHeaderValue erweitert: nimmt jetzt lang mit
|
||||
private fun localizeHeaderValue(id: String, raw: String, lang: String): String {
|
||||
if (id == "client_code") return raw
|
||||
|
||||
fun norm(s: String) = s.lowercase().replace(Regex("[^a-z0-9]+"), "_").trim('_')
|
||||
val candidates = buildList {
|
||||
when (raw) { "Done" -> add("done"); "Not Done" -> add("not_done"); "None" -> add("none") }
|
||||
add(raw)
|
||||
val n = norm(raw)
|
||||
if (n.isNotBlank() && n != raw) add(n)
|
||||
if (n.isNotBlank()) { add("${id}_$n"); add("${id}-$n") }
|
||||
}
|
||||
for (k in candidates) t(lang, k)?.let { return it }
|
||||
return stripBrackets(raw)
|
||||
}
|
||||
}
|
||||
|
||||
163
app/src/main/java/com/dano/test1/ExcelExportService.kt
Normal file
163
app/src/main/java/com/dano/test1/ExcelExportService.kt
Normal file
@ -0,0 +1,163 @@
|
||||
package com.dano.test1
|
||||
|
||||
import android.content.ContentValues
|
||||
import android.content.Context
|
||||
import android.media.MediaScannerConnection
|
||||
import android.net.Uri
|
||||
import android.os.Build
|
||||
import android.os.Environment
|
||||
import android.provider.MediaStore
|
||||
import org.apache.poi.ss.usermodel.Row
|
||||
import org.apache.poi.xssf.usermodel.XSSFWorkbook
|
||||
|
||||
class ExcelExportService(
|
||||
private val context: Context,
|
||||
private val headerRepo: HeaderOrderRepository
|
||||
) {
|
||||
|
||||
/** Baut die Excel-Datei und speichert sie ausschließlich unter "Downloads". */
|
||||
suspend fun exportHeadersForAllClients(): Uri? {
|
||||
val orderedIds = headerRepo.loadOrderedIds()
|
||||
if (orderedIds.isEmpty()) return null
|
||||
|
||||
val clients = MyApp.database.clientDao().getAllClients()
|
||||
val questionnaires = MyApp.database.questionnaireDao().getAll()
|
||||
val questionnaireIdSet = questionnaires.map { it.id }.toSet()
|
||||
|
||||
val wb = XSSFWorkbook()
|
||||
val sheet = wb.createSheet("Headers")
|
||||
sheet.setColumnWidth(0, 8 * 256)
|
||||
for (i in 1..orderedIds.size) sheet.setColumnWidth(i, 36 * 256)
|
||||
|
||||
// Row 1: IDs
|
||||
var col = 0
|
||||
val headerRow: Row = sheet.createRow(0)
|
||||
headerRow.createCell(col++).setCellValue("#")
|
||||
orderedIds.forEach { id -> headerRow.createCell(col++).setCellValue(id) }
|
||||
|
||||
// Row 2: Questions (EN)
|
||||
val questionRow: Row = sheet.createRow(1)
|
||||
var qc = 0
|
||||
questionRow.createCell(qc++).setCellValue("Question (EN)")
|
||||
for (id in orderedIds) {
|
||||
val englishQuestion = englishQuestionForId(id, questionnaireIdSet)
|
||||
questionRow.createCell(qc++).setCellValue(englishQuestion)
|
||||
}
|
||||
|
||||
// Rows 3+: Values per client
|
||||
clients.forEachIndexed { rowIdx, client ->
|
||||
val row: Row = sheet.createRow(rowIdx + 2)
|
||||
var c = 0
|
||||
row.createCell(c++).setCellValue((rowIdx + 1).toDouble())
|
||||
|
||||
val completedForClient = MyApp.database.completedQuestionnaireDao().getAllForClient(client.clientCode)
|
||||
val statusMap = completedForClient.associate { it.questionnaireId to it.isDone }
|
||||
val answers = MyApp.database.answerDao().getAnswersForClient(client.clientCode)
|
||||
val answerMap = answers.associate { it.questionId to it.answerValue }
|
||||
|
||||
orderedIds.forEach { id ->
|
||||
val raw = when {
|
||||
id == "client_code" -> client.clientCode
|
||||
id in questionnaireIdSet -> if (statusMap[id] == true) "Done" else "Not Done"
|
||||
else -> answerMap[id]?.takeIf { it.isNotBlank() } ?: "None"
|
||||
}
|
||||
val out = localizeForExportEn(id, raw)
|
||||
row.createCell(c++).setCellValue(out)
|
||||
}
|
||||
}
|
||||
|
||||
val bytes = java.io.ByteArrayOutputStream().use { bos ->
|
||||
wb.write(bos); bos.toByteArray()
|
||||
}
|
||||
wb.close()
|
||||
|
||||
return saveToDownloads(
|
||||
filename = "ClientHeaders.xlsx",
|
||||
mimeType = "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet",
|
||||
bytes = bytes
|
||||
)
|
||||
}
|
||||
|
||||
/** Speichert Bytes nach "Downloads". */
|
||||
private fun saveToDownloads(filename: String, mimeType: String, bytes: ByteArray): Uri? {
|
||||
return if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
|
||||
val resolver = context.contentResolver
|
||||
val values = ContentValues().apply {
|
||||
put(MediaStore.MediaColumns.DISPLAY_NAME, filename)
|
||||
put(MediaStore.MediaColumns.MIME_TYPE, mimeType)
|
||||
put(MediaStore.MediaColumns.RELATIVE_PATH, Environment.DIRECTORY_DOWNLOADS)
|
||||
}
|
||||
val collection = MediaStore.Downloads.getContentUri(MediaStore.VOLUME_EXTERNAL_PRIMARY)
|
||||
val uri = resolver.insert(collection, values)
|
||||
if (uri != null) {
|
||||
resolver.openOutputStream(uri)?.use { it.write(bytes) } ?: return null
|
||||
uri
|
||||
} else null
|
||||
} else {
|
||||
val downloadsDir = Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DOWNLOADS)
|
||||
if (!downloadsDir.exists()) downloadsDir.mkdirs()
|
||||
val outFile = java.io.File(downloadsDir, filename)
|
||||
outFile.writeBytes(bytes)
|
||||
MediaScannerConnection.scanFile(
|
||||
context,
|
||||
arrayOf(outFile.absolutePath),
|
||||
arrayOf(mimeType),
|
||||
null
|
||||
)
|
||||
Uri.fromFile(outFile)
|
||||
}
|
||||
}
|
||||
|
||||
// ---------- Export-spezifische Lokalisierung (EN) ----------
|
||||
private suspend fun englishQuestionForId(id: String, questionnaireIdSet: Set<String>): String {
|
||||
if (id == "client_code") return "Client code"
|
||||
if (id in questionnaireIdSet && !id.contains('-')) return "Questionnaire status"
|
||||
|
||||
localizeEnglishNoBrackets(id)?.let { lm ->
|
||||
if (!looksLikeId(lm, id)) return lm
|
||||
}
|
||||
|
||||
val fieldPart = id.substringAfterLast('-', id)
|
||||
localizeEnglishNoBrackets(fieldPart)?.let { lm ->
|
||||
if (!looksLikeId(lm, fieldPart)) return lm
|
||||
}
|
||||
|
||||
val pretty = humanizeIdToEnglish(fieldPart)
|
||||
if (pretty.isNotBlank()) return pretty
|
||||
return "Question"
|
||||
}
|
||||
|
||||
private fun looksLikeId(text: String, originalId: String): Boolean {
|
||||
val normText = text.lowercase().replace(Regex("[^a-z0-9]+"), "_").trim('_')
|
||||
val normId = originalId.lowercase().replace(Regex("[^a-z0-9]+"), "_").trim('_')
|
||||
return normText == normId
|
||||
}
|
||||
|
||||
private fun humanizeIdToEnglish(source: String): String {
|
||||
val s = source.replace(Regex("^questionnaire_\\d+_"), "").replace('_', ' ').trim()
|
||||
if (s.isBlank()) return s
|
||||
return s.split(Regex("\\s+")).joinToString(" ") { it.lowercase().replaceFirstChar { c -> c.titlecase() } }
|
||||
}
|
||||
|
||||
private fun localizeEnglishNoBrackets(key: String): String? {
|
||||
val t = try { LanguageManager.getText("ENGLISH", key) } catch (_: Exception) { null }
|
||||
val m = Regex("^\\[(.*)]$").matchEntire(t?.trim() ?: "")
|
||||
val stripped = m?.groupValues?.get(1) ?: t
|
||||
if (stripped == null || stripped.isBlank() || stripped.equals(key, ignoreCase = true)) return null
|
||||
return stripped
|
||||
}
|
||||
|
||||
/** Englisch für Export; belässt Done/Not Done/None. */
|
||||
private fun localizeForExportEn(id: String, raw: String): String {
|
||||
if (id == "client_code") return raw
|
||||
if (raw == "Done" || raw == "Not Done" || raw == "None") return raw
|
||||
val norm = raw.lowercase().replace(Regex("[^a-z0-9]+"), "_").trim('_')
|
||||
val candidates = buildList {
|
||||
add(raw)
|
||||
if (norm.isNotBlank() && norm != raw) add(norm)
|
||||
if (norm.isNotBlank()) { add("${id}_$norm"); add("${id}-$norm") }
|
||||
}
|
||||
for (key in candidates) localizeEnglishNoBrackets(key)?.let { return it }
|
||||
return raw
|
||||
}
|
||||
}
|
||||
@ -210,6 +210,7 @@ class HandlerOpeningScreen(private val activity: MainActivity) {
|
||||
}
|
||||
|
||||
private fun updateButtonTexts() {
|
||||
// --- dynamische Fragebogen-Buttons wie gehabt ---
|
||||
questionnaireFiles.forEach { (button, fileName) ->
|
||||
val entry = questionnaireEntries.firstOrNull { it.file == fileName }
|
||||
val key = fileName.substringAfter("questionnaire_").substringAfter("_").removeSuffix(".json")
|
||||
@ -220,23 +221,32 @@ class HandlerOpeningScreen(private val activity: MainActivity) {
|
||||
if (entry?.showPoints == true && pointsAvailable != null) {
|
||||
buttonText += " (${points} P)"
|
||||
}
|
||||
|
||||
button.text = buttonText
|
||||
|
||||
if (entry?.showPoints == true && pointsAvailable != null) {
|
||||
when {
|
||||
points in 0..12 -> button.setBackgroundColor(Color.parseColor("#4CAF50"))
|
||||
points in 0..12 -> button.setBackgroundColor(Color.parseColor("#4CAF50"))
|
||||
points in 13..36 -> button.setBackgroundColor(Color.parseColor("#FFEB3B"))
|
||||
points in 37..100 -> button.setBackgroundColor(Color.parseColor("#F44336"))
|
||||
else -> button.setBackgroundColor(Color.parseColor("#E0E0E0"))
|
||||
points in 37..100-> button.setBackgroundColor(Color.parseColor("#F44336"))
|
||||
else -> button.setBackgroundColor(Color.parseColor("#E0E0E0"))
|
||||
}
|
||||
} else {
|
||||
button.setBackgroundColor(Color.parseColor("#E0E0E0"))
|
||||
}
|
||||
}
|
||||
|
||||
buttonLoad.text = LanguageManager.getText(languageID, "load")
|
||||
databaseButton.text = "Datenbank" // fixierter Text gewünscht
|
||||
// --- HIER: alle Hauptbuttons nach Sprache neu setzen ---
|
||||
buttonLoad.text = LanguageManager.getText(languageID, "load")
|
||||
saveButton.text = LanguageManager.getText(languageID, "save")
|
||||
editButton.text = LanguageManager.getText(languageID, "edit")
|
||||
uploadButton.text = LanguageManager.getText(languageID, "upload")
|
||||
downloadButton.text= LanguageManager.getText(languageID, "download")
|
||||
databaseButton.text= LanguageManager.getText(languageID, "database")
|
||||
|
||||
// optional: Beispieltext/Hints auch aktualisieren
|
||||
val hintTag = editText.tag as? String ?: ""
|
||||
editText.hint = LanguageManager.getText(languageID, hintTag)
|
||||
textView.text = LanguageManager.getText(languageID, "example_text")
|
||||
}
|
||||
|
||||
private fun setButtonsEnabled(enabledButtons: List<Button>) {
|
||||
|
||||
75
app/src/main/java/com/dano/test1/HeaderOrderRepository.kt
Normal file
75
app/src/main/java/com/dano/test1/HeaderOrderRepository.kt
Normal file
@ -0,0 +1,75 @@
|
||||
package com.dano.test1
|
||||
|
||||
import android.content.Context
|
||||
import android.util.Log
|
||||
import android.widget.Toast
|
||||
import org.apache.poi.xssf.usermodel.XSSFWorkbook
|
||||
import org.json.JSONArray
|
||||
import java.nio.charset.Charset
|
||||
|
||||
class HeaderOrderRepository(private val context: Context) {
|
||||
|
||||
private val tag = "HeaderOrderRepository"
|
||||
private var orderedIdsCache: List<String>? = null
|
||||
|
||||
fun loadOrderedIds(): List<String> {
|
||||
orderedIdsCache?.let { return it }
|
||||
|
||||
val fromXlsx = runCatching { loadOrderedIdsFromExcel("header_order.xlsx") }
|
||||
.onFailure { e -> Log.w(tag, "header_order.xlsx konnte nicht gelesen werden: ${e.message}") }
|
||||
.getOrElse { emptyList() }
|
||||
|
||||
if (fromXlsx.isNotEmpty()) {
|
||||
orderedIdsCache = fromXlsx
|
||||
return fromXlsx
|
||||
}
|
||||
|
||||
return try {
|
||||
val stream = context.assets.open("header_order.json")
|
||||
val json = stream.readBytes().toString(Charset.forName("UTF-8"))
|
||||
val arr = JSONArray(json)
|
||||
val list = MutableList(arr.length()) { i -> arr.getString(i) }
|
||||
orderedIdsCache = list
|
||||
list
|
||||
} catch (e: Exception) {
|
||||
Log.e(tag, "Weder header_order.xlsx noch header_order.json verfügbar/gültig: ${e.message}")
|
||||
Toast.makeText(context, "Keine Header-Vorlage gefunden", Toast.LENGTH_LONG).show()
|
||||
emptyList()
|
||||
}
|
||||
}
|
||||
|
||||
private fun loadOrderedIdsFromExcel(assetFileName: String): List<String> {
|
||||
context.assets.open(assetFileName).use { input ->
|
||||
XSSFWorkbook(input).use { wb ->
|
||||
val sheet = wb.getSheetAt(0) ?: return emptyList()
|
||||
val row = sheet.getRow(0) ?: return emptyList()
|
||||
|
||||
val first = row.firstCellNum.toInt()
|
||||
val last = row.lastCellNum.toInt() // exklusiv
|
||||
val out = mutableListOf<String>()
|
||||
|
||||
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))
|
||||
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
|
||||
else -> ""
|
||||
}.trim()
|
||||
|
||||
if (value.isEmpty()) continue
|
||||
if (i == first && value == "#") continue // „#“ in Spalte 0 ignorieren
|
||||
out.add(value)
|
||||
}
|
||||
return out
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -328,7 +328,33 @@ object LanguageManager {
|
||||
"no_profile" to "Dieser Klient ist noch nicht Teil der Datenbank",
|
||||
"questionnaires_finished" to "Für diesen Klienten wurden alle Fragebögen ausgefüllt!",
|
||||
"edit" to "Bearbeiten",
|
||||
"save" to "Speichern"
|
||||
"save" to "Speichern",
|
||||
"upload" to "Hochladen",
|
||||
"download" to "Herunterladen",
|
||||
"database" to "Datenbank",
|
||||
"clients" to "Clients",
|
||||
"client" to "Client",
|
||||
"client_code_label" to "Client-Code",
|
||||
"questionnaires" to "Fragebögen",
|
||||
"questionnaire" to "Fragebogen",
|
||||
"questionnaire_id" to "Fragebogen-ID",
|
||||
"status" to "Status",
|
||||
"header_label" to "Header",
|
||||
"id" to "ID",
|
||||
"value" to "Wert",
|
||||
"no_clients" to "Keine Clients vorhanden.",
|
||||
"no_questionnaires" to "Keine Fragebögen vorhanden.",
|
||||
"no_questions" to "Keine Fragen vorhanden.",
|
||||
"question" to "Frage",
|
||||
"answer" to "Antwort",
|
||||
"download_header" to "Download Header",
|
||||
"back" to "Zurück",
|
||||
"export_success_downloads_headers" to "Export erfolgreich: Downloads/ClientHeaders.xlsx",
|
||||
"export_failed" to "Export fehlgeschlagen.",
|
||||
"error_generic" to "Fehler",
|
||||
"done" to "abgeschlossen",
|
||||
"not_done" to "nicht abgeschlossen",
|
||||
"none" to "keine Angabe",
|
||||
|
||||
),
|
||||
|
||||
@ -625,7 +651,33 @@ object LanguageManager {
|
||||
"no_profile" to "This client is not yet part of the database",
|
||||
"questionnaires_finished" to "All questionnaires have been completed for this client!",
|
||||
"edit" to "Bearbeiten",
|
||||
"save" to "Speichern"
|
||||
"save" to "Speichern",
|
||||
"upload" to "Upload",
|
||||
"download" to "Download",
|
||||
"database" to "Database",
|
||||
"clients" to "Clients",
|
||||
"client" to "Client",
|
||||
"client_code_label" to "Client Code",
|
||||
"questionnaires" to "Questionnaires",
|
||||
"questionnaire" to "Questionnaire",
|
||||
"questionnaire_id" to "Questionnaire ID",
|
||||
"status" to "Status",
|
||||
"header_label" to "Header",
|
||||
"id" to "ID",
|
||||
"value" to "Value",
|
||||
"no_clients" to "No clients available.",
|
||||
"no_questionnaires" to "No questionnaires available.",
|
||||
"no_questions" to "No questions available.",
|
||||
"question" to "Question",
|
||||
"answer" to "Answer",
|
||||
"download_header" to "Download header",
|
||||
"back" to "Back",
|
||||
"export_success_downloads_headers" to "Export successful: Downloads/ClientHeaders.xlsx",
|
||||
"export_failed" to "Export failed.",
|
||||
"error_generic" to "Error",
|
||||
"done" to "Done",
|
||||
"not_done" to "Not Done",
|
||||
"none" to "None",
|
||||
),
|
||||
|
||||
"FRENCH" to mapOf(
|
||||
@ -925,7 +977,33 @@ object LanguageManager {
|
||||
"no_profile" to "Ce client ne fait pas encore partie de la base de données",
|
||||
"questionnaires_finished" to "Tous les questionnaires ont été complétés pour ce client !",
|
||||
"edit" to "Modifier",
|
||||
"save" to "Sauvegarder"
|
||||
"save" to "Sauvegarder",
|
||||
"upload" to "Téléverser",
|
||||
"download" to "Télécharger",
|
||||
"database" to "Base de données",
|
||||
"clients" to "Clients",
|
||||
"client" to "Client",
|
||||
"client_code_label" to "Code client",
|
||||
"questionnaires" to "Questionnaires",
|
||||
"questionnaire" to "Questionnaire",
|
||||
"questionnaire_id" to "ID du questionnaire",
|
||||
"status" to "Statut",
|
||||
"header_label" to "En-tête",
|
||||
"id" to "ID",
|
||||
"value" to "Valeur",
|
||||
"no_clients" to "Aucun client disponible.",
|
||||
"no_questionnaires" to "Aucun questionnaire disponible.",
|
||||
"no_questions" to "Aucune question disponible.",
|
||||
"question" to "Question",
|
||||
"answer" to "Réponse",
|
||||
"download_header" to "Télécharger l’en-tête",
|
||||
"back" to "Retour",
|
||||
"export_success_downloads_headers" to "Export réussi : Downloads/ClientHeaders.xlsx",
|
||||
"export_failed" to "Échec de l’exportation.",
|
||||
"error_generic" to "Erreur",
|
||||
"done" to "terminé",
|
||||
"not_done" to "non terminé",
|
||||
"none" to "aucune réponse",
|
||||
),
|
||||
|
||||
"RUSSIAN" to mapOf(
|
||||
@ -1221,7 +1299,33 @@ object LanguageManager {
|
||||
"no_profile" to "Этот клиент еще не внесен в базу данных",
|
||||
"questionnaires_finished" to "Для этого клиента все анкеты заполнены!",
|
||||
"edit" to "Редактировать",
|
||||
"save" to "Сохранять"
|
||||
"save" to "Сохранять",
|
||||
"upload" to "Загрузить",
|
||||
"download" to "Скачать",
|
||||
"database" to "База данных",
|
||||
"clients" to "Клиенты",
|
||||
"client" to "Клиент",
|
||||
"client_code_label" to "Код клиента",
|
||||
"questionnaires" to "Анкеты",
|
||||
"questionnaire" to "Анкета",
|
||||
"questionnaire_id" to "ID анкеты",
|
||||
"status" to "Статус",
|
||||
"header_label" to "Заголовки",
|
||||
"id" to "ID",
|
||||
"value" to "Значение",
|
||||
"no_clients" to "Клиентов нет.",
|
||||
"no_questionnaires" to "Нет анкет.",
|
||||
"no_questions" to "Нет вопросов.",
|
||||
"question" to "Вопрос",
|
||||
"answer" to "Ответ",
|
||||
"download_header" to "Скачать заголовки",
|
||||
"back" to "Назад",
|
||||
"export_success_downloads_headers" to "Экспорт выполнен: Downloads/ClientHeaders.xlsx",
|
||||
"export_failed" to "Экспорт не выполнен.",
|
||||
"error_generic" to "Ошибка",
|
||||
"done" to "выполнено",
|
||||
"not_done" to "не выполнено",
|
||||
"none" to "нет ответа",
|
||||
),
|
||||
|
||||
"UKRAINIAN" to mapOf(
|
||||
@ -1521,7 +1625,33 @@ object LanguageManager {
|
||||
"no_profile" to "Цей клієнт ще не внесений до бази даних",
|
||||
"questionnaires_finished" to "Для цього клієнта всі опитувальники заповнені!",
|
||||
"edit" to "Редагувати",
|
||||
"save" to "Зберегти"
|
||||
"save" to "Зберегти",
|
||||
"upload" to "Вивантажити",
|
||||
"download" to "Завантажити",
|
||||
"database" to "База даних",
|
||||
"clients" to "Клієнти",
|
||||
"client" to "Клієнт",
|
||||
"client_code_label" to "Код клієнта",
|
||||
"questionnaires" to "Анкети",
|
||||
"questionnaire" to "Анкета",
|
||||
"questionnaire_id" to "ID анкети",
|
||||
"status" to "Статус",
|
||||
"header_label" to "Заголовки",
|
||||
"id" to "ID",
|
||||
"value" to "Значення",
|
||||
"no_clients" to "Немає клієнтів.",
|
||||
"no_questionnaires" to "Немає анкет.",
|
||||
"no_questions" to "Немає запитань.",
|
||||
"question" to "Питання",
|
||||
"answer" to "Відповідь",
|
||||
"download_header" to "Завантажити заголовки",
|
||||
"back" to "Назад",
|
||||
"export_success_downloads_headers" to "Експорт успішний: Downloads/ClientHeaders.xlsx",
|
||||
"export_failed" to "Помилка експорту.",
|
||||
"error_generic" to "Помилка",
|
||||
"done" to "завершено",
|
||||
"not_done" to "не завершено",
|
||||
"none" to "немає відповіді",
|
||||
),
|
||||
|
||||
"TURKISH" to mapOf(
|
||||
@ -1821,7 +1951,33 @@ object LanguageManager {
|
||||
"no_profile" to "Bu danışan henüz veritabanında kayıtlı değil",
|
||||
"questionnaires_finished" to "Bu danışan için tüm anketler doldurulmuştur!",
|
||||
"edit" to "Düzenlemek",
|
||||
"save" to "Kaydetmek"
|
||||
"save" to "Kaydetmek",
|
||||
"upload" to "Yükle",
|
||||
"download" to "İndir",
|
||||
"database" to "Veritabanı",
|
||||
"clients" to "Müşteriler",
|
||||
"client" to "Müşteri",
|
||||
"client_code_label" to "Müşteri Kodu",
|
||||
"questionnaires" to "Anketler",
|
||||
"questionnaire" to "Anket",
|
||||
"questionnaire_id" to "Anket Kimliği",
|
||||
"status" to "Durum",
|
||||
"header_label" to "Başlıklar",
|
||||
"id" to "ID",
|
||||
"value" to "Değer",
|
||||
"no_clients" to "Müşteri yok.",
|
||||
"no_questionnaires" to "Anket yok.",
|
||||
"no_questions" to "Soru yok.",
|
||||
"question" to "Soru",
|
||||
"answer" to "Cevap",
|
||||
"download_header" to "Başlıkları indir",
|
||||
"back" to "Geri",
|
||||
"export_success_downloads_headers" to "Dışa aktarma başarılı: Downloads/ClientHeaders.xlsx",
|
||||
"export_failed" to "Dışa aktarma başarısız.",
|
||||
"error_generic" to "Hata",
|
||||
"done" to "tamamlandı",
|
||||
"not_done" to "tamamlanmadı",
|
||||
"none" to "yanıt yok",
|
||||
),
|
||||
|
||||
"POLISH" to mapOf(
|
||||
@ -2121,7 +2277,33 @@ object LanguageManager {
|
||||
"no_profile" to "Ten klient nie jest jeszcze częścią bazy danych",
|
||||
"questionnaires_finished" to "Dla tego klienta wypełniono wszystkie kwestionariusze!",
|
||||
"edit" to "Redagować",
|
||||
"save" to "Ratować"
|
||||
"save" to "Ratować",
|
||||
"upload" to "Prześlij",
|
||||
"download" to "Pobierz",
|
||||
"database" to "Baza danych",
|
||||
"clients" to "Klienci",
|
||||
"client" to "Klient",
|
||||
"client_code_label" to "Kod klienta",
|
||||
"questionnaires" to "Kwestionariusze",
|
||||
"questionnaire" to "Kwestionariusz",
|
||||
"questionnaire_id" to "ID kwestionariusza",
|
||||
"status" to "Status",
|
||||
"header_label" to "Nagłówek",
|
||||
"id" to "ID",
|
||||
"value" to "Wartość",
|
||||
"no_clients" to "Brak klientów.",
|
||||
"no_questionnaires" to "Brak kwestionariuszy.",
|
||||
"no_questions" to "Brak pytań.",
|
||||
"question" to "Pytanie",
|
||||
"answer" to "Odpowiedź",
|
||||
"download_header" to "Pobierz nagłówki",
|
||||
"back" to "Wstecz",
|
||||
"export_success_downloads_headers" to "Eksport zakończony: Downloads/ClientHeaders.xlsx",
|
||||
"export_failed" to "Eksport nieudany.",
|
||||
"error_generic" to "Błąd",
|
||||
"done" to "zakończono",
|
||||
"not_done" to "nie zakończono",
|
||||
"none" to "brak odpowiedzi",
|
||||
),
|
||||
|
||||
"ARABIC" to mapOf(
|
||||
@ -2421,7 +2603,33 @@ object LanguageManager {
|
||||
"no_profile" to "هذا العميل غير موجود في قاعدة البيانات بعد",
|
||||
"questionnaires_finished" to "تم الانتهاء من جميع الاستبيانات لهذا العميل!",
|
||||
"edit" to "يحرر",
|
||||
"save" to "يحفظ"
|
||||
"save" to "يحفظ",
|
||||
"upload" to "رفع",
|
||||
"download" to "تنزيل",
|
||||
"database" to "قاعدة البيانات",
|
||||
"clients" to "العملاء",
|
||||
"client" to "العميل",
|
||||
"client_code_label" to "رمز العميل",
|
||||
"questionnaires" to "الاستبيانات",
|
||||
"questionnaire" to "استبيان",
|
||||
"questionnaire_id" to "معرف الاستبيان",
|
||||
"status" to "الحالة",
|
||||
"header_label" to "رؤوس",
|
||||
"id" to "المعرّف",
|
||||
"value" to "القيمة",
|
||||
"no_clients" to "لا يوجد عملاء.",
|
||||
"no_questionnaires" to "لا توجد استبيانات.",
|
||||
"no_questions" to "لا توجد أسئلة.",
|
||||
"question" to "سؤال",
|
||||
"answer" to "إجابة",
|
||||
"download_header" to "تنزيل الرؤوس",
|
||||
"back" to "رجوع",
|
||||
"export_success_downloads_headers" to "تم التصدير بنجاح: Downloads/ClientHeaders.xlsx",
|
||||
"export_failed" to "فشل التصدير.",
|
||||
"error_generic" to "خطأ",
|
||||
"done" to "مكتمل",
|
||||
"not_done" to "غير مكتمل",
|
||||
"none" to "لا توجد إجابة",
|
||||
),
|
||||
|
||||
"ROMANIAN" to mapOf(
|
||||
@ -2721,7 +2929,33 @@ object LanguageManager {
|
||||
"no_profile" to "Acest client nu face încă parte din baza de date",
|
||||
"questionnaires_finished" to "Toate chestionarele pentru acest client au fost completate!",
|
||||
"edit" to "Editizel",
|
||||
"save" to "Xastral"
|
||||
"save" to "Xastral",
|
||||
"upload" to "Încarcă",
|
||||
"download" to "Descarcă",
|
||||
"database" to "Bază de date",
|
||||
"clients" to "Clienți",
|
||||
"client" to "Client",
|
||||
"client_code_label" to "Cod client",
|
||||
"questionnaires" to "Chestionare",
|
||||
"questionnaire" to "Chestionar",
|
||||
"questionnaire_id" to "ID chestionar",
|
||||
"status" to "Stare",
|
||||
"header_label" to "Antet",
|
||||
"id" to "ID",
|
||||
"value" to "Valoare",
|
||||
"no_clients" to "Nu există clienți.",
|
||||
"no_questionnaires" to "Nu există chestionare.",
|
||||
"no_questions" to "Nu există întrebări.",
|
||||
"question" to "Întrebare",
|
||||
"answer" to "Răspuns",
|
||||
"download_header" to "Descarcă antetele",
|
||||
"back" to "Înapoi",
|
||||
"export_success_downloads_headers" to "Export reușit: Downloads/ClientHeaders.xlsx",
|
||||
"export_failed" to "Export nereușit.",
|
||||
"error_generic" to "Eroare",
|
||||
"done" to "finalizat",
|
||||
"not_done" to "nefinalizat",
|
||||
"none" to "niciun răspuns",
|
||||
),
|
||||
|
||||
"SPANISH" to mapOf(
|
||||
@ -3021,7 +3255,33 @@ object LanguageManager {
|
||||
"no_profile" to "Este cliente aún no forma parte de la base de datos",
|
||||
"questionnaires_finished" to "Se han completado todos los cuestionarios para este cliente.",
|
||||
"edit" to "Editar",
|
||||
"save" to "Ahorrar"
|
||||
"save" to "Ahorrar",
|
||||
"upload" to "Subir",
|
||||
"download" to "Descargar",
|
||||
"database" to "Base de datos",
|
||||
"clients" to "Clientes",
|
||||
"client" to "Cliente",
|
||||
"client_code_label" to "Código de cliente",
|
||||
"questionnaires" to "Cuestionarios",
|
||||
"questionnaire" to "Cuestionario",
|
||||
"questionnaire_id" to "ID del cuestionario",
|
||||
"status" to "Estado",
|
||||
"header_label" to "Encabezados",
|
||||
"id" to "ID",
|
||||
"value" to "Valor",
|
||||
"no_clients" to "No hay clientes.",
|
||||
"no_questionnaires" to "No hay cuestionarios.",
|
||||
"no_questions" to "No hay preguntas.",
|
||||
"question" to "Pregunta",
|
||||
"answer" to "Respuesta",
|
||||
"download_header" to "Descargar encabezados",
|
||||
"back" to "Atrás",
|
||||
"export_success_downloads_headers" to "Exportación correcta: Downloads/ClientHeaders.xlsx",
|
||||
"export_failed" to "Fallo en la exportación.",
|
||||
"error_generic" to "Error",
|
||||
"done" to "completado",
|
||||
"not_done" to "no completado",
|
||||
"none" to "sin respuesta",
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user