diff --git a/app/src/main/java/com/dano/test1/DatabaseDownloader.kt b/app/src/main/java/com/dano/test1/DatabaseDownloader.kt index 5757df9..28b3263 100644 --- a/app/src/main/java/com/dano/test1/DatabaseDownloader.kt +++ b/app/src/main/java/com/dano/test1/DatabaseDownloader.kt @@ -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) + } } diff --git a/app/src/main/java/com/dano/test1/DatabaseUploader.kt b/app/src/main/java/com/dano/test1/DatabaseUploader.kt index abf6c84..4dd7bd4 100644 --- a/app/src/main/java/com/dano/test1/DatabaseUploader.kt +++ b/app/src/main/java/com/dano/test1/DatabaseUploader.kt @@ -160,4 +160,8 @@ object DatabaseUploader { } return arr } + + fun uploadDatabaseWithToken(context: Context, token: String) { + uploadDatabase(context, token) // nutzt die bestehende interne Logik + } } diff --git a/app/src/main/java/com/dano/test1/HandlerOpeningScreen.kt b/app/src/main/java/com/dano/test1/HandlerOpeningScreen.kt index 36aea76..449d7a3 100644 --- a/app/src/main/java/com/dano/test1/HandlerOpeningScreen.kt +++ b/app/src/main/java/com/dano/test1/HandlerOpeningScreen.kt @@ -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 { diff --git a/app/src/main/java/com/dano/test1/LoginManager.kt b/app/src/main/java/com/dano/test1/LoginManager.kt index 4133fdd..aa4bf2b 100644 --- a/app/src/main/java/com/dano/test1/LoginManager.kt +++ b/app/src/main/java/com/dano/test1/LoginManager.kt @@ -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) + } } diff --git a/app/src/main/java/com/dano/test1/MainActivity.kt b/app/src/main/java/com/dano/test1/MainActivity.kt index 20c57eb..05cded2 100644 --- a/app/src/main/java/com/dano/test1/MainActivity.kt +++ b/app/src/main/java/com/dano/test1/MainActivity.kt @@ -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. - openingScreenHandler = HandlerOpeningScreen(this) - openingScreenHandler.init() + + // Zeige sofort Login-Dialog; erst NACH erfolgreichem Login + Download initialisieren wir den Opening Screen + showLoginThenDownload() } - // Wichtig: Bei Konfigurationsänderungen NICHT den Screen neu setzen. - // So bleibt man auf der aktuellen Frage/Seite. + 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() + + 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) - // Kein setContentView(), kein openingScreenHandler.init() hier! - // Falls du Layout-Metriken bei Rotation neu berechnen willst, kannst du das gezielt hier tun. } - /** - * 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 - openingScreenHandler.init() + if (::openingScreenHandler.isInitialized) { + openingScreenHandler.init() + } } } diff --git a/app/src/main/java/com/dano/test1/TokenStore.kt b/app/src/main/java/com/dano/test1/TokenStore.kt new file mode 100644 index 0000000..d953307 --- /dev/null +++ b/app/src/main/java/com/dano/test1/TokenStore.kt @@ -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) +}