added offline mode and improved coachcode system
This commit is contained in:
4
.idea/deploymentTargetSelector.xml
generated
4
.idea/deploymentTargetSelector.xml
generated
@ -4,10 +4,10 @@
|
|||||||
<selectionStates>
|
<selectionStates>
|
||||||
<SelectionState runConfigName="app">
|
<SelectionState runConfigName="app">
|
||||||
<option name="selectionMode" value="DROPDOWN" />
|
<option name="selectionMode" value="DROPDOWN" />
|
||||||
<DropdownSelection timestamp="2025-09-29T09:46:59.707167200Z">
|
<DropdownSelection timestamp="2025-09-29T10:52:30.282144200Z">
|
||||||
<Target type="DEFAULT_BOOT">
|
<Target type="DEFAULT_BOOT">
|
||||||
<handle>
|
<handle>
|
||||||
<DeviceId pluginId="PhysicalDevice" identifier="serial=HA218GZY" />
|
<DeviceId pluginId="LocalEmulator" identifier="path=C:\Users\danie\.android\avd\Medium_Phone.avd" />
|
||||||
</handle>
|
</handle>
|
||||||
</Target>
|
</Target>
|
||||||
</DropdownSelection>
|
</DropdownSelection>
|
||||||
|
|||||||
@ -27,29 +27,37 @@ class HandlerClientCoachCode(
|
|||||||
this.question = question
|
this.question = question
|
||||||
|
|
||||||
val clientCodeField = layout.findViewById<EditText>(R.id.client_code)
|
val clientCodeField = layout.findViewById<EditText>(R.id.client_code)
|
||||||
val coachCodeField = layout.findViewById<EditText>(R.id.coach_code)
|
val coachCodeField = layout.findViewById<EditText>(R.id.coach_code)
|
||||||
val questionTextView = layout.findViewById<TextView>(R.id.question)
|
val questionTextView = layout.findViewById<TextView>(R.id.question)
|
||||||
val titleTextView = layout.findViewById<TextView>(R.id.textView)
|
val titleTextView = layout.findViewById<TextView>(R.id.textView)
|
||||||
|
|
||||||
questionTextView.text = question.question?.let { LanguageManager.getText(languageID, it) } ?: ""
|
questionTextView.text = question.question?.let { LanguageManager.getText(languageID, it) } ?: ""
|
||||||
|
|
||||||
setTextSizePercentOfScreenHeight(titleTextView, 0.03f)
|
setTextSizePercentOfScreenHeight(titleTextView, 0.03f)
|
||||||
setTextSizePercentOfScreenHeight(questionTextView,0.03f)
|
setTextSizePercentOfScreenHeight(questionTextView, 0.03f)
|
||||||
setTextSizePercentOfScreenHeight(clientCodeField, 0.025f)
|
setTextSizePercentOfScreenHeight(clientCodeField, 0.025f)
|
||||||
setTextSizePercentOfScreenHeight(coachCodeField, 0.025f)
|
setTextSizePercentOfScreenHeight(coachCodeField, 0.025f)
|
||||||
|
|
||||||
// *** WICHTIG: Nur den ERFOLGREICH GELADENEN Code verwenden ***
|
// Client-Code: nur verwenden, wenn bereits geladen
|
||||||
val loadedClientCode = GlobalValues.LOADED_CLIENT_CODE
|
val loadedClientCode = GlobalValues.LOADED_CLIENT_CODE
|
||||||
if (!loadedClientCode.isNullOrBlank()) {
|
if (!loadedClientCode.isNullOrBlank()) {
|
||||||
clientCodeField.setText(loadedClientCode)
|
clientCodeField.setText(loadedClientCode)
|
||||||
clientCodeField.isEnabled = false
|
clientCodeField.isEnabled = false
|
||||||
} else {
|
} else {
|
||||||
// Nichts ist geladen → Feld bleibt leer und editierbar
|
|
||||||
clientCodeField.setText("")
|
clientCodeField.setText("")
|
||||||
clientCodeField.isEnabled = true
|
clientCodeField.isEnabled = true
|
||||||
}
|
}
|
||||||
|
|
||||||
coachCodeField.setText(answers["coach_code"] as? String ?: "")
|
// === NEU: Coach-Code immer aus dem Login (TokenStore) setzen und sperren ===
|
||||||
|
val coachFromLogin = TokenStore.getUsername(layout.context)
|
||||||
|
if (!coachFromLogin.isNullOrBlank()) {
|
||||||
|
coachCodeField.setText(coachFromLogin)
|
||||||
|
lockCoachField(coachCodeField) // optisch & technisch gesperrt
|
||||||
|
} else {
|
||||||
|
// Falls (theoretisch) kein Login-Username vorhanden ist, verhalten wie bisher
|
||||||
|
coachCodeField.setText(answers["coach_code"] as? String ?: "")
|
||||||
|
coachCodeField.isEnabled = true
|
||||||
|
}
|
||||||
|
|
||||||
layout.findViewById<Button>(R.id.Qnext).setOnClickListener {
|
layout.findViewById<Button>(R.id.Qnext).setOnClickListener {
|
||||||
onNextClicked(clientCodeField, coachCodeField)
|
onNextClicked(clientCodeField, coachCodeField)
|
||||||
@ -67,7 +75,6 @@ class HandlerClientCoachCode(
|
|||||||
}
|
}
|
||||||
|
|
||||||
private fun onNextClicked(clientCodeField: EditText, coachCodeField: EditText) {
|
private fun onNextClicked(clientCodeField: EditText, coachCodeField: EditText) {
|
||||||
// 1) Ohne vorher geladenen Client NICHT weiter
|
|
||||||
val loadedClientCode = GlobalValues.LOADED_CLIENT_CODE
|
val loadedClientCode = GlobalValues.LOADED_CLIENT_CODE
|
||||||
|
|
||||||
if (!validate()) {
|
if (!validate()) {
|
||||||
@ -77,7 +84,8 @@ class HandlerClientCoachCode(
|
|||||||
}
|
}
|
||||||
|
|
||||||
val clientCode = clientCodeField.text.toString()
|
val clientCode = clientCodeField.text.toString()
|
||||||
val coachCode = coachCodeField.text.toString()
|
// Erzwinge Coach-Code aus Login (falls vorhanden)
|
||||||
|
val coachCode = TokenStore.getUsername(layout.context) ?: coachCodeField.text.toString()
|
||||||
|
|
||||||
// Prüfen, ob die DB-Datei vor dem Zugriff existiert
|
// Prüfen, ob die DB-Datei vor dem Zugriff existiert
|
||||||
val dbPath = layout.context.getDatabasePath("questionnaire_database")
|
val dbPath = layout.context.getDatabasePath("questionnaire_database")
|
||||||
@ -87,7 +95,6 @@ class HandlerClientCoachCode(
|
|||||||
val existingClient = MyApp.database.clientDao().getClientByCode(clientCode)
|
val existingClient = MyApp.database.clientDao().getClientByCode(clientCode)
|
||||||
|
|
||||||
withContext(Dispatchers.Main) {
|
withContext(Dispatchers.Main) {
|
||||||
// Wenn Feld editierbar war und Code bereits existiert → Hinweis
|
|
||||||
if (existingClient != null && clientCodeField.isEnabled) {
|
if (existingClient != null && clientCodeField.isEnabled) {
|
||||||
val message = LanguageManager.getText(languageID, "client_code_exists")
|
val message = LanguageManager.getText(languageID, "client_code_exists")
|
||||||
showToast(message)
|
showToast(message)
|
||||||
@ -108,25 +115,44 @@ class HandlerClientCoachCode(
|
|||||||
|
|
||||||
private fun onPreviousClicked(clientCodeField: EditText, coachCodeField: EditText) {
|
private fun onPreviousClicked(clientCodeField: EditText, coachCodeField: EditText) {
|
||||||
val clientCode = clientCodeField.text.toString()
|
val clientCode = clientCodeField.text.toString()
|
||||||
val coachCode = coachCodeField.text.toString()
|
val coachCode = TokenStore.getUsername(layout.context) ?: coachCodeField.text.toString()
|
||||||
saveAnswers(clientCode, coachCode)
|
saveAnswers(clientCode, coachCode)
|
||||||
goToPreviousQuestion()
|
goToPreviousQuestion()
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun validate(): Boolean {
|
override fun validate(): Boolean {
|
||||||
val clientCode = layout.findViewById<EditText>(R.id.client_code).text
|
val clientCode = layout.findViewById<EditText>(R.id.client_code).text
|
||||||
val coachCode = layout.findViewById<EditText>(R.id.coach_code).text
|
// Validierung nimmt den (ggf. gesperrten) Text – passt
|
||||||
return clientCode.isNotBlank() && coachCode.isNotBlank()
|
val coachText = layout.findViewById<EditText>(R.id.coach_code).text
|
||||||
|
return clientCode.isNotBlank() && coachText.isNotBlank()
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun saveAnswers(clientCode: String, coachCode: String) {
|
private fun saveAnswers(clientCode: String, coachCode: String) {
|
||||||
// Optional: LAST_CLIENT_CODE kann gesetzt bleiben; maßgeblich ist LOADED_CLIENT_CODE
|
|
||||||
GlobalValues.LAST_CLIENT_CODE = clientCode
|
GlobalValues.LAST_CLIENT_CODE = clientCode
|
||||||
answers["client_code"] = clientCode
|
answers["client_code"] = clientCode
|
||||||
answers["coach_code"] = coachCode
|
// Speichere garantierten Coach-Code aus Login bevorzugt
|
||||||
|
val loginCoach = TokenStore.getUsername(layout.context)
|
||||||
|
answers["coach_code"] = loginCoach ?: coachCode
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun saveAnswer() {
|
override fun saveAnswer() {
|
||||||
// Not used
|
// Not used
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// --- Helfer zum Sperren inkl. optischer Markierung (wie im Opening Screen) ---
|
||||||
|
private fun lockCoachField(field: EditText) {
|
||||||
|
field.isFocusable = false
|
||||||
|
field.isFocusableInTouchMode = false
|
||||||
|
field.isCursorVisible = false
|
||||||
|
field.keyListener = null
|
||||||
|
field.isLongClickable = false
|
||||||
|
field.isClickable = false
|
||||||
|
field.setBackgroundResource(R.drawable.bg_field_locked)
|
||||||
|
field.setCompoundDrawablesWithIntrinsicBounds(0, 0, R.drawable.ic_lock_24, 0)
|
||||||
|
field.compoundDrawablePadding = dp(8)
|
||||||
|
field.alpha = 0.95f
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun dp(v: Int): Int =
|
||||||
|
(v * layout.resources.displayMetrics.density).toInt()
|
||||||
}
|
}
|
||||||
|
|||||||
@ -13,6 +13,7 @@ import android.widget.ProgressBar
|
|||||||
import android.widget.Toast
|
import android.widget.Toast
|
||||||
import androidx.appcompat.app.AlertDialog
|
import androidx.appcompat.app.AlertDialog
|
||||||
import androidx.appcompat.app.AppCompatActivity
|
import androidx.appcompat.app.AppCompatActivity
|
||||||
|
import java.io.File
|
||||||
|
|
||||||
class MainActivity : AppCompatActivity() {
|
class MainActivity : AppCompatActivity() {
|
||||||
|
|
||||||
@ -23,15 +24,33 @@ class MainActivity : AppCompatActivity() {
|
|||||||
|
|
||||||
private var progress: ProgressBar? = null
|
private var progress: ProgressBar? = null
|
||||||
|
|
||||||
// LIVE: Network-Callback (wird in onResume registriert, in onPause deregistriert)
|
// LIVE: Network-Callback (optional für Statusleiste)
|
||||||
private var netCb: ConnectivityManager.NetworkCallback? = null
|
private var netCb: ConnectivityManager.NetworkCallback? = null
|
||||||
|
|
||||||
override fun onCreate(savedInstanceState: Bundle?) {
|
override fun onCreate(savedInstanceState: Bundle?) {
|
||||||
super.onCreate(savedInstanceState)
|
super.onCreate(savedInstanceState)
|
||||||
// Login-Dialog -> Login -> Auto-Download -> OpeningScreen
|
|
||||||
|
// === Offline-Start ermöglichen ===
|
||||||
|
// Bedingung: gespeicherter User/Token UND lokale DB vorhanden -> direkt OpeningScreen
|
||||||
|
val hasCreds = !TokenStore.getUsername(this).isNullOrBlank() && !TokenStore.getToken(this).isNullOrBlank()
|
||||||
|
val hasDb = hasLocalDb()
|
||||||
|
if (hasCreds && hasDb) {
|
||||||
|
openingScreenHandler = HandlerOpeningScreen(this)
|
||||||
|
openingScreenHandler.init()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Sonst: Login-Dialog -> Login -> DB (einmalig) laden -> OpeningScreen
|
||||||
showLoginThenDownload()
|
showLoginThenDownload()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** Prüft, ob die lokale DB-Datei vorhanden ist. */
|
||||||
|
private fun hasLocalDb(): Boolean {
|
||||||
|
val dbFile = getDatabasePath("questionnaire_database")
|
||||||
|
return dbFile != null && dbFile.exists() && dbFile.length() > 0
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Zeigt den Login-Dialog an, führt Login aus und lädt danach einmalig die DB. */
|
||||||
private fun showLoginThenDownload() {
|
private fun showLoginThenDownload() {
|
||||||
val container = LinearLayout(this).apply {
|
val container = LinearLayout(this).apply {
|
||||||
orientation = LinearLayout.VERTICAL
|
orientation = LinearLayout.VERTICAL
|
||||||
@ -47,7 +66,8 @@ class MainActivity : AppCompatActivity() {
|
|||||||
inputType = android.text.InputType.TYPE_CLASS_TEXT or
|
inputType = android.text.InputType.TYPE_CLASS_TEXT or
|
||||||
android.text.InputType.TYPE_TEXT_VARIATION_PASSWORD
|
android.text.InputType.TYPE_TEXT_VARIATION_PASSWORD
|
||||||
}
|
}
|
||||||
container.addView(etUser); container.addView(etPass)
|
container.addView(etUser)
|
||||||
|
container.addView(etPass)
|
||||||
|
|
||||||
val dialog = AlertDialog.Builder(this)
|
val dialog = AlertDialog.Builder(this)
|
||||||
.setTitle("Login erforderlich")
|
.setTitle("Login erforderlich")
|
||||||
@ -67,18 +87,29 @@ class MainActivity : AppCompatActivity() {
|
|||||||
username = user,
|
username = user,
|
||||||
password = pass,
|
password = pass,
|
||||||
onSuccess = { token ->
|
onSuccess = { token ->
|
||||||
// Token & Login-Timestamp werden in TokenStore gespeichert (siehe LoginManager/TokenStore).
|
// Nach erfolgreichem Login: einmalig komplette DB ziehen
|
||||||
DatabaseDownloader.downloadAndReplaceDatabase(
|
DatabaseDownloader.downloadAndReplaceDatabase(
|
||||||
context = this,
|
context = this,
|
||||||
token = token
|
token = token
|
||||||
) { ok ->
|
) { ok ->
|
||||||
showBusy(false)
|
showBusy(false)
|
||||||
if (!ok) {
|
|
||||||
Toast.makeText(this, "Download fehlgeschlagen", Toast.LENGTH_LONG).show()
|
// Wenn Download fehlgeschlagen ist, aber evtl. schon eine DB lokal liegt,
|
||||||
|
// lassen wir den Nutzer trotzdem weiterarbeiten (Offline).
|
||||||
|
if (!ok && !hasLocalDb()) {
|
||||||
|
Toast.makeText(this, "Download fehlgeschlagen – keine lokale Datenbank vorhanden", Toast.LENGTH_LONG).show()
|
||||||
|
// Zurück zum Login, damit man es erneut probieren kann
|
||||||
|
showLoginThenDownload()
|
||||||
|
return@downloadAndReplaceDatabase
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (!ok) {
|
||||||
|
Toast.makeText(this, "Download fehlgeschlagen – arbeite offline mit vorhandener DB", Toast.LENGTH_LONG).show()
|
||||||
|
}
|
||||||
|
|
||||||
|
// Opening-Screen starten
|
||||||
openingScreenHandler = HandlerOpeningScreen(this)
|
openingScreenHandler = HandlerOpeningScreen(this)
|
||||||
openingScreenHandler.init()
|
openingScreenHandler.init()
|
||||||
// Ersten Status sofort anzeigen
|
|
||||||
openingScreenHandler.refreshHeaderStatusLive()
|
openingScreenHandler.refreshHeaderStatusLive()
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@ -111,11 +142,10 @@ class MainActivity : AppCompatActivity() {
|
|||||||
|
|
||||||
private fun dp(v: Int): Int = (v * resources.displayMetrics.density).toInt()
|
private fun dp(v: Int): Int = (v * resources.displayMetrics.density).toInt()
|
||||||
|
|
||||||
// --- LIVE NETZSTATUS ---
|
// --- LIVE NETZSTATUS (optional, für deine Status-Leiste) ---
|
||||||
override fun onResume() {
|
override fun onResume() {
|
||||||
super.onResume()
|
super.onResume()
|
||||||
registerNetworkCallback()
|
registerNetworkCallback()
|
||||||
// Falls die Startseite schon steht: Status jetzt gleich ziehen
|
|
||||||
if (::openingScreenHandler.isInitialized && !isInQuestionnaire) {
|
if (::openingScreenHandler.isInitialized && !isInQuestionnaire) {
|
||||||
openingScreenHandler.refreshHeaderStatusLive()
|
openingScreenHandler.refreshHeaderStatusLive()
|
||||||
}
|
}
|
||||||
@ -127,7 +157,7 @@ class MainActivity : AppCompatActivity() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private fun registerNetworkCallback() {
|
private fun registerNetworkCallback() {
|
||||||
if (netCb != null) return // schon aktiv
|
if (netCb != null) return
|
||||||
val cm = getSystemService(ConnectivityManager::class.java)
|
val cm = getSystemService(ConnectivityManager::class.java)
|
||||||
val req = NetworkRequest.Builder()
|
val req = NetworkRequest.Builder()
|
||||||
.addCapability(NetworkCapabilities.NET_CAPABILITY_INTERNET)
|
.addCapability(NetworkCapabilities.NET_CAPABILITY_INTERNET)
|
||||||
@ -147,7 +177,6 @@ class MainActivity : AppCompatActivity() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// Optional: auch auf „nicht validiert“ reagieren
|
|
||||||
override fun onCapabilitiesChanged(network: Network, caps: NetworkCapabilities) {
|
override fun onCapabilitiesChanged(network: Network, caps: NetworkCapabilities) {
|
||||||
runOnUiThread {
|
runOnUiThread {
|
||||||
if (::openingScreenHandler.isInitialized && !isInQuestionnaire) {
|
if (::openingScreenHandler.isInitialized && !isInQuestionnaire) {
|
||||||
|
|||||||
Reference in New Issue
Block a user