implemented donwload upload handling with username

This commit is contained in:
oxidiert
2025-09-23 16:49:59 +02:00
parent 5f5c766133
commit bf33501b69
6 changed files with 189 additions and 53 deletions

View File

@ -1,4 +1,3 @@
// app/src/main/java/com/dano/test1/DatabaseDownloader.kt
package com.dano.test1
import android.content.Context
@ -6,6 +5,7 @@ import android.util.Log
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext
import okhttp3.OkHttpClient
import okhttp3.Request
import java.io.File
@ -18,8 +18,10 @@ object DatabaseDownloader {
private val client = OkHttpClient()
fun downloadAndReplaceDatabase(context: Context, token: String) {
// Neue Variante mit Callback
fun downloadAndReplaceDatabase(context: Context, token: String, onDone: ((Boolean) -> Unit)? = null) {
CoroutineScope(Dispatchers.IO).launch {
var ok = false
try {
val request = Request.Builder()
.url(SERVER_DOWNLOAD_URL)
@ -29,6 +31,7 @@ object DatabaseDownloader {
val response = client.newCall(request).execute()
if (!response.isSuccessful) {
Log.e("DOWNLOAD", "HTTP ${response.code}")
withContext(Dispatchers.Main) { onDone?.invoke(false) }
return@launch
}
@ -43,9 +46,17 @@ object DatabaseDownloader {
FileOutputStream(dbFile).use { it.write(decryptedBytes) }
Log.d("DOWNLOAD", "DB erfolgreich ersetzt")
ok = true
} catch (e: Exception) {
Log.e("DOWNLOAD", "Fehler", e)
} finally {
withContext(Dispatchers.Main) { onDone?.invoke(ok) }
}
}
}
// Abwärtskompatible alte Signatur
fun downloadAndReplaceDatabase(context: Context, token: String) {
downloadAndReplaceDatabase(context, token, null)
}
}

View File

@ -160,4 +160,8 @@ object DatabaseUploader {
}
return arr
}
fun uploadDatabaseWithToken(context: Context, token: String) {
uploadDatabase(context, token) // nutzt die bestehende interne Logik
}
}

View File

@ -434,17 +434,25 @@ class HandlerOpeningScreen(private val activity: MainActivity) {
private fun setupUploadButton() {
uploadButton.text = t("upload")
uploadButton.setOnClickListener {
// Token aus dem Login (MainActivity speichert es in TokenStore)
val token = TokenStore.getToken(activity)
if (token.isNullOrBlank()) {
Toast.makeText(
activity,
t("login_required") ?: "Bitte zuerst einloggen",
Toast.LENGTH_LONG
).show()
return@setOnClickListener
}
// wie gewohnt: Client-Code merken, dann direkt hochladen
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)
}
)
DatabaseUploader.uploadDatabaseWithToken(activity, token)
// Hinweis: Nach erfolgreichem Upload schließt sich die App (exitProcess(0)) wie bisher.
}
}
private fun setupDownloadButton() {
downloadButton.text = t("download")
downloadButton.setOnClickListener {

View File

@ -1,4 +1,3 @@
// app/src/main/java/com/dano/test1/LoginManager.kt
package com.dano.test1
import android.content.Context
@ -17,9 +16,7 @@ object LoginManager {
private const val SERVER_LOGIN_URL = "http://49.13.157.44/login.php"
private val client = OkHttpClient()
/**
* Neuer Login: Benutzername + Passwort -> Token
*/
// Neuer Login mit Username+Passwort
fun loginUserWithCredentials(
context: Context,
username: String,
@ -30,32 +27,28 @@ object LoginManager {
CoroutineScope(Dispatchers.IO).launch {
try {
val bodyJson = JSONObject().apply {
put("username", username.trim())
put("username", username)
put("password", password)
}.toString()
val requestBody = bodyJson.toRequestBody("application/json".toMediaType())
val request = Request.Builder()
.url(SERVER_LOGIN_URL)
.post(requestBody)
.post(bodyJson.toRequestBody("application/json".toMediaType()))
.build()
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()
val response = client.newCall(request).execute()
val text = response.body?.string()
withContext(Dispatchers.Main) {
if (ok && token.isNotBlank()) onSuccess(token)
else onError(json?.optString("message") ?: "Login fehlgeschlagen")
if (response.isSuccessful && text != null) {
val json = JSONObject(text)
if (json.optBoolean("success")) {
val token = json.getString("token")
withContext(Dispatchers.Main) { onSuccess(token) }
} else {
withContext(Dispatchers.Main) { onError(json.optString("message", "Login fehlgeschlagen")) }
}
} else {
withContext(Dispatchers.Main) { onError("Fehler beim Login (${response.code})") }
}
} catch (e: Exception) {
Log.e("LOGIN", "Exception", e)
@ -63,4 +56,15 @@ object LoginManager {
}
}
}
// Alte Signatur (nur Passwort) für evtl. Altcode beibehalten
fun loginUser(
context: Context,
password: String,
onSuccess: (String) -> Unit,
onError: (String) -> Unit
) {
// nicht mehr genutzt; leitet auf neuen Login um mit leerem User
loginUserWithCredentials(context, "", password, onSuccess, onError)
}
}

View File

@ -2,6 +2,12 @@ package com.dano.test1
import android.content.res.Configuration
import android.os.Bundle
import android.view.View
import android.widget.EditText
import android.widget.LinearLayout
import android.widget.ProgressBar
import android.widget.Toast
import androidx.appcompat.app.AlertDialog
import androidx.appcompat.app.AppCompatActivity
class MainActivity : AppCompatActivity() {
@ -11,27 +17,112 @@ class MainActivity : AppCompatActivity() {
var isInQuestionnaire: Boolean = false
var isFirstQuestionnairePage: Boolean = false
private var progress: ProgressBar? = null
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
// Opening Screen nur beim echten Start initialisieren.
// Durch configChanges wird onCreate bei Drehung NICHT erneut aufgerufen.
// Zeige sofort Login-Dialog; erst NACH erfolgreichem Login + Download initialisieren wir den Opening Screen
showLoginThenDownload()
}
private fun showLoginThenDownload() {
// UI mit Username/Passwort
val container = LinearLayout(this).apply {
orientation = LinearLayout.VERTICAL
setPadding(dp(20), dp(8), dp(20), 0)
}
val etUser = EditText(this).apply {
hint = "Username"
setSingleLine()
}
val etPass = EditText(this).apply {
hint = "Passwort"
setSingleLine()
inputType = android.text.InputType.TYPE_CLASS_TEXT or
android.text.InputType.TYPE_TEXT_VARIATION_PASSWORD
}
container.addView(etUser)
container.addView(etPass)
val dialog = AlertDialog.Builder(this)
.setTitle("Login erforderlich")
.setView(container)
.setCancelable(false)
.setPositiveButton("Login") { _, _ ->
val user = etUser.text.toString().trim()
val pass = etPass.text.toString()
if (user.isEmpty() || pass.isEmpty()) {
Toast.makeText(this, "Bitte Username & Passwort eingeben", Toast.LENGTH_SHORT).show()
showLoginThenDownload()
return@setPositiveButton
}
showBusy(true)
// Login -> Token -> Auto-Download -> OpeningScreen init
LoginManager.loginUserWithCredentials(
context = this,
username = user,
password = pass,
onSuccess = { token ->
// optional speichern
TokenStore.save(this, user, token)
DatabaseDownloader.downloadAndReplaceDatabase(
context = this,
token = token
) { ok ->
showBusy(false)
if (!ok) {
Toast.makeText(this, "Download fehlgeschlagen", Toast.LENGTH_LONG).show()
}
// Jetzt erst die Start-UI hochziehen
openingScreenHandler = HandlerOpeningScreen(this)
openingScreenHandler.init()
}
},
onError = { msg ->
showBusy(false)
Toast.makeText(this, msg, Toast.LENGTH_LONG).show()
// erneut anbieten
showLoginThenDownload()
}
)
}
.setNegativeButton("Beenden") { _, _ ->
finishAffinity()
}
.create()
// Wichtig: Bei Konfigurationsänderungen NICHT den Screen neu setzen.
// So bleibt man auf der aktuellen Frage/Seite.
override fun onConfigurationChanged(newConfig: Configuration) {
super.onConfigurationChanged(newConfig)
// Kein setContentView(), kein openingScreenHandler.init() hier!
// Falls du Layout-Metriken bei Rotation neu berechnen willst, kannst du das gezielt hier tun.
dialog.show()
}
private fun showBusy(show: Boolean) {
if (show) {
if (progress == null) {
progress = ProgressBar(this).apply {
isIndeterminate = true
// simpler Vollbild-Overlay
val content = window?.decorView as? android.view.ViewGroup
content?.addView(this, android.view.ViewGroup.LayoutParams(
android.view.ViewGroup.LayoutParams.WRAP_CONTENT,
android.view.ViewGroup.LayoutParams.WRAP_CONTENT
))
this.x = (resources.displayMetrics.widthPixels / 2f) - width / 2f
this.y = (resources.displayMetrics.heightPixels / 2f) - height / 2f
}
}
progress?.visibility = View.VISIBLE
} else {
progress?.visibility = View.GONE
}
}
private fun dp(v: Int): Int = (v * resources.displayMetrics.density).toInt()
// Wichtig: Keine Neu-Initialisierung bei Rotation
override fun onConfigurationChanged(newConfig: Configuration) {
super.onConfigurationChanged(newConfig)
}
/**
* Starts the given questionnaire and attaches it to this activity.
* @param questionnaire The questionnaire instance to start.
* @param languageID The language identifier for localization.
*/
fun startQuestionnaire(questionnaire: QuestionnaireBase<*>, languageID: String) {
isInQuestionnaire = true
isFirstQuestionnairePage = true
@ -39,23 +130,17 @@ class MainActivity : AppCompatActivity() {
questionnaire.startQuestionnaire()
}
/**
* Handle the back button press.
* If the openingScreenHandler can handle it, do not call super.
* Otherwise, call the default back press behavior.
*/
override fun onBackPressed() {
if (!openingScreenHandler.onBackPressed()) {
if (!::openingScreenHandler.isInitialized || !openingScreenHandler.onBackPressed()) {
super.onBackPressed()
}
}
/**
* Finish the questionnaire and return to the opening screen.
*/
fun finishQuestionnaire() {
isInQuestionnaire = false
isFirstQuestionnairePage = false
if (::openingScreenHandler.isInitialized) {
openingScreenHandler.init()
}
}
}

View File

@ -0,0 +1,24 @@
package com.dano.test1
import android.content.Context
import android.content.SharedPreferences
object TokenStore {
private const val PREF = "auth_prefs"
private const val KEY_TOKEN = "token"
private const val KEY_USER = "username"
fun save(context: Context, username: String, token: String) {
prefs(context).edit()
.putString(KEY_TOKEN, token)
.putString(KEY_USER, username)
.apply()
}
fun getToken(context: Context): String? = prefs(context).getString(KEY_TOKEN, null)
fun getUsername(context: Context): String? = prefs(context).getString(KEY_USER, null)
fun clear(context: Context) { prefs(context).edit().clear().apply() }
private fun prefs(ctx: Context): SharedPreferences =
ctx.getSharedPreferences(PREF, Context.MODE_PRIVATE)
}