Skip to content

Commit

Permalink
Review screen ux changes like font style, error icon, not answered te…
Browse files Browse the repository at this point in the history
…xt color etc. (#1737)

* Reduce question text font size in review mode.

* Do not show help button in review mode.

* Add error icon if text is not answered and set error color to text if value is not answered.

* spotless apply

* update not answered text color.

* Address review comment.

* Remove test which is not required.

* Address review comments.

* Address review comments.

* Address review comment.

* Fix failed test.

* Address review comments.

* Refactored the code

* Refactore the file name.

* Fix local unit tests.

* Address review comment.

* Add more unit tests.

* Remove updateUI call from on review button click.

---------

Co-authored-by: Santosh Pingle <[email protected]>
  • Loading branch information
santosh-pingle and Santosh Pingle authored Mar 10, 2023
1 parent f87fa04 commit 5937cc6
Show file tree
Hide file tree
Showing 11 changed files with 497 additions and 91 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -606,7 +606,8 @@ internal class QuestionnaireViewModel(application: Application, state: SavedStat
// Determine the validation result, which will be displayed on the item itself
val validationResult =
if (modifiedQuestionnaireResponseItemSet.contains(questionnaireResponseItem) ||
forceValidation
forceValidation ||
isInReviewModeFlow.value
) {
QuestionnaireResponseItemValidator.validate(
questionnaireItem,
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
/*
* Copyright 2022 Google LLC
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

package com.google.android.fhir.datacapture.extensions

import android.text.Spanned
import android.view.View.GONE
import android.view.View.VISIBLE
import android.widget.Button
import android.widget.TextView
import com.google.android.fhir.datacapture.hasHelpButton
import com.google.android.fhir.datacapture.localizedHelpSpanned
import com.google.android.material.card.MaterialCardView
import org.hl7.fhir.r4.model.Questionnaire

internal fun TextView.updateTextAndVisibility(localizedText: Spanned? = null) {
text = localizedText
visibility =
if (localizedText.isNullOrEmpty()) {
GONE
} else {
VISIBLE
}
}

/** Returns [VISIBLE] if any of the [view] is visible, [GONE] otherwise. */
internal fun getHeaderViewVisibility(vararg view: TextView): Int {
if (view.any { it.visibility == VISIBLE }) {
return VISIBLE
}
return GONE
}

/**
* Initializes the text for [helpTextView] with instructions on how to use the feature, and sets the
* visibility and click listener for the [helpButton] to allow users to access the help information
* and toggles the visibility for view [helpCardView].
*/
internal fun initHelpViews(
helpButton: Button,
helpCardView: MaterialCardView,
helpTextView: TextView,
questionnaireItem: Questionnaire.QuestionnaireItemComponent
) {
helpButton.visibility =
if (questionnaireItem.hasHelpButton) {
VISIBLE
} else {
GONE
}
helpButton.setOnClickListener {
helpCardView.visibility =
when (helpCardView.visibility) {
VISIBLE -> GONE
else -> VISIBLE
}
}
helpTextView.updateTextAndVisibility(questionnaireItem.localizedHelpSpanned)
}
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,9 @@ import android.view.LayoutInflater
import android.widget.LinearLayout
import android.widget.TextView
import com.google.android.fhir.datacapture.R
import com.google.android.fhir.datacapture.extensions.getHeaderViewVisibility
import com.google.android.fhir.datacapture.extensions.initHelpViews
import com.google.android.fhir.datacapture.extensions.updateTextAndVisibility
import com.google.android.fhir.datacapture.localizedInstructionsSpanned
import com.google.android.fhir.datacapture.localizedPrefixSpanned
import com.google.android.fhir.datacapture.localizedTextSpanned
Expand All @@ -38,10 +41,15 @@ internal class GroupHeaderView(context: Context, attrs: AttributeSet?) :
val prefix = findViewById<TextView>(R.id.prefix)
val question = findViewById<TextView>(R.id.question)
val hint = findViewById<TextView>(R.id.hint)
initHelpButton(this, questionnaireItem)
initHelpViews(
helpButton = findViewById(R.id.helpButton),
helpCardView = findViewById(R.id.helpCardView),
helpTextView = findViewById(R.id.helpText),
questionnaireItem
)
prefix.updateTextAndVisibility(questionnaireItem.localizedPrefixSpanned)
question.updateTextAndVisibility(questionnaireItem.localizedTextSpanned)
hint.updateTextAndVisibility(questionnaireItem.localizedInstructionsSpanned)
visibility = getViewGroupVisibility(prefix, question, hint)
visibility = getHeaderViewVisibility(prefix, question, hint)
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -17,22 +17,17 @@
package com.google.android.fhir.datacapture.views

import android.content.Context
import android.text.Spanned
import android.util.AttributeSet
import android.view.LayoutInflater
import android.view.View
import android.view.View.GONE
import android.view.View.VISIBLE
import android.widget.Button
import android.widget.LinearLayout
import android.widget.TextView
import com.google.android.fhir.datacapture.R
import com.google.android.fhir.datacapture.hasHelpButton
import com.google.android.fhir.datacapture.localizedHelpSpanned
import com.google.android.fhir.datacapture.extensions.getHeaderViewVisibility
import com.google.android.fhir.datacapture.extensions.initHelpViews
import com.google.android.fhir.datacapture.extensions.updateTextAndVisibility
import com.google.android.fhir.datacapture.localizedInstructionsSpanned
import com.google.android.fhir.datacapture.localizedPrefixSpanned
import com.google.android.fhir.datacapture.localizedTextSpanned
import com.google.android.material.card.MaterialCardView
import org.hl7.fhir.r4.model.Questionnaire

/** View for the prefix, question, and hint of a questionnaire item. */
Expand All @@ -51,10 +46,15 @@ internal class HeaderView(context: Context, attrs: AttributeSet?) : LinearLayout
prefix.updateTextAndVisibility(questionnaireItem.localizedPrefixSpanned)
question.updateTextAndVisibility(questionnaireItem.localizedTextSpanned)
hint.updateTextAndVisibility(questionnaireItem.localizedInstructionsSpanned)
initHelpButton(this, questionnaireItem)
initHelpViews(
helpButton = findViewById(R.id.helpButton),
helpCardView = findViewById(R.id.helpCardView),
helpTextView = findViewById(R.id.helpText),
questionnaireItem
)
// Make the entire view GONE if there is nothing to show. This is to avoid an empty row in the
// questionnaire.
visibility = getViewGroupVisibility(prefix, question, hint)
visibility = getHeaderViewVisibility(prefix, question, hint)
}

/**
Expand All @@ -74,49 +74,3 @@ internal class HeaderView(context: Context, attrs: AttributeSet?) : LinearLayout
errorTextView.text = errorText
}
}

internal fun TextView.updateTextAndVisibility(localizedText: Spanned? = null) {
text = localizedText
visibility =
if (localizedText.isNullOrEmpty()) {
GONE
} else {
VISIBLE
}
}

/** Returns [VISIBLE] if any of the [view] is visible, else returns [GONE]. */
internal fun getViewGroupVisibility(vararg view: TextView): Int {
if (view.any { it.visibility == VISIBLE }) {
return VISIBLE
}
return GONE
}

internal fun initHelpButton(
view: View,
questionnaireItem: Questionnaire.QuestionnaireItemComponent
) {
val helpButton = view.findViewById<Button>(R.id.helpButton)
helpButton.visibility =
if (questionnaireItem.hasHelpButton) {
VISIBLE
} else {
GONE
}
val helpCardView = view.findViewById<MaterialCardView>(R.id.helpCardView)
var isHelpCardViewVisible = false
helpButton.setOnClickListener {
if (isHelpCardViewVisible) {
isHelpCardViewVisible = false
helpCardView.visibility = GONE
} else {
isHelpCardViewVisible = true
helpCardView.visibility = VISIBLE
}
}

view
.findViewById<TextView>(R.id.helpText)
.updateTextAndVisibility(questionnaireItem.localizedHelpSpanned)
}
Original file line number Diff line number Diff line change
Expand Up @@ -20,9 +20,15 @@ import android.view.View
import android.view.View.GONE
import android.view.View.VISIBLE
import android.widget.TextView
import androidx.constraintlayout.widget.ConstraintLayout
import com.google.android.fhir.datacapture.R
import com.google.android.fhir.datacapture.extensions.getHeaderViewVisibility
import com.google.android.fhir.datacapture.extensions.updateTextAndVisibility
import com.google.android.fhir.datacapture.localizedFlyoverSpanned
import com.google.android.fhir.datacapture.views.HeaderView
import com.google.android.fhir.datacapture.localizedInstructionsSpanned
import com.google.android.fhir.datacapture.localizedPrefixSpanned
import com.google.android.fhir.datacapture.localizedTextSpanned
import com.google.android.fhir.datacapture.validation.Invalid
import com.google.android.fhir.datacapture.views.QuestionnaireViewItem
import com.google.android.material.divider.MaterialDivider
import org.hl7.fhir.r4.model.Questionnaire
Expand All @@ -34,21 +40,39 @@ import org.hl7.fhir.r4.model.Questionnaire
internal object ReviewViewHolderFactory : QuestionnaireItemViewHolderFactory(R.layout.review_view) {
override fun getQuestionnaireItemViewHolderDelegate() =
object : QuestionnaireItemViewHolderDelegate {
private lateinit var header: HeaderView
private lateinit var header: ConstraintLayout
private lateinit var flyOverTextView: TextView
private lateinit var answerTextView: TextView
private lateinit var errorView: View
private lateinit var answerView: TextView
private lateinit var divider: MaterialDivider
private lateinit var prefix: TextView
private lateinit var question: TextView
private lateinit var hint: TextView
override lateinit var questionnaireViewItem: QuestionnaireViewItem

override fun init(itemView: View) {
header = itemView.findViewById(R.id.header)
flyOverTextView = itemView.findViewById(R.id.flyover_text_view)
answerTextView = itemView.findViewById(R.id.answer_text_view)
divider = itemView.findViewById(R.id.text_divider)
prefix = itemView.findViewById(R.id.prefix)
question = itemView.findViewById(R.id.question)
hint = itemView.findViewById(R.id.hint)
errorView = itemView.findViewById(R.id.error_view)
answerView = itemView.findViewById(R.id.answer_text_view)
}

override fun bind(questionnaireViewItem: QuestionnaireViewItem) {
header.bind(questionnaireViewItem.questionnaireItem)
prefix.updateTextAndVisibility(
questionnaireViewItem.questionnaireItem.localizedPrefixSpanned
)
question.updateTextAndVisibility(
questionnaireViewItem.questionnaireItem.localizedTextSpanned
)
hint.updateTextAndVisibility(
questionnaireViewItem.questionnaireItem.localizedInstructionsSpanned
)
header.visibility = getHeaderViewVisibility(prefix, question, hint)

val localizedFlyoverSpanned =
questionnaireViewItem.questionnaireItem.localizedFlyoverSpanned
flyOverTextView.apply {
Expand All @@ -60,21 +84,30 @@ internal object ReviewViewHolderFactory : QuestionnaireItemViewHolderFactory(R.l
}
text = localizedFlyoverSpanned
}

answerTextView.apply {
visibility =
when (questionnaireViewItem.questionnaireItem.type) {
Questionnaire.QuestionnaireItemType.GROUP,
Questionnaire.QuestionnaireItemType.DISPLAY -> GONE
else -> VISIBLE
when (questionnaireViewItem.questionnaireItem.type) {
Questionnaire.QuestionnaireItemType.GROUP,
Questionnaire.QuestionnaireItemType.DISPLAY -> {
errorView.visibility = GONE
answerView.visibility = GONE
}
else -> {
answerView.text = questionnaireViewItem.answerString(answerView.context)
answerView.visibility = VISIBLE
if (questionnaireViewItem.validationResult is Invalid) {
errorView.findViewById<TextView>(R.id.error_text_view).text =
questionnaireViewItem.validationResult.getSingleStringValidationMessage()
errorView.visibility = VISIBLE
} else {
errorView.visibility = GONE
}
text = questionnaireViewItem.answerString(context)
}
}

divider.visibility =
if (header.visibility == VISIBLE ||
flyOverTextView.visibility == VISIBLE ||
answerTextView.visibility == VISIBLE
answerView.visibility == VISIBLE ||
errorView.visibility == VISIBLE
) {
VISIBLE
} else {
Expand Down
14 changes: 14 additions & 0 deletions datacapture/src/main/res/drawable/ic_error_48px.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
<vector
android:autoMirrored="true"
android:height="48dp"
android:tint="?attr/colorControlNormal"
android:viewportHeight="48"
android:viewportWidth="48"
android:width="48dp"
xmlns:android="http://schemas.android.com/apk/res/android"
>
<path
android:fillColor="@android:color/white"
android:pathData="M24,34Q24.7,34 25.175,33.525Q25.65,33.05 25.65,32.35Q25.65,31.65 25.175,31.175Q24.7,30.7 24,30.7Q23.3,30.7 22.825,31.175Q22.35,31.65 22.35,32.35Q22.35,33.05 22.825,33.525Q23.3,34 24,34ZM22.65,26.35H25.65V13.7H22.65ZM24,44Q19.9,44 16.25,42.425Q12.6,40.85 9.875,38.125Q7.15,35.4 5.575,31.75Q4,28.1 4,23.95Q4,19.85 5.575,16.2Q7.15,12.55 9.875,9.85Q12.6,7.15 16.25,5.575Q19.9,4 24.05,4Q28.15,4 31.8,5.575Q35.45,7.15 38.15,9.85Q40.85,12.55 42.425,16.2Q44,19.85 44,24Q44,28.1 42.425,31.75Q40.85,35.4 38.15,38.125Q35.45,40.85 31.8,42.425Q28.15,44 24,44Z"
/>
</vector>
Loading

0 comments on commit 5937cc6

Please sign in to comment.