implemented donwload upload handling with username
This commit is contained in:
@ -1,4 +1,3 @@
|
|||||||
// app/src/main/java/com/dano/test1/DatabaseDownloader.kt
|
|
||||||
package com.dano.test1
|
package com.dano.test1
|
||||||
|
|
||||||
import android.content.Context
|
import android.content.Context
|
||||||
@ -6,6 +5,7 @@ import android.util.Log
|
|||||||
import kotlinx.coroutines.CoroutineScope
|
import kotlinx.coroutines.CoroutineScope
|
||||||
import kotlinx.coroutines.Dispatchers
|
import kotlinx.coroutines.Dispatchers
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
|
import kotlinx.coroutines.withContext
|
||||||
import okhttp3.OkHttpClient
|
import okhttp3.OkHttpClient
|
||||||
import okhttp3.Request
|
import okhttp3.Request
|
||||||
import java.io.File
|
import java.io.File
|
||||||
@ -18,8 +18,10 @@ object DatabaseDownloader {
|
|||||||
|
|
||||||
private val client = OkHttpClient()
|
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 {
|
CoroutineScope(Dispatchers.IO).launch {
|
||||||
|
var ok = false
|
||||||
try {
|
try {
|
||||||
val request = Request.Builder()
|
val request = Request.Builder()
|
||||||
.url(SERVER_DOWNLOAD_URL)
|
.url(SERVER_DOWNLOAD_URL)
|
||||||
@ -29,6 +31,7 @@ object DatabaseDownloader {
|
|||||||
val response = client.newCall(request).execute()
|
val response = client.newCall(request).execute()
|
||||||
if (!response.isSuccessful) {
|
if (!response.isSuccessful) {
|
||||||
Log.e("DOWNLOAD", "HTTP ${response.code}")
|
Log.e("DOWNLOAD", "HTTP ${response.code}")
|
||||||
|
withContext(Dispatchers.Main) { onDone?.invoke(false) }
|
||||||
return@launch
|
return@launch
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -43,9 +46,17 @@ object DatabaseDownloader {
|
|||||||
FileOutputStream(dbFile).use { it.write(decryptedBytes) }
|
FileOutputStream(dbFile).use { it.write(decryptedBytes) }
|
||||||
|
|
||||||
Log.d("DOWNLOAD", "DB erfolgreich ersetzt")
|
Log.d("DOWNLOAD", "DB erfolgreich ersetzt")
|
||||||
|
ok = true
|
||||||
} catch (e: Exception) {
|
} catch (e: Exception) {
|
||||||
Log.e("DOWNLOAD", "Fehler", e)
|
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)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -160,4 +160,8 @@ object DatabaseUploader {
|
|||||||
}
|
}
|
||||||
return arr
|
return arr
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun uploadDatabaseWithToken(context: Context, token: String) {
|
||||||
|
uploadDatabase(context, token) // nutzt die bestehende interne Logik
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -434,17 +434,25 @@ class HandlerOpeningScreen(private val activity: MainActivity) {
|
|||||||
private fun setupUploadButton() {
|
private fun setupUploadButton() {
|
||||||
uploadButton.text = t("upload")
|
uploadButton.text = t("upload")
|
||||||
uploadButton.setOnClickListener {
|
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()
|
GlobalValues.LAST_CLIENT_CODE = editText.text.toString().trim()
|
||||||
promptCredentials(
|
DatabaseUploader.uploadDatabaseWithToken(activity, token)
|
||||||
title = t("login_required") ?: "Login erforderlich",
|
// Hinweis: Nach erfolgreichem Upload schließt sich die App (exitProcess(0)) wie bisher.
|
||||||
onOk = { user, pass ->
|
|
||||||
// Login -> Upload
|
|
||||||
DatabaseUploader.uploadDatabaseWithLogin(activity, user, pass)
|
|
||||||
}
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
private fun setupDownloadButton() {
|
private fun setupDownloadButton() {
|
||||||
downloadButton.text = t("download")
|
downloadButton.text = t("download")
|
||||||
downloadButton.setOnClickListener {
|
downloadButton.setOnClickListener {
|
||||||
|
|||||||
@ -1,4 +1,3 @@
|
|||||||
// app/src/main/java/com/dano/test1/LoginManager.kt
|
|
||||||
package com.dano.test1
|
package com.dano.test1
|
||||||
|
|
||||||
import android.content.Context
|
import android.content.Context
|
||||||
@ -17,9 +16,7 @@ object LoginManager {
|
|||||||
private const val SERVER_LOGIN_URL = "http://49.13.157.44/login.php"
|
private const val SERVER_LOGIN_URL = "http://49.13.157.44/login.php"
|
||||||
private val client = OkHttpClient()
|
private val client = OkHttpClient()
|
||||||
|
|
||||||
/**
|
// Neuer Login mit Username+Passwort
|
||||||
* Neuer Login: Benutzername + Passwort -> Token
|
|
||||||
*/
|
|
||||||
fun loginUserWithCredentials(
|
fun loginUserWithCredentials(
|
||||||
context: Context,
|
context: Context,
|
||||||
username: String,
|
username: String,
|
||||||
@ -30,32 +27,28 @@ object LoginManager {
|
|||||||
CoroutineScope(Dispatchers.IO).launch {
|
CoroutineScope(Dispatchers.IO).launch {
|
||||||
try {
|
try {
|
||||||
val bodyJson = JSONObject().apply {
|
val bodyJson = JSONObject().apply {
|
||||||
put("username", username.trim())
|
put("username", username)
|
||||||
put("password", password)
|
put("password", password)
|
||||||
}.toString()
|
}.toString()
|
||||||
|
|
||||||
val requestBody = bodyJson.toRequestBody("application/json".toMediaType())
|
|
||||||
val request = Request.Builder()
|
val request = Request.Builder()
|
||||||
.url(SERVER_LOGIN_URL)
|
.url(SERVER_LOGIN_URL)
|
||||||
.post(requestBody)
|
.post(bodyJson.toRequestBody("application/json".toMediaType()))
|
||||||
.build()
|
.build()
|
||||||
|
|
||||||
client.newCall(request).execute().use { resp ->
|
val response = client.newCall(request).execute()
|
||||||
val text = resp.body?.string()
|
val text = response.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 (response.isSuccessful && text != null) {
|
||||||
if (ok && token.isNotBlank()) onSuccess(token)
|
val json = JSONObject(text)
|
||||||
else onError(json?.optString("message") ?: "Login fehlgeschlagen")
|
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) {
|
} catch (e: Exception) {
|
||||||
Log.e("LOGIN", "Exception", e)
|
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)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -2,6 +2,12 @@ package com.dano.test1
|
|||||||
|
|
||||||
import android.content.res.Configuration
|
import android.content.res.Configuration
|
||||||
import android.os.Bundle
|
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
|
import androidx.appcompat.app.AppCompatActivity
|
||||||
|
|
||||||
class MainActivity : AppCompatActivity() {
|
class MainActivity : AppCompatActivity() {
|
||||||
@ -11,27 +17,112 @@ class MainActivity : AppCompatActivity() {
|
|||||||
var isInQuestionnaire: Boolean = false
|
var isInQuestionnaire: Boolean = false
|
||||||
var isFirstQuestionnairePage: Boolean = false
|
var isFirstQuestionnairePage: Boolean = false
|
||||||
|
|
||||||
|
private var progress: ProgressBar? = null
|
||||||
|
|
||||||
override fun onCreate(savedInstanceState: Bundle?) {
|
override fun onCreate(savedInstanceState: Bundle?) {
|
||||||
super.onCreate(savedInstanceState)
|
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
|
||||||
openingScreenHandler = HandlerOpeningScreen(this)
|
showLoginThenDownload()
|
||||||
openingScreenHandler.init()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Wichtig: Bei Konfigurationsänderungen NICHT den Screen neu setzen.
|
private fun showLoginThenDownload() {
|
||||||
// So bleibt man auf der aktuellen Frage/Seite.
|
// 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) {
|
override fun onConfigurationChanged(newConfig: Configuration) {
|
||||||
super.onConfigurationChanged(newConfig)
|
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) {
|
fun startQuestionnaire(questionnaire: QuestionnaireBase<*>, languageID: String) {
|
||||||
isInQuestionnaire = true
|
isInQuestionnaire = true
|
||||||
isFirstQuestionnairePage = true
|
isFirstQuestionnairePage = true
|
||||||
@ -39,23 +130,17 @@ class MainActivity : AppCompatActivity() {
|
|||||||
questionnaire.startQuestionnaire()
|
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() {
|
override fun onBackPressed() {
|
||||||
if (!openingScreenHandler.onBackPressed()) {
|
if (!::openingScreenHandler.isInitialized || !openingScreenHandler.onBackPressed()) {
|
||||||
super.onBackPressed()
|
super.onBackPressed()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Finish the questionnaire and return to the opening screen.
|
|
||||||
*/
|
|
||||||
fun finishQuestionnaire() {
|
fun finishQuestionnaire() {
|
||||||
isInQuestionnaire = false
|
isInQuestionnaire = false
|
||||||
isFirstQuestionnairePage = false
|
isFirstQuestionnairePage = false
|
||||||
openingScreenHandler.init()
|
if (::openingScreenHandler.isInitialized) {
|
||||||
|
openingScreenHandler.init()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
24
app/src/main/java/com/dano/test1/TokenStore.kt
Normal file
24
app/src/main/java/com/dano/test1/TokenStore.kt
Normal 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)
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user