added username and password for up- and download
This commit is contained in:
@ -26,10 +26,14 @@ object DatabaseUploader {
|
||||
|
||||
private val client = OkHttpClient()
|
||||
|
||||
fun uploadDatabaseWithLogin(context: Context, password: String) {
|
||||
LoginManager.loginUser(context, password,
|
||||
/** NEU: Login mit Username+Password, danach Upload wie gehabt */
|
||||
fun uploadDatabaseWithLogin(context: Context, username: String, password: String) {
|
||||
LoginManager.loginUserWithCredentials(
|
||||
context = context,
|
||||
username = username,
|
||||
password = password,
|
||||
onSuccess = { token ->
|
||||
Log.d("UPLOAD", "Login OK")
|
||||
Log.d("UPLOAD", "Login OK (user=$username)")
|
||||
uploadDatabase(context, token)
|
||||
},
|
||||
onError = { msg -> Log.e("UPLOAD", "Login fehlgeschlagen: $msg") }
|
||||
@ -47,13 +51,12 @@ object DatabaseUploader {
|
||||
|
||||
try {
|
||||
val db = SQLiteDatabase.openDatabase(dbFile.absolutePath, null, SQLiteDatabase.OPEN_READWRITE)
|
||||
db.rawQuery("PRAGMA wal_checkpoint(FULL);", null).use { /* ignore */ }
|
||||
db.rawQuery("PRAGMA wal_checkpoint(FULL);", null).use { /* noop */ }
|
||||
db.close()
|
||||
} catch (_: Exception) { }
|
||||
|
||||
checkDatabaseExists() // nur Logging
|
||||
checkDatabaseExists()
|
||||
uploadPseudoDelta(context, dbFile, token)
|
||||
|
||||
} catch (e: Exception) {
|
||||
Log.e("UPLOAD", "Fehler", e)
|
||||
}
|
||||
@ -99,7 +102,7 @@ object DatabaseUploader {
|
||||
.addFormDataPart("file", "payload.enc", tmpEnc.asRequestBody("application/octet-stream".toMediaType()))
|
||||
.build()
|
||||
|
||||
val request = Request.Builder().url(SERVER_DELTA_URL).post(body).build()
|
||||
val request = Request.Builder().url("http://49.13.157.44/uploadDeltaTest5.php").post(body).build()
|
||||
client.newCall(request).enqueue(object : Callback {
|
||||
override fun onFailure(call: Call, e: IOException) {
|
||||
Log.e("UPLOAD", "Fehlgeschlagen: ${e.message}")
|
||||
@ -109,8 +112,17 @@ object DatabaseUploader {
|
||||
val respBody = try { response.body?.string() ?: "" } catch (_: Exception) { "" }
|
||||
if (response.isSuccessful) {
|
||||
Log.d("UPLOAD", "OK: $respBody")
|
||||
if (!file.delete()) Log.w("UPLOAD", "Lokale DB nicht gelöscht.")
|
||||
File(file.parent, file.name + "-journal").delete()
|
||||
|
||||
// <<< alte Logik wieder aktivieren: lokale DB + Nebendateien löschen
|
||||
try {
|
||||
if (!file.delete()) Log.w("UPLOAD", "Lokale DB nicht gelöscht.")
|
||||
File(file.parent, "${file.name}-journal").delete()
|
||||
File(file.parent, "${file.name}-wal").delete()
|
||||
File(file.parent, "${file.name}-shm").delete()
|
||||
} catch (e: Exception) {
|
||||
Log.w("UPLOAD", "Fehler beim Löschen lokaler DB-Dateien", e)
|
||||
}
|
||||
// >>>
|
||||
} else {
|
||||
Log.e("UPLOAD", "HTTP ${response.code}: $respBody")
|
||||
}
|
||||
|
||||
@ -60,16 +60,13 @@ class HandlerGlassScaleQuestion(
|
||||
setTextSizePercentOfScreenHeight(titleTv, 0.03f)
|
||||
setTextSizePercentOfScreenHeight(questionTv, 0.03f)
|
||||
|
||||
// --- FIXE ICON-LEISTE AUFBAUEN (bleibt stehen) ---
|
||||
// ----- feste Icon-Leiste -----
|
||||
val header = layout.findViewById<LinearLayout>(R.id.glass_header)
|
||||
header.removeAllViews()
|
||||
|
||||
// linker Platzhalter (entspricht der Symptom-Spalte mit weight=4)
|
||||
header.addView(Space(context).apply {
|
||||
layoutParams = LinearLayout.LayoutParams(0, LinearLayout.LayoutParams.WRAP_CONTENT, 4f)
|
||||
})
|
||||
|
||||
val iconSizePx = (context.resources.displayMetrics.density * 36).toInt() // ~36dp
|
||||
val iconSizePx = (context.resources.displayMetrics.density * 36).toInt()
|
||||
scaleLabels.forEach { labelKey ->
|
||||
val cell = FrameLayout(context).apply {
|
||||
layoutParams = LinearLayout.LayoutParams(0, LinearLayout.LayoutParams.WRAP_CONTENT, 1f)
|
||||
@ -83,51 +80,44 @@ class HandlerGlassScaleQuestion(
|
||||
cell.addView(img)
|
||||
header.addView(cell)
|
||||
}
|
||||
// ---------------------------------------------------
|
||||
// -----------------------------
|
||||
|
||||
val tableLayout = layout.findViewById<TableLayout>(R.id.glass_table)
|
||||
tableLayout.removeAllViews()
|
||||
|
||||
addSymptomRows(tableLayout)
|
||||
|
||||
// ggf. Antworten aus DB wiederherstellen
|
||||
val anySymptomNeedsRestore = question.symptoms.any { !answers.containsKey(it) }
|
||||
if (anySymptomNeedsRestore) {
|
||||
CoroutineScope(Dispatchers.IO).launch {
|
||||
try {
|
||||
val clientCode = GlobalValues.LAST_CLIENT_CODE
|
||||
if (clientCode.isNullOrBlank()) return@launch
|
||||
|
||||
val clientCode = GlobalValues.LAST_CLIENT_CODE ?: return@launch
|
||||
val allAnswersForClient = MyApp.database.answerDao().getAnswersForClient(clientCode)
|
||||
val answerMap = allAnswersForClient.associateBy({ it.questionId }, { it.answerValue })
|
||||
|
||||
withContext(Dispatchers.Main) {
|
||||
val table = tableLayout
|
||||
for ((index, symptomKey) in question.symptoms.withIndex()) {
|
||||
val answerMapKey = questionnaireMeta + "-" + symptomKey
|
||||
val answerMapKey = "$questionnaireMeta-$symptomKey"
|
||||
val dbAnswer = answerMap[answerMapKey]?.takeIf { it.isNotBlank() }?.trim()
|
||||
if (!answers.containsKey(symptomKey) && !dbAnswer.isNullOrBlank()) {
|
||||
val rowIndex = index // erste Datenzeile ist Index 1 im TableLayout-Aufbau unten
|
||||
if (rowIndex < table.childCount) {
|
||||
val row = table.getChildAt(rowIndex) as? TableRow ?: continue
|
||||
if (index < table.childCount) {
|
||||
val row = table.getChildAt(index) as? TableRow ?: continue
|
||||
val radioGroup = row.getChildAt(1) as? RadioGroup ?: continue
|
||||
for (i in 0 until radioGroup.childCount) {
|
||||
val cell = radioGroup.getChildAt(i) as? FrameLayout ?: continue
|
||||
val rb = cell.getChildAt(0) as? RadioButton ?: continue
|
||||
val rb = getRadioFromChild(radioGroup.getChildAt(i)) ?: continue
|
||||
if ((rb.tag as? String)?.trim() == dbAnswer) {
|
||||
rb.isChecked = true
|
||||
break
|
||||
}
|
||||
}
|
||||
answers[symptomKey] = dbAnswer
|
||||
val point = pointsMap[dbAnswer] ?: 0
|
||||
points.add(point)
|
||||
points.add(pointsMap[dbAnswer] ?: 0)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
} catch (e: Exception) {
|
||||
e.printStackTrace()
|
||||
}
|
||||
} catch (_: Exception) { /* ignore */ }
|
||||
}
|
||||
}
|
||||
|
||||
@ -166,25 +156,21 @@ class HandlerGlassScaleQuestion(
|
||||
layoutParams = TableRow.LayoutParams(0, TableRow.LayoutParams.WRAP_CONTENT, 5f)
|
||||
}
|
||||
|
||||
// WICHTIG: RadioButtons sind direkte Kinder des RadioGroup!
|
||||
scaleLabels.forEach { labelKey ->
|
||||
val cell = FrameLayout(context).apply {
|
||||
layoutParams = RadioGroup.LayoutParams(0, RadioGroup.LayoutParams.WRAP_CONTENT, 1f)
|
||||
}
|
||||
val radioButton = RadioButton(context).apply {
|
||||
val rb = RadioButton(context).apply {
|
||||
tag = labelKey
|
||||
id = View.generateViewId()
|
||||
isChecked = savedLabel == labelKey
|
||||
layoutParams = FrameLayout.LayoutParams(
|
||||
FrameLayout.LayoutParams.WRAP_CONTENT,
|
||||
FrameLayout.LayoutParams.WRAP_CONTENT,
|
||||
Gravity.CENTER
|
||||
)
|
||||
gravity = Gravity.CENTER
|
||||
setPadding(0, 0, 0, 0)
|
||||
}
|
||||
cell.addView(radioButton)
|
||||
radioGroup.addView(cell)
|
||||
val lp = RadioGroup.LayoutParams(
|
||||
0, RadioGroup.LayoutParams.WRAP_CONTENT, 1f
|
||||
).apply { gravity = Gravity.CENTER }
|
||||
rb.layoutParams = lp
|
||||
radioGroup.addView(rb)
|
||||
}
|
||||
|
||||
row.addView(radioGroup)
|
||||
table.addView(row)
|
||||
}
|
||||
@ -197,8 +183,7 @@ class HandlerGlassScaleQuestion(
|
||||
val radioGroup = row.getChildAt(1) as RadioGroup
|
||||
var anyChecked = false
|
||||
for (j in 0 until radioGroup.childCount) {
|
||||
val cell = radioGroup.getChildAt(j) as FrameLayout
|
||||
val rb = cell.getChildAt(0) as RadioButton
|
||||
val rb = getRadioFromChild(radioGroup.getChildAt(j)) ?: continue
|
||||
if (rb.isChecked) { anyChecked = true; break }
|
||||
}
|
||||
if (!anyChecked) return false
|
||||
@ -207,9 +192,10 @@ class HandlerGlassScaleQuestion(
|
||||
}
|
||||
|
||||
override fun saveAnswer() {
|
||||
// alte Punkte entfernen
|
||||
question.symptoms.forEach { key ->
|
||||
val previousLabel = answers[key] as? String
|
||||
previousLabel?.let { lbl -> pointsMap[lbl] }?.let { points.remove(it) }
|
||||
val prev = answers[key] as? String
|
||||
prev?.let { pointsMap[it] }?.let { points.remove(it) }
|
||||
}
|
||||
|
||||
val table = layout.findViewById<TableLayout>(R.id.glass_table)
|
||||
@ -218,18 +204,25 @@ class HandlerGlassScaleQuestion(
|
||||
val symptomKey = question.symptoms[i]
|
||||
val radioGroup = row.getChildAt(1) as RadioGroup
|
||||
for (j in 0 until radioGroup.childCount) {
|
||||
val cell = radioGroup.getChildAt(j) as FrameLayout
|
||||
val rb = cell.getChildAt(0) as RadioButton
|
||||
val rb = getRadioFromChild(radioGroup.getChildAt(j)) ?: continue
|
||||
if (rb.isChecked) {
|
||||
val selectedLabel = rb.tag as String
|
||||
answers[symptomKey] = selectedLabel
|
||||
points.add(pointsMap[selectedLabel] ?: 0)
|
||||
val selected = rb.tag as String
|
||||
answers[symptomKey] = selected
|
||||
points.add(pointsMap[selected] ?: 0)
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// --- Helpers ---
|
||||
private fun getRadioFromChild(child: View): RadioButton? =
|
||||
when (child) {
|
||||
is RadioButton -> child
|
||||
is FrameLayout -> child.getChildAt(0) as? RadioButton
|
||||
else -> null
|
||||
}
|
||||
|
||||
private fun setTextSizePercentOfScreenHeight(view: TextView, percentOfHeight: Float) {
|
||||
val dm = (view.context ?: layout.context).resources.displayMetrics
|
||||
val sp = (dm.heightPixels * percentOfHeight) / dm.scaledDensity
|
||||
|
||||
@ -434,59 +434,80 @@ class HandlerOpeningScreen(private val activity: MainActivity) {
|
||||
private fun setupUploadButton() {
|
||||
uploadButton.text = t("upload")
|
||||
uploadButton.setOnClickListener {
|
||||
val clientCode = editText.text.toString().trim()
|
||||
GlobalValues.LAST_CLIENT_CODE = clientCode
|
||||
val input = EditText(activity).apply { hint = "Server-Passwort" }
|
||||
android.app.AlertDialog.Builder(activity)
|
||||
.setTitle(t("login_required"))
|
||||
.setView(input)
|
||||
.setPositiveButton("OK") { _, _ ->
|
||||
val password = input.text.toString()
|
||||
if (password.isNotBlank()) {
|
||||
Toast.makeText(activity, t("checking_login"), Toast.LENGTH_SHORT).show()
|
||||
DatabaseUploader.uploadDatabaseWithLogin(activity, password)
|
||||
} else {
|
||||
Toast.makeText(activity, t("enter_password"), Toast.LENGTH_SHORT).show()
|
||||
}
|
||||
GlobalValues.LAST_CLIENT_CODE = editText.text.toString().trim()
|
||||
promptCredentials(
|
||||
title = t("login_required") ?: "Login erforderlich",
|
||||
onOk = { user, pass ->
|
||||
// Login -> Upload
|
||||
DatabaseUploader.uploadDatabaseWithLogin(activity, user, pass)
|
||||
}
|
||||
.setNegativeButton(t("cancel"), null)
|
||||
.show()
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
private fun setupDownloadButton() {
|
||||
downloadButton.text = t("download")
|
||||
downloadButton.setOnClickListener {
|
||||
val clientCode = editText.text.toString().trim()
|
||||
GlobalValues.LAST_CLIENT_CODE = clientCode
|
||||
val input = EditText(activity).apply { hint = "Server-Passwort" }
|
||||
android.app.AlertDialog.Builder(activity)
|
||||
.setTitle(t("login_required"))
|
||||
.setView(input)
|
||||
.setPositiveButton("OK") { _, _ ->
|
||||
val password = input.text.toString()
|
||||
if (password.isNotBlank()) {
|
||||
LoginManager.loginUser(
|
||||
context = activity,
|
||||
password = password,
|
||||
onSuccess = { token ->
|
||||
Toast.makeText(activity, t("login_ok"), Toast.LENGTH_SHORT).show()
|
||||
DatabaseDownloader.downloadAndReplaceDatabase(activity, token)
|
||||
updateMainButtonsState(true)
|
||||
},
|
||||
onError = { error ->
|
||||
Toast.makeText(activity, error, Toast.LENGTH_LONG).show()
|
||||
}
|
||||
)
|
||||
} else {
|
||||
Toast.makeText(activity, t("enter_password"), Toast.LENGTH_SHORT).show()
|
||||
}
|
||||
GlobalValues.LAST_CLIENT_CODE = editText.text.toString().trim()
|
||||
promptCredentials(
|
||||
title = t("login_required") ?: "Login erforderlich",
|
||||
onOk = { user, pass ->
|
||||
// Login -> Token -> Download
|
||||
LoginManager.loginUserWithCredentials(
|
||||
context = activity,
|
||||
username = user,
|
||||
password = pass,
|
||||
onSuccess = { token ->
|
||||
Toast.makeText(activity, t("login_ok") ?: "Login OK", Toast.LENGTH_SHORT).show()
|
||||
DatabaseDownloader.downloadAndReplaceDatabase(activity, token)
|
||||
updateMainButtonsState(true)
|
||||
},
|
||||
onError = { error ->
|
||||
Toast.makeText(activity, error, Toast.LENGTH_LONG).show()
|
||||
}
|
||||
)
|
||||
}
|
||||
.setNegativeButton(t("cancel"), null)
|
||||
.show()
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
private fun promptCredentials(
|
||||
title: String,
|
||||
onOk: (username: String, password: String) -> Unit
|
||||
) {
|
||||
val wrapper = LinearLayout(activity).apply {
|
||||
orientation = LinearLayout.VERTICAL
|
||||
setPadding(dp(20), dp(8), dp(20), 0)
|
||||
}
|
||||
val etUser = EditText(activity).apply {
|
||||
hint = "Username"
|
||||
setSingleLine()
|
||||
}
|
||||
val etPass = EditText(activity).apply {
|
||||
hint = "Passwort"
|
||||
setSingleLine()
|
||||
inputType = android.text.InputType.TYPE_CLASS_TEXT or
|
||||
android.text.InputType.TYPE_TEXT_VARIATION_PASSWORD
|
||||
}
|
||||
wrapper.addView(etUser)
|
||||
wrapper.addView(etPass)
|
||||
|
||||
android.app.AlertDialog.Builder(activity)
|
||||
.setTitle(title)
|
||||
.setView(wrapper)
|
||||
.setPositiveButton("OK") { _, _ ->
|
||||
val u = etUser.text.toString().trim()
|
||||
val p = etPass.text.toString()
|
||||
if (u.isNotEmpty() && p.isNotEmpty()) {
|
||||
onOk(u, p)
|
||||
} else {
|
||||
Toast.makeText(activity, t("enter_password") ?: "Bitte Username & Passwort eingeben", Toast.LENGTH_SHORT).show()
|
||||
}
|
||||
}
|
||||
.setNegativeButton(t("cancel") ?: "Abbrechen", null)
|
||||
.show()
|
||||
}
|
||||
|
||||
private fun setupDatabaseButtonHandler() {
|
||||
DatabaseButtonHandler(
|
||||
activity = activity,
|
||||
|
||||
@ -19,7 +19,7 @@ object LanguageManager {
|
||||
private fun injectDynamicValues(text: String): String {
|
||||
val points = RHS_POINTS ?: 0
|
||||
val color = when (points) {
|
||||
in 1..12 -> "#4CAF50" // Grün
|
||||
in 0..12 -> "#4CAF50" // Grün
|
||||
in 13..36 -> "#FFEB3B" // Gelb
|
||||
in 37..100 -> "#F44336" // Rot
|
||||
else -> "#9E9E9E" // Grau (Standard)
|
||||
|
||||
@ -17,35 +17,45 @@ object LoginManager {
|
||||
private const val SERVER_LOGIN_URL = "http://49.13.157.44/login.php"
|
||||
private val client = OkHttpClient()
|
||||
|
||||
fun loginUser(
|
||||
/**
|
||||
* Neuer Login: Benutzername + Passwort -> Token
|
||||
*/
|
||||
fun loginUserWithCredentials(
|
||||
context: Context,
|
||||
username: String,
|
||||
password: String,
|
||||
onSuccess: (String) -> Unit,
|
||||
onError: (String) -> Unit
|
||||
) {
|
||||
CoroutineScope(Dispatchers.IO).launch {
|
||||
try {
|
||||
val requestBody = """{"password":"$password"}"""
|
||||
.toRequestBody("application/json".toMediaType())
|
||||
val bodyJson = JSONObject().apply {
|
||||
put("username", username.trim())
|
||||
put("password", password)
|
||||
}.toString()
|
||||
|
||||
val requestBody = bodyJson.toRequestBody("application/json".toMediaType())
|
||||
val request = Request.Builder()
|
||||
.url(SERVER_LOGIN_URL)
|
||||
.post(requestBody)
|
||||
.build()
|
||||
|
||||
val response = client.newCall(request).execute()
|
||||
val responseText = response.body?.string()
|
||||
|
||||
if (response.isSuccessful && responseText != null) {
|
||||
val json = JSONObject(responseText)
|
||||
if (json.optBoolean("success")) {
|
||||
val token = json.getString("token")
|
||||
withContext(Dispatchers.Main) { onSuccess(token) }
|
||||
} else {
|
||||
withContext(Dispatchers.Main) { onError("Login fehlgeschlagen") }
|
||||
client.newCall(request).execute().use { resp ->
|
||||
val text = resp.body?.string()
|
||||
if (!resp.isSuccessful || text == null) {
|
||||
withContext(Dispatchers.Main) {
|
||||
onError("Fehler beim Login (${resp.code})")
|
||||
}
|
||||
return@use
|
||||
}
|
||||
val json = runCatching { JSONObject(text) }.getOrNull()
|
||||
val ok = json?.optBoolean("success") == true
|
||||
val token = json?.optString("token").orEmpty()
|
||||
|
||||
withContext(Dispatchers.Main) {
|
||||
if (ok && token.isNotBlank()) onSuccess(token)
|
||||
else onError(json?.optString("message") ?: "Login fehlgeschlagen")
|
||||
}
|
||||
} else {
|
||||
withContext(Dispatchers.Main) { onError("Fehler beim Login (${response.code})") }
|
||||
}
|
||||
} catch (e: Exception) {
|
||||
Log.e("LOGIN", "Exception", e)
|
||||
|
||||
Reference in New Issue
Block a user