Skip to content

Commit

Permalink
Refactor implementation
Browse files Browse the repository at this point in the history
- Simpler logic
- Use `questionnaireViewItem.answers` state to check if the AutoComplete is editable or not
- Remove keyListener to make the AutoComplete not editable
- Hide keyboard after typing an option then clicking the shown option
- Add cool ripple effect on clicking the clear input icon
  • Loading branch information
FikriMilano committed Jan 28, 2025
1 parent 7e68464 commit 079ad81
Show file tree
Hide file tree
Showing 4 changed files with 75 additions and 85 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -18,15 +18,18 @@ package com.google.android.fhir.datacapture.views.factories

import android.content.Context
import android.graphics.drawable.Drawable
import android.text.InputType
import android.text.Spanned
import android.text.method.TextKeyListener
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import android.view.inputmethod.InputMethodManager
import android.widget.AdapterView
import android.widget.ArrayAdapter
import android.widget.ImageView
import android.widget.FrameLayout
import android.widget.TextView
import androidx.appcompat.app.AppCompatActivity
import androidx.core.widget.doAfterTextChanged
import androidx.lifecycle.lifecycleScope
import com.google.android.fhir.datacapture.R
import com.google.android.fhir.datacapture.extensions.displayString
Expand All @@ -35,12 +38,14 @@ import com.google.android.fhir.datacapture.extensions.getValidationErrorMessage
import com.google.android.fhir.datacapture.extensions.identifierString
import com.google.android.fhir.datacapture.extensions.itemAnswerOptionImage
import com.google.android.fhir.datacapture.extensions.localizedFlyoverSpanned
import com.google.android.fhir.datacapture.extensions.toSpanned
import com.google.android.fhir.datacapture.extensions.tryUnwrapContext
import com.google.android.fhir.datacapture.validation.ValidationResult
import com.google.android.fhir.datacapture.views.HeaderView
import com.google.android.fhir.datacapture.views.QuestionnaireViewItem
import com.google.android.material.textfield.MaterialAutoCompleteTextView
import com.google.android.material.textfield.TextInputLayout
import kotlinx.coroutines.delay
import kotlinx.coroutines.launch
import org.hl7.fhir.r4.model.QuestionnaireResponse
import timber.log.Timber
Expand All @@ -52,17 +57,21 @@ internal object DropDownViewHolderFactory :
private lateinit var header: HeaderView
private lateinit var textInputLayout: TextInputLayout
private lateinit var autoCompleteTextView: MaterialAutoCompleteTextView
private lateinit var clearIcon: ImageView
private lateinit var clearInputIcon: FrameLayout
override lateinit var questionnaireViewItem: QuestionnaireViewItem
private lateinit var context: AppCompatActivity
private var isDropdownEditable = true

override fun init(itemView: View) {
header = itemView.findViewById(R.id.header)
textInputLayout = itemView.findViewById(R.id.text_input_layout)
autoCompleteTextView = itemView.findViewById(R.id.auto_complete)
clearInputIcon = itemView.findViewById(R.id.clear_input_icon)
context = itemView.context.tryUnwrapContext()!!
clearIcon = itemView.findViewById(R.id.clearIcon)
autoCompleteTextView.setOnFocusChangeListener { view, hasFocus ->
if (!hasFocus) {
(view.context.getSystemService(Context.INPUT_METHOD_SERVICE) as InputMethodManager).hideSoftInputFromWindow(view.windowToken, 0)
}
}
}

override fun bind(questionnaireViewItem: QuestionnaireViewItem) {
Expand Down Expand Up @@ -97,8 +106,8 @@ internal object DropDownViewHolderFactory :
answerOptionList
.firstOrNull { it.answerId == selectedAnswerIdentifier }
?.let {
autoCompleteTextView.setText(it.answerOptionString)
autoCompleteTextView.setSelection(it.answerOptionString.length)
autoCompleteTextView.setText(it.answerOptionStringSpanned())
autoCompleteTextView.setSelection(it.answerOptionStringSpanned().length)
autoCompleteTextView.setCompoundDrawablesRelative(
it.answerOptionImage,
null,
Expand All @@ -109,68 +118,41 @@ internal object DropDownViewHolderFactory :
autoCompleteTextView.setAdapter(adapter)
autoCompleteTextView.onItemClickListener =
AdapterView.OnItemClickListener { _, _, position, _ ->
if (isDropdownEditable) {
val selectedItem = adapter.getItem(position)
autoCompleteTextView.setText(selectedItem?.answerOptionString, false)
autoCompleteTextView.setCompoundDrawablesRelative(
adapter.getItem(position)?.answerOptionImage,
null,
null,
null,
)

isDropdownEditable = false
val selectedAnswer =
questionnaireViewItem.enabledAnswerOptions
.firstOrNull { it.value.identifierString(context) == selectedItem?.answerId }
?.value

context.lifecycleScope.launch {
if (selectedAnswer == null) {
questionnaireViewItem.clearAnswer()
} else {
questionnaireViewItem.setAnswer(
QuestionnaireResponse.QuestionnaireResponseItemAnswerComponent()
.setValue(selectedAnswer),
)
}
val selectedItem = adapter.getItem(position)
autoCompleteTextView.setText(selectedItem?.answerOptionStringSpanned(), false)
autoCompleteTextView.setCompoundDrawablesRelative(
adapter.getItem(position)?.answerOptionImage,
null,
null,
null,
)
val selectedAnswer =
questionnaireViewItem.enabledAnswerOptions
.firstOrNull { it.value.identifierString(context) == selectedItem?.answerId }
?.value

context.lifecycleScope.launch {
if (selectedAnswer == null) {
questionnaireViewItem.clearAnswer()
} else {
questionnaireViewItem.setAnswer(
QuestionnaireResponse.QuestionnaireResponseItemAnswerComponent()
.setValue(selectedAnswer),
)
}
}
}

autoCompleteTextView.doAfterTextChanged {
if (it.isNullOrBlank()) {
// Hide the clear icon when the text is empty
clearIcon.visibility = View.GONE

// Delay to ensure dropdown is displayed after text is cleared
// And after MaterialAutoCompleteTextView resets its state
autoCompleteTextView.postDelayed(
{
if (autoCompleteTextView.isPopupShowing.not() && isDropdownEditable) {
autoCompleteTextView.showDropDown()
}
},
100,
)
} else {
// Show the clear icon when the text is not empty
clearIcon.visibility = View.VISIBLE
val isEditable = questionnaireViewItem.answers.isEmpty()
if (!isEditable) autoCompleteTextView.clearFocus()
autoCompleteTextView.keyListener = if (isEditable) TextKeyListener.getInstance() else null
clearInputIcon.visibility = if (isEditable) View.GONE else View.VISIBLE
clearInputIcon.setOnClickListener {
context.lifecycleScope.launch {
delay(200) // to show ripple effect on the icon before clearing the answer
questionnaireViewItem.clearAnswer()
}
}

clearIcon.setOnClickListener {
// Clear the text in the AutoCompleteTextView
autoCompleteTextView.text = null

// Enable dropdown editing after text is cleared
isDropdownEditable = true

// Clear the answer added in the questionnaireViewItem after clearIcon is clicked
context.lifecycleScope.launch { questionnaireViewItem.clearAnswer() }
setReadOnly(false)
}

displayValidationResult(questionnaireViewItem.validationResult)
}

Expand All @@ -185,8 +167,6 @@ internal object DropDownViewHolderFactory :

override fun setReadOnly(isReadOnly: Boolean) {
textInputLayout.isEnabled = !isReadOnly
autoCompleteTextView.isEnabled = isDropdownEditable && !isReadOnly
if (isReadOnly) clearIcon.visibility = View.GONE
}

private fun cleanupOldState() {
Expand All @@ -209,7 +189,7 @@ internal class AnswerOptionDropDownArrayAdapter(
val answerOption: DropDownAnswerOption? = getItem(position)
val answerOptionTextView =
listItemView?.findViewById<View>(R.id.answer_option_textview) as TextView
answerOptionTextView.text = answerOption?.answerOptionString
answerOptionTextView.text = answerOption?.answerOptionStringSpanned()
answerOptionTextView.setCompoundDrawablesRelative(
answerOption?.answerOptionImage,
null,
Expand All @@ -231,4 +211,6 @@ internal data class DropDownAnswerOption(
override fun toString(): String {
return this.answerOptionString
}

fun answerOptionStringSpanned(): Spanned = answerOptionString.toSpanned()
}
7 changes: 4 additions & 3 deletions datacapture/src/main/res/drawable/ic_clear.xml
Original file line number Diff line number Diff line change
@@ -1,12 +1,13 @@
<vector
xmlns:android="http://schemas.android.com/apk/res/android"
android:width="20dp"
android:height="20dp"
android:width="24dp"
android:height="24dp"
android:viewportWidth="24"
android:viewportHeight="24"
android:tint="?attr/colorControlNormal"
>
<path
android:fillColor="#999999"
android:fillColor="@android:color/white"
android:pathData="M19,6.41L17.59,5 12,10.59 6.41,5 5,6.41 10.59,12 5,17.59 6.41,19 12,13.41 17.59,19 19,17.59 13.41,12z"
/>
</vector>
35 changes: 20 additions & 15 deletions datacapture/src/main/res/layout/drop_down_view.xml
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@
-->
<LinearLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginHorizontal="@dimen/item_margin_horizontal"
Expand All @@ -35,37 +35,42 @@

<FrameLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
>
android:layout_height="wrap_content">

<com.google.android.material.textfield.TextInputLayout
android:id="@+id/text_input_layout"
style="?attr/questionnaireDropdownLayoutStyle"
android:layout_width="match_parent"
android:layout_height="wrap_content"
>
android:layout_height="wrap_content">

<com.google.android.material.textfield.MaterialAutoCompleteTextView
android:id="@+id/auto_complete"
style="?attr/questionnaireDropDownSelectedTextStyle"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:drawablePadding="@dimen/icon_drawable_padding"
/>
android:drawablePadding="@dimen/icon_drawable_padding" />

</com.google.android.material.textfield.TextInputLayout>

<ImageView
android:id="@+id/clearIcon"
<FrameLayout
android:id="@+id/clear_input_icon"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:background="?attr/selectableItemBackgroundBorderless"
android:layout_gravity="end|center_vertical"
android:layout_marginTop="@dimen/help_container_margin_top"
android:layout_marginEnd="@dimen/drop_down_clear_icon_margin"
android:padding="@dimen/icon_drawable_padding"
android:src="@drawable/ic_clear"
android:visibility="gone"
/>
android:padding="@dimen/drop_down_clear_input_icon_ripple_padding"
android:layout_marginTop="@dimen/drop_down_clear_icon_margin_top"
android:layout_marginEnd="@dimen/drop_down_clear_icon_margin_end">

<ImageView
android:layout_width="20dp"
android:layout_height="20dp"
android:layout_gravity="center"
android:src="@drawable/ic_clear"
android:scaleType="fitCenter"
app:tint="#999999"/>

</FrameLayout>

</FrameLayout>

Expand Down
4 changes: 3 additions & 1 deletion datacapture/src/main/res/values/dimens.xml
Original file line number Diff line number Diff line change
Expand Up @@ -95,7 +95,9 @@

<!-- Dropdown -->
<dimen name="drop_down_padding">16dp</dimen>
<dimen name="drop_down_clear_icon_margin">28dp</dimen>
<dimen name="drop_down_clear_icon_margin_end">38dp</dimen>
<dimen name="drop_down_clear_icon_margin_top">4dp</dimen>
<dimen name="drop_down_clear_input_icon_ripple_padding">4dp</dimen>

<!-- Item Answer Media -->
<dimen name="item_answer_media_image_size">48dp</dimen>
Expand Down

0 comments on commit 079ad81

Please sign in to comment.