added button for save and edit

This commit is contained in:
oxidiert
2025-08-04 15:21:17 +02:00
parent e22ff56ab1
commit 2cf9faed38
5 changed files with 110 additions and 64 deletions

View File

@ -20,6 +20,8 @@ class HandlerOpeningScreen(private val activity: MainActivity) {
private lateinit var textView: TextView private lateinit var textView: TextView
private lateinit var buttonContainer: LinearLayout private lateinit var buttonContainer: LinearLayout
private lateinit var buttonLoad: Button private lateinit var buttonLoad: Button
private lateinit var saveButton: Button
private lateinit var editButton: Button
private val dynamicButtons = mutableListOf<Button>() private val dynamicButtons = mutableListOf<Button>()
private val questionnaireFiles = mutableMapOf<Button, String>() private val questionnaireFiles = mutableMapOf<Button, String>()
@ -36,6 +38,8 @@ class HandlerOpeningScreen(private val activity: MainActivity) {
setupLanguageSpinner() setupLanguageSpinner()
setupLoadButton() setupLoadButton()
setupSaveButton()
setupEditButton()
if (!editText.text.isNullOrBlank()) { if (!editText.text.isNullOrBlank()) {
buttonLoad.performClick() buttonLoad.performClick()
@ -48,6 +52,8 @@ class HandlerOpeningScreen(private val activity: MainActivity) {
textView = activity.findViewById(R.id.textView) textView = activity.findViewById(R.id.textView)
buttonContainer = activity.findViewById(R.id.buttonContainer) buttonContainer = activity.findViewById(R.id.buttonContainer)
buttonLoad = activity.findViewById(R.id.loadButton) buttonLoad = activity.findViewById(R.id.loadButton)
saveButton = activity.findViewById(R.id.saveButton)
editButton = activity.findViewById(R.id.editButton)
val tag = editText.tag as? String ?: "" val tag = editText.tag as? String ?: ""
editText.hint = LanguageManager.getText(languageID, tag) editText.hint = LanguageManager.getText(languageID, tag)
@ -154,13 +160,7 @@ class HandlerOpeningScreen(private val activity: MainActivity) {
return return
} }
val isDatabaseView = inputText.endsWith("_database")
val isChangeView = inputText.endsWith("_change")
// Extrahiere nur den echten Code, ohne die Suffixe
val clientCode = inputText val clientCode = inputText
.removeSuffix("_database")
.removeSuffix("_change")
GlobalValues.LAST_CLIENT_CODE = clientCode GlobalValues.LAST_CLIENT_CODE = clientCode
@ -176,16 +176,7 @@ class HandlerOpeningScreen(private val activity: MainActivity) {
} }
withContext(Dispatchers.Main) { withContext(Dispatchers.Main) {
// Normales Laden
handleNormalLoad(clientCode) handleNormalLoad(clientCode)
// Erweiterungen bei speziellen Suffixen
if (isDatabaseView) {
showCompletedQuestionnaires(clientCode)
enableCompletedQuestionnaireButtons(clientCode)
} else if (isChangeView) {
enableCompletedQuestionnaireButtons(clientCode)
}
} }
} }
} }
@ -517,4 +508,56 @@ class HandlerOpeningScreen(private val activity: MainActivity) {
} }
} }
} private fun setupSaveButton() {
saveButton.text = LanguageManager.getText(languageID, "save")
saveButton.setOnClickListener {
val clientCode = editText.text.toString().trim()
if (clientCode.isBlank()) {
val message = LanguageManager.getText(languageID, "please_client_code")
Toast.makeText(activity, message, Toast.LENGTH_SHORT).show()
return@setOnClickListener
}
GlobalValues.LAST_CLIENT_CODE = clientCode
showCompletedQuestionnaires(clientCode)
}
}
private fun setupEditButton() {
editButton.text = LanguageManager.getText(languageID, "edit")
editButton.setOnClickListener {
val clientCode = editText.text.toString().trim()
if (clientCode.isBlank()) {
val message = LanguageManager.getText(languageID, "please_client_code")
Toast.makeText(activity, message, Toast.LENGTH_SHORT).show()
return@setOnClickListener
}
GlobalValues.LAST_CLIENT_CODE = clientCode
CoroutineScope(Dispatchers.IO).launch {
val completedEntries = MyApp.database.completedQuestionnaireDao().getAllForClient(clientCode)
val completedFiles = completedEntries
.filter { it.isDone }
.map { it.questionnaireId.lowercase() }
buttonPoints.clear()
for (entry in completedEntries) {
if (entry.isDone) {
buttonPoints[entry.questionnaireId] = entry.sumPoints ?: 0
}
}
withContext(Dispatchers.Main) {
updateButtonTexts()
val enabledButtons = questionnaireFiles.filter { (_, fileName) ->
completedFiles.any { completedId -> fileName.lowercase().contains(completedId) }
}.keys.toList()
setButtonsEnabled(enabledButtons)
}
}
}
}
}

View File

@ -19,7 +19,6 @@ class HandlerRadioQuestion(
private lateinit var layout: View private lateinit var layout: View
private lateinit var question: QuestionItem.RadioQuestion private lateinit var question: QuestionItem.RadioQuestion
// Bind the question data to the view
override fun bind(layout: View, question: QuestionItem) { override fun bind(layout: View, question: QuestionItem) {
this.layout = layout this.layout = layout
this.question = question as QuestionItem.RadioQuestion this.question = question as QuestionItem.RadioQuestion
@ -28,17 +27,14 @@ class HandlerRadioQuestion(
val questionTextView = layout.findViewById<TextView>(R.id.textView) val questionTextView = layout.findViewById<TextView>(R.id.textView)
val questionTitle = layout.findViewById<TextView>(R.id.question) val questionTitle = layout.findViewById<TextView>(R.id.question)
// Set question text and optional text key
questionTextView.text = question.textKey?.let { LanguageManager.getText(languageID, it) } ?: "" questionTextView.text = question.textKey?.let { LanguageManager.getText(languageID, it) } ?: ""
questionTitle.text = question.question?.let { questionTitle.text = question.question?.let {
Html.fromHtml(LanguageManager.getText(languageID, it), Html.FROM_HTML_MODE_LEGACY) Html.fromHtml(LanguageManager.getText(languageID, it), Html.FROM_HTML_MODE_LEGACY)
} ?: "" } ?: ""
// Clear previous radio buttons if any
radioGroup.removeAllViews() radioGroup.removeAllViews()
// Dynamically create radio buttons based on options
question.options.forEach { option -> question.options.forEach { option ->
val radioButton = RadioButton(context).apply { val radioButton = RadioButton(context).apply {
text = LanguageManager.getText(languageID, option.key) text = LanguageManager.getText(languageID, option.key)
@ -55,10 +51,8 @@ class HandlerRadioQuestion(
radioGroup.addView(radioButton) radioGroup.addView(radioButton)
} }
// Restore previous answer if one was saved
restorePreviousAnswer(radioGroup) restorePreviousAnswer(radioGroup)
// Handle Next button click
layout.findViewById<Button>(R.id.Qnext).setOnClickListener { layout.findViewById<Button>(R.id.Qnext).setOnClickListener {
if (validate()) { if (validate()) {
saveAnswer() saveAnswer()
@ -67,7 +61,6 @@ class HandlerRadioQuestion(
val selectedRadioButton = layout.findViewById<RadioButton>(selectedId) val selectedRadioButton = layout.findViewById<RadioButton>(selectedId)
val selectedKey = selectedRadioButton.tag.toString() val selectedKey = selectedRadioButton.tag.toString()
// Check if there is a specific next question ID
val nextId = question.options.find { it.key == selectedKey }?.nextQuestionId val nextId = question.options.find { it.key == selectedKey }?.nextQuestionId
if (!nextId.isNullOrEmpty()) { if (!nextId.isNullOrEmpty()) {
@ -80,13 +73,11 @@ class HandlerRadioQuestion(
} }
} }
// Handle Previous button click
layout.findViewById<Button>(R.id.Qprev).setOnClickListener { layout.findViewById<Button>(R.id.Qprev).setOnClickListener {
goToPreviousQuestion() goToPreviousQuestion()
} }
} }
// Restore previously saved answer (if exists)
private fun restorePreviousAnswer(radioGroup: RadioGroup) { private fun restorePreviousAnswer(radioGroup: RadioGroup) {
question.question?.let { questionKey -> question.question?.let { questionKey ->
val savedAnswer = answers[questionKey] as? String val savedAnswer = answers[questionKey] as? String
@ -102,12 +93,10 @@ class HandlerRadioQuestion(
} }
} }
// Validate if any radio button has been selected
override fun validate(): Boolean { override fun validate(): Boolean {
return layout.findViewById<RadioGroup>(R.id.RadioGroup).checkedRadioButtonId != -1 return layout.findViewById<RadioGroup>(R.id.RadioGroup).checkedRadioButtonId != -1
} }
// Save selected answer and update points list
override fun saveAnswer() { override fun saveAnswer() {
val radioGroup = layout.findViewById<RadioGroup>(R.id.RadioGroup) val radioGroup = layout.findViewById<RadioGroup>(R.id.RadioGroup)
val selectedId = radioGroup.checkedRadioButtonId val selectedId = radioGroup.checkedRadioButtonId
@ -116,17 +105,13 @@ class HandlerRadioQuestion(
question.question?.let { questionKey -> question.question?.let { questionKey ->
// Alte Antwort und Punkt ermitteln
val oldAnswerKey = answers[questionKey] as? String val oldAnswerKey = answers[questionKey] as? String
val oldPoint = oldAnswerKey?.let { question.pointsMap?.get(it) } ?: 0 val oldPoint = oldAnswerKey?.let { question.pointsMap?.get(it) } ?: 0
// Alten Punkt entfernen, falls vorhanden
points.remove(oldPoint) points.remove(oldPoint)
// Neue Antwort speichern
answers[questionKey] = answerKey answers[questionKey] = answerKey
// Neuen Punkt ermitteln und hinzufügen
val newPoint = question.pointsMap?.get(answerKey) ?: 0 val newPoint = question.pointsMap?.get(answerKey) ?: 0
points.add(newPoint) points.add(newPoint)
} }

View File

@ -324,7 +324,9 @@ object LanguageManager {
"choose_more_elements" to "Es müssen mehr Elemenete ausgewählt werden.", "choose_more_elements" to "Es müssen mehr Elemenete ausgewählt werden.",
"please_client_code" to "Bitte Klienten Code eingeben", "please_client_code" to "Bitte Klienten Code eingeben",
"no_profile" to "Dieser Klient ist noch nicht Teil der Datenbank", "no_profile" to "Dieser Klient ist noch nicht Teil der Datenbank",
"questionnaires_finished" to "Für diesen Klienten wurden alle Fragebögen ausgefüllt!" "questionnaires_finished" to "Für diesen Klienten wurden alle Fragebögen ausgefüllt!",
"edit" to "Bearbeiten",
"save" to "Speichern"
), ),
@ -617,7 +619,9 @@ object LanguageManager {
"choose_more_elements" to "More elements must be selected.", "choose_more_elements" to "More elements must be selected.",
"please_client_code" to "Please enter client code", "please_client_code" to "Please enter client code",
"no_profile" to "This client is not yet part of the database", "no_profile" to "This client is not yet part of the database",
"questionnaires_finished" to "All questionnaires have been completed for this client!" "questionnaires_finished" to "All questionnaires have been completed for this client!",
"edit" to "Bearbeiten",
"save" to "Speichern"
), ),
"FRENCH" to mapOf( "FRENCH" to mapOf(
@ -913,7 +917,9 @@ object LanguageManager {
"choose_more_elements" to "Plus déléments doivent être sélectionnés.", "choose_more_elements" to "Plus déléments doivent être sélectionnés.",
"please_client_code" to "Veuillez saisir le code client", "please_client_code" to "Veuillez saisir le code client",
"no_profile" to "Ce client ne fait pas encore partie de la base de données", "no_profile" to "Ce client ne fait pas encore partie de la base de données",
"questionnaires_finished" to "Tous les questionnaires ont été complétés pour ce client !" "questionnaires_finished" to "Tous les questionnaires ont été complétés pour ce client !",
"edit" to "Modifier",
"save" to "Sauvegarder"
), ),
"RUSSIAN" to mapOf( "RUSSIAN" to mapOf(
@ -1205,7 +1211,9 @@ object LanguageManager {
"choose_more_elements" to "Необходимо выбрать больше элементов.", "choose_more_elements" to "Необходимо выбрать больше элементов.",
"please_client_code" to "Пожалуйста, введите код клиента", "please_client_code" to "Пожалуйста, введите код клиента",
"no_profile" to "Этот клиент еще не внесен в базу данных", "no_profile" to "Этот клиент еще не внесен в базу данных",
"questionnaires_finished" to "Для этого клиента все анкеты заполнены!" "questionnaires_finished" to "Для этого клиента все анкеты заполнены!",
"edit" to "Редактировать",
"save" to "Сохранять"
), ),
"UKRAINIAN" to mapOf( "UKRAINIAN" to mapOf(
@ -1501,7 +1509,9 @@ object LanguageManager {
"choose_more_elements" to "Потрібно вибрати більше елементів.", "choose_more_elements" to "Потрібно вибрати більше елементів.",
"please_client_code" to "Будь ласка, введіть код клієнта", "please_client_code" to "Будь ласка, введіть код клієнта",
"no_profile" to "Цей клієнт ще не внесений до бази даних", "no_profile" to "Цей клієнт ще не внесений до бази даних",
"questionnaires_finished" to "Для цього клієнта всі опитувальники заповнені!" "questionnaires_finished" to "Для цього клієнта всі опитувальники заповнені!",
"edit" to "Редагувати",
"save" to "Зберегти"
), ),
"TURKISH" to mapOf( "TURKISH" to mapOf(
@ -1797,7 +1807,9 @@ object LanguageManager {
"choose_more_elements" to "Daha fazla öğe seçilmesi gerekiyor.", "choose_more_elements" to "Daha fazla öğe seçilmesi gerekiyor.",
"please_client_code" to "Lütfen danışan kodunu girin", "please_client_code" to "Lütfen danışan kodunu girin",
"no_profile" to "Bu danışan henüz veritabanında kayıtlı değil", "no_profile" to "Bu danışan henüz veritabanında kayıtlı değil",
"questionnaires_finished" to "Bu danışan için tüm anketler doldurulmuştur!" "questionnaires_finished" to "Bu danışan için tüm anketler doldurulmuştur!",
"edit" to "Düzenlemek",
"save" to "Kaydetmek"
), ),
"POLISH" to mapOf( "POLISH" to mapOf(
@ -2093,7 +2105,9 @@ object LanguageManager {
"choose_more_elements" to "Należy wybrać więcej elementów.", "choose_more_elements" to "Należy wybrać więcej elementów.",
"please_client_code" to "Proszę wprowadzić kod klienta", "please_client_code" to "Proszę wprowadzić kod klienta",
"no_profile" to "Ten klient nie jest jeszcze częścią bazy danych", "no_profile" to "Ten klient nie jest jeszcze częścią bazy danych",
"questionnaires_finished" to "Dla tego klienta wypełniono wszystkie kwestionariusze!" "questionnaires_finished" to "Dla tego klienta wypełniono wszystkie kwestionariusze!",
"edit" to "Redagować",
"save" to "Ratować"
), ),
"ARABIC" to mapOf( "ARABIC" to mapOf(
@ -2389,7 +2403,9 @@ object LanguageManager {
"choose_more_elements" to "يجب اختيار المزيد من العناصر.", "choose_more_elements" to "يجب اختيار المزيد من العناصر.",
"please_client_code" to "يرجى إدخال رمز العميل", "please_client_code" to "يرجى إدخال رمز العميل",
"no_profile" to "هذا العميل غير موجود في قاعدة البيانات بعد", "no_profile" to "هذا العميل غير موجود في قاعدة البيانات بعد",
"questionnaires_finished" to "تم الانتهاء من جميع الاستبيانات لهذا العميل!" "questionnaires_finished" to "تم الانتهاء من جميع الاستبيانات لهذا العميل!",
"edit" to "يحرر",
"save" to "يحفظ"
), ),
"ROMANIAN" to mapOf( "ROMANIAN" to mapOf(
@ -2685,7 +2701,9 @@ object LanguageManager {
"choose_more_elements" to "Trebuie selectate mai multe elemente.", "choose_more_elements" to "Trebuie selectate mai multe elemente.",
"please_client_code" to "Vă rugăm să introduceți codul clientului", "please_client_code" to "Vă rugăm să introduceți codul clientului",
"no_profile" to "Acest client nu face încă parte din baza de date", "no_profile" to "Acest client nu face încă parte din baza de date",
"questionnaires_finished" to "Toate chestionarele pentru acest client au fost completate!" "questionnaires_finished" to "Toate chestionarele pentru acest client au fost completate!",
"edit" to "Editizel",
"save" to "Xastral"
), ),
"SPANISH" to mapOf( "SPANISH" to mapOf(
@ -2981,7 +2999,9 @@ object LanguageManager {
"choose_more_elements" to "Se deben seleccionar más elementos.", "choose_more_elements" to "Se deben seleccionar más elementos.",
"please_client_code" to "Por favor, ingrese el código de cliente", "please_client_code" to "Por favor, ingrese el código de cliente",
"no_profile" to "Este cliente aún no forma parte de la base de datos", "no_profile" to "Este cliente aún no forma parte de la base de datos",
"questionnaires_finished" to "Se han completado todos los cuestionarios para este cliente." "questionnaires_finished" to "Se han completado todos los cuestionarios para este cliente.",
"edit" to "Editar",
"save" to "Ahorrar"
) )
) )
} }

View File

@ -11,7 +11,6 @@ import kotlinx.coroutines.*
import java.io.File import java.io.File
import java.util.Calendar import java.util.Calendar
// Global constants and values
const val MAX_VALUE_AGE = 122 const val MAX_VALUE_AGE = 122
val MAX_VALUE_YEAR = Calendar.getInstance().get(Calendar.YEAR) val MAX_VALUE_YEAR = Calendar.getInstance().get(Calendar.YEAR)
@ -21,11 +20,9 @@ object GlobalValues {
var LOADED_CLIENT_CODE: String? = null var LOADED_CLIENT_CODE: String? = null
} }
// Data classes for questionnaire metadata
data class QuestionnaireMeta(val id: String) data class QuestionnaireMeta(val id: String)
data class QuestionnaireData(val meta: QuestionnaireMeta, val questions: List<QuestionItem>) data class QuestionnaireData(val meta: QuestionnaireMeta, val questions: List<QuestionItem>)
// Abstract base class for questionnaire
abstract class QuestionnaireBase<T> { abstract class QuestionnaireBase<T> {
abstract fun startQuestionnaire() abstract fun startQuestionnaire()
abstract fun showCurrentQuestion() abstract fun showCurrentQuestion()
@ -35,7 +32,6 @@ abstract class QuestionnaireBase<T> {
this.languageID = language this.languageID = language
} }
// Protected properties
protected lateinit var questionnaireMeta: QuestionnaireMeta protected lateinit var questionnaireMeta: QuestionnaireMeta
protected var currentIndex = 0 protected var currentIndex = 0
protected lateinit var questions: List<QuestionItem> protected lateinit var questions: List<QuestionItem>
@ -45,7 +41,6 @@ abstract class QuestionnaireBase<T> {
protected lateinit var context: Activity protected lateinit var context: Activity
private val navigationHistory = mutableListOf<Int>() private val navigationHistory = mutableListOf<Int>()
// Utility functions
protected fun showToast(message: String) { protected fun showToast(message: String) {
Toast.makeText(context, message, Toast.LENGTH_SHORT).show() Toast.makeText(context, message, Toast.LENGTH_SHORT).show()
} }
@ -54,7 +49,6 @@ abstract class QuestionnaireBase<T> {
return fields.all { it.text.toString().trim().isNotEmpty() } return fields.all { it.text.toString().trim().isNotEmpty() }
} }
// Replace placeholders in file content and save updated file
protected fun updateFileContent(fileName: String, placeholders: Map<String, Any?>) { protected fun updateFileContent(fileName: String, placeholders: Map<String, Any?>) {
val file = File(context.filesDir, fileName) val file = File(context.filesDir, fileName)
if (file.exists()) { if (file.exists()) {
@ -66,7 +60,6 @@ abstract class QuestionnaireBase<T> {
} }
} }
// Setup spinner UI element
protected fun setupSpinner(spinner: Spinner, spinnerValues: List<Any>, selectedValue: Any?) { protected fun setupSpinner(spinner: Spinner, spinnerValues: List<Any>, selectedValue: Any?) {
val adapter = ArrayAdapter(context, android.R.layout.simple_spinner_item, spinnerValues).apply { val adapter = ArrayAdapter(context, android.R.layout.simple_spinner_item, spinnerValues).apply {
setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item) setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item)
@ -80,7 +73,6 @@ abstract class QuestionnaireBase<T> {
} }
} }
// Navigate to specific layout
protected fun navigateTo(layoutResId: Int, setup: (View) -> Unit) { protected fun navigateTo(layoutResId: Int, setup: (View) -> Unit) {
context.setContentView(layoutResId) context.setContentView(layoutResId)
val rootView = context.findViewById<View>(android.R.id.content) val rootView = context.findViewById<View>(android.R.id.content)
@ -97,17 +89,14 @@ abstract class QuestionnaireBase<T> {
} }
} }
// Resolve layout resource ID from layout name
protected fun getLayoutResId(layoutName: String?): Int { protected fun getLayoutResId(layoutName: String?): Int {
return layoutName?.let { return layoutName?.let {
context.resources.getIdentifier(it, "layout", context.packageName) context.resources.getIdentifier(it, "layout", context.packageName)
} ?: 0 } ?: 0
} }
// Calculate sum of all points
protected fun calculateIntegrationIndex(): Int = points.sum() protected fun calculateIntegrationIndex(): Int = points.sum()
// Navigation logic
protected fun goToPreviousQuestion() { protected fun goToPreviousQuestion() {
if (navigationHistory.isNotEmpty()) { if (navigationHistory.isNotEmpty()) {
currentIndex = navigationHistory.removeAt(navigationHistory.size - 1) currentIndex = navigationHistory.removeAt(navigationHistory.size - 1)
@ -134,7 +123,6 @@ abstract class QuestionnaireBase<T> {
} }
} }
// Load questionnaire data from JSON
protected fun loadQuestionnaireFromJson(filename: String): Pair<QuestionnaireMeta, List<QuestionItem>> { protected fun loadQuestionnaireFromJson(filename: String): Pair<QuestionnaireMeta, List<QuestionItem>> {
val jsonString = context.assets.open(filename).bufferedReader().use { it.readText() } val jsonString = context.assets.open(filename).bufferedReader().use { it.readText() }
val gson = Gson() val gson = Gson()
@ -165,9 +153,6 @@ abstract class QuestionnaireBase<T> {
return Pair(meta, questionsList) return Pair(meta, questionsList)
} }
// Factory to create handler for each question type
protected open fun createHandlerForQuestion(question: QuestionItem): QuestionHandler? { protected open fun createHandlerForQuestion(question: QuestionItem): QuestionHandler? {
return when (question) { return when (question) {
is QuestionItem.RadioQuestion -> HandlerRadioQuestion(context, answers, points, languageID, ::goToNextQuestion, ::goToPreviousQuestion, ::goToQuestionById, ::showToast) is QuestionItem.RadioQuestion -> HandlerRadioQuestion(context, answers, points, languageID, ::goToNextQuestion, ::goToPreviousQuestion, ::goToQuestionById, ::showToast)
@ -185,7 +170,6 @@ abstract class QuestionnaireBase<T> {
} }
} }
// Persist answers into database
suspend fun saveAnswersToDatabase(answers: Map<String, Any>, questionnaireId: String) { suspend fun saveAnswersToDatabase(answers: Map<String, Any>, questionnaireId: String) {
Log.d("AnswersMap", answers.toString()) Log.d("AnswersMap", answers.toString())
val db = MyApp.database val db = MyApp.database
@ -241,8 +225,6 @@ abstract class QuestionnaireBase<T> {
db.completedQuestionnaireDao().insert(completedEntry) db.completedQuestionnaireDao().insert(completedEntry)
} }
fun endQuestionnaire() { fun endQuestionnaire() {
(context as? MainActivity)?.finishQuestionnaire() (context as? MainActivity)?.finishQuestionnaire()
} }

View File

@ -44,16 +44,32 @@
app:layout_constraintStart_toStartOf="parent" app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@id/textView" /> app:layout_constraintTop_toBottomOf="@id/textView" />
<Button <Button
android:id="@+id/loadButton" android:id="@+id/loadButton"
android:layout_width="166dp" android:layout_width="72dp"
android:layout_height="52dp" android:layout_height="52dp"
app:layout_constraintTop_toBottomOf="@id/editText"
app:layout_constraintStart_toStartOf="@id/editText"
app:layout_constraintEnd_toEndOf="@id/editText" app:layout_constraintEnd_toEndOf="@id/editText"
app:layout_constraintHorizontal_bias="0.5" app:layout_constraintHorizontal_bias="0.0"
/> app:layout_constraintStart_toStartOf="@id/editText"
app:layout_constraintTop_toBottomOf="@id/editText" />
<Button
android:id="@+id/editButton"
android:layout_width="72dp"
android:layout_height="52dp"
app:layout_constraintEnd_toEndOf="@id/editText"
app:layout_constraintStart_toStartOf="@id/editText"
app:layout_constraintTop_toBottomOf="@id/editText" />
<Button
android:id="@+id/saveButton"
android:layout_width="72dp"
android:layout_height="52dp"
app:layout_constraintEnd_toEndOf="@id/editText"
app:layout_constraintHorizontal_bias="1.0"
app:layout_constraintStart_toStartOf="@id/editText"
app:layout_constraintTop_toBottomOf="@id/editText" />
<TextView <TextView
android:id="@+id/textView" android:id="@+id/textView"