From d9653b61d03eb3b1d4a570c451269ab7f3fd03c7 Mon Sep 17 00:00:00 2001 From: santosh-pingle <86107848+santosh-pingle@users.noreply.github.com> Date: Wed, 4 Sep 2024 18:53:44 +0530 Subject: [PATCH] Per question custom style (#2636) * per question custom style * adding missing file. * update default style. * textAppearance support * rename custom attributes. * github documentation for custom style example. * Address review comments. * address review comment. * update text format icon as component icon. * code cleanup. * Code refactoring and cleanup. * custom style example with multiple question items. * Address review comments. * support prefix per question item custom style * Revert dataconfig changes for custom style mapping. * Address review comments. * Address review comments. * Address review comments. * Address review comments. * Address review comment. --------- Co-authored-by: Santosh Pingle --- .../component_per_question_custom_style.json | 149 ++++++++++ .../fhir/catalog/ComponentListViewModel.kt | 6 + .../src/main/res/drawable/ic_location_on.xml | 4 +- .../main/res/drawable/text_format_48dp.xml | 15 + catalog/src/main/res/values-night/colors.xml | 36 +++ catalog/src/main/res/values/colors.xml | 22 ++ catalog/src/main/res/values/strings.xml | 3 + catalog/src/main/res/values/styles.xml | 171 ++++++++++- .../datacapture/extensions/MoreHeaderViews.kt | 38 +++ .../extensions/MoreQuestionItemStyle.kt | 267 ++++++++++++++++++ .../MoreQuestionnaireItemComponents.kt | 21 ++ .../fhir/datacapture/views/GroupHeaderView.kt | 7 + .../fhir/datacapture/views/HeaderView.kt | 7 + datacapture/src/main/res/values/attrs.xml | 20 ++ ...tomize-how-a-Questionnaire-is-displayed.md | 134 +++++++++ 15 files changed, 897 insertions(+), 3 deletions(-) create mode 100644 catalog/src/main/assets/component_per_question_custom_style.json create mode 100644 catalog/src/main/res/drawable/text_format_48dp.xml create mode 100644 catalog/src/main/res/values-night/colors.xml create mode 100644 datacapture/src/main/java/com/google/android/fhir/datacapture/extensions/MoreQuestionItemStyle.kt diff --git a/catalog/src/main/assets/component_per_question_custom_style.json b/catalog/src/main/assets/component_per_question_custom_style.json new file mode 100644 index 0000000000..2f2e8f3b86 --- /dev/null +++ b/catalog/src/main/assets/component_per_question_custom_style.json @@ -0,0 +1,149 @@ +{ + "resourceType": "Questionnaire", + "item": [ + { + "linkId": "1", + "text": "Custom style 1", + "type": "display", + "extension": [ + { + "url": "https://github.com/google/android-fhir/tree/master/datacapture/android-style", + "extension": [ + { + "url": "question_text_view", + "valueString": "CustomStyle_1" + } + ] + } + ] + }, + { + "linkId": "2", + "text": "Custom style 2", + "type": "display", + "extension": [ + { + "url": "https://github.com/google/android-fhir/tree/master/datacapture/android-style", + "extension": [ + { + "url": "question_text_view", + "valueString": "CustomStyle_2" + } + ] + } + ] + }, + { + "linkId": "3", + "text": "Custom style 3", + "type": "display", + "extension": [ + { + "url": "https://github.com/google/android-fhir/tree/master/datacapture/android-style", + "extension": [ + { + "url": "question_text_view", + "valueString": "CustomStyle_3" + } + ] + } + ] + }, + { + "linkId": "4", + "text": "Custom style 4", + "type": "display", + "extension": [ + { + "url": "https://github.com/google/android-fhir/tree/master/datacapture/android-style", + "extension": [ + { + "url": "question_text_view", + "valueString": "CustomStyle_4" + } + ] + } + ] + }, + { + "linkId": "5", + "text": "Custom style 5", + "type": "display", + "extension": [ + { + "url": "https://github.com/google/android-fhir/tree/master/datacapture/android-style", + "extension": [ + { + "url": "question_text_view", + "valueString": "CustomStyle_5" + } + ] + } + ] + }, + { + "linkId": "6", + "text": "Custom style 6", + "type": "display", + "extension": [ + { + "url": "https://github.com/google/android-fhir/tree/master/datacapture/android-style", + "extension": [ + { + "url": "question_text_view", + "valueString": "CustomStyle_6" + } + ] + } + ] + }, + { + "linkId": "7", + "text": "Custom style 7", + "type": "display", + "extension": [ + { + "url": "https://github.com/google/android-fhir/tree/master/datacapture/android-style", + "extension": [ + { + "url": "question_text_view", + "valueString": "CustomStyle_7" + } + ] + } + ] + }, + { + "linkId": "8", + "text": "Custom style 8", + "type": "display", + "extension": [ + { + "url": "https://github.com/google/android-fhir/tree/master/datacapture/android-style", + "extension": [ + { + "url": "question_text_view", + "valueString": "CustomStyle_8" + } + ] + } + ] + }, + { + "linkId": "9", + "text": "Custom style 9", + "type": "display", + "extension": [ + { + "url": "https://github.com/google/android-fhir/tree/master/datacapture/android-style", + "extension": [ + { + "url": "question_text_view", + "valueString": "CustomStyle_9" + } + ] + } + ] + } + ] +} \ No newline at end of file diff --git a/catalog/src/main/java/com/google/android/fhir/catalog/ComponentListViewModel.kt b/catalog/src/main/java/com/google/android/fhir/catalog/ComponentListViewModel.kt index bdafbcd17a..d9e4637ada 100644 --- a/catalog/src/main/java/com/google/android/fhir/catalog/ComponentListViewModel.kt +++ b/catalog/src/main/java/com/google/android/fhir/catalog/ComponentListViewModel.kt @@ -152,6 +152,11 @@ class ComponentListViewModel(application: Application, private val state: SavedS R.string.component_name_location_widget, "component_location_widget.json", ), + QUESTION_ITEM_CUSTOM_STYLE( + R.drawable.text_format_48dp, + R.string.component_name_per_question_custom_style, + "component_per_question_custom_style.json", + ), } val viewItemList = @@ -177,6 +182,7 @@ class ComponentListViewModel(application: Application, private val state: SavedS ViewItem.ComponentItem(Component.ITEM_ANSWER_MEDIA), ViewItem.ComponentItem(Component.INITIAL_VALUE), ViewItem.ComponentItem(Component.LOCATION_WIDGET), + ViewItem.ComponentItem(Component.QUESTION_ITEM_CUSTOM_STYLE), ) fun isComponent(context: Context, title: String) = diff --git a/catalog/src/main/res/drawable/ic_location_on.xml b/catalog/src/main/res/drawable/ic_location_on.xml index 0f96a89039..9821fffba8 100644 --- a/catalog/src/main/res/drawable/ic_location_on.xml +++ b/catalog/src/main/res/drawable/ic_location_on.xml @@ -1,7 +1,7 @@ + + + + diff --git a/catalog/src/main/res/values-night/colors.xml b/catalog/src/main/res/values-night/colors.xml new file mode 100644 index 0000000000..d80470737a --- /dev/null +++ b/catalog/src/main/res/values-night/colors.xml @@ -0,0 +1,36 @@ + + + + #000000 + #0C0A20 + #201441 + #341F63 + #482A85 + #5C35A6 + #7F5FBA + #A289CF + #C5B3E3 + #FFFFFF + #FFFFFF + #FFFFFF + #FFFFFF + #FFFFFF + #FFFFFF + #FFFFFF + #000000 + #000000 + diff --git a/catalog/src/main/res/values/colors.xml b/catalog/src/main/res/values/colors.xml index 8a1561f3da..f9b63e732a 100644 --- a/catalog/src/main/res/values/colors.xml +++ b/catalog/src/main/res/values/colors.xml @@ -102,4 +102,26 @@ #C4C7C5 #8E918F + + + #7A9FFF + #668FFF + #5581FF + #476FFF + #3B5CFF + #3249FF + #2936FF + #2024FF + #1816FF + #FFFFFF + #FFFFFF + #FFFFFF + #FFFFFF + #FFFFFF + #FFFFFF + #FFFFFF + #FFFFFF + #FFFFFF + #FFFFFF + diff --git a/catalog/src/main/res/values/strings.xml b/catalog/src/main/res/values/strings.xml index 7e09047feb..ae47067784 100644 --- a/catalog/src/main/res/values/strings.xml +++ b/catalog/src/main/res/values/strings.xml @@ -37,6 +37,9 @@ Repeated Group Attachment Location Widget + Per question custom style Default Paginated Review diff --git a/catalog/src/main/res/values/styles.xml b/catalog/src/main/res/values/styles.xml index f88c5ea9fa..f2afa052f3 100644 --- a/catalog/src/main/res/values/styles.xml +++ b/catalog/src/main/res/values/styles.xml @@ -80,7 +80,10 @@ @@ -98,4 +101,170 @@ 2 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/datacapture/src/main/java/com/google/android/fhir/datacapture/extensions/MoreHeaderViews.kt b/datacapture/src/main/java/com/google/android/fhir/datacapture/extensions/MoreHeaderViews.kt index 022964c9db..fd4690ea26 100644 --- a/datacapture/src/main/java/com/google/android/fhir/datacapture/extensions/MoreHeaderViews.kt +++ b/datacapture/src/main/java/com/google/android/fhir/datacapture/extensions/MoreHeaderViews.kt @@ -104,3 +104,41 @@ fun appendAsteriskToQuestionText( } } } + +internal fun applyCustomOrDefaultStyle( + questionnaireItem: Questionnaire.QuestionnaireItemComponent, + prefixTextView: TextView, + questionTextView: TextView, + instructionTextView: TextView, +) { + applyCustomOrDefaultStyle( + context = prefixTextView.context, + view = prefixTextView, + customStyleName = + questionnaireItem.readCustomStyleExtension( + StyleUrl.PREFIX_TEXT_VIEW, + ), + defaultStyleResId = + getStyleResIdFromAttribute(questionTextView.context, R.attr.questionnaireQuestionTextStyle), + ) + applyCustomOrDefaultStyle( + context = questionTextView.context, + view = questionTextView, + customStyleName = + questionnaireItem.readCustomStyleExtension( + StyleUrl.QUESTION_TEXT_VIEW, + ), + defaultStyleResId = + getStyleResIdFromAttribute(questionTextView.context, R.attr.questionnaireQuestionTextStyle), + ) + applyCustomOrDefaultStyle( + context = instructionTextView.context, + view = instructionTextView, + customStyleName = + questionnaireItem.readCustomStyleExtension( + StyleUrl.SUBTITLE_TEXT_VIEW, + ), + defaultStyleResId = + getStyleResIdFromAttribute(questionTextView.context, R.attr.questionnaireSubtitleTextStyle), + ) +} diff --git a/datacapture/src/main/java/com/google/android/fhir/datacapture/extensions/MoreQuestionItemStyle.kt b/datacapture/src/main/java/com/google/android/fhir/datacapture/extensions/MoreQuestionItemStyle.kt new file mode 100644 index 0000000000..3067d969bd --- /dev/null +++ b/datacapture/src/main/java/com/google/android/fhir/datacapture/extensions/MoreQuestionItemStyle.kt @@ -0,0 +1,267 @@ +/* + * Copyright 2024 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.content.Context +import android.content.res.TypedArray +import android.view.View +import android.widget.TextView +import androidx.core.content.ContextCompat +import com.google.android.fhir.datacapture.R + +/** + * Applies either a custom style or a default style to the given view based on the provided custom + * style name and default style resource ID. + * + * If the custom style resource name is valid, it applies the custom style to the view. If the + * custom style resource name is not valid or not found, it falls back to applying the default style + * defined by the given style resource ID. It sets the view's tag to resourceId to indicate that the + * custom style has been applied. + * + * @param context the context used to access resources. + * @param view the view to which the style should be applied. + * @param customStyleName the name of the custom style to apply. + * @param defaultStyleResId the default style resource ID to use if no custom style is found. + */ +internal fun applyCustomOrDefaultStyle( + context: Context, + view: View, + customStyleName: String?, + defaultStyleResId: Int, +) { + val customStyleResId = customStyleName?.let { getStyleResIdByName(context, it) } ?: 0 + when { + customStyleResId != 0 -> { + view.tag = customStyleResId + QuestionItemCustomStyle().applyStyle(context, view, customStyleResId) + } + defaultStyleResId != 0 -> { + applyDefaultStyleIfNotApplied(context, view, defaultStyleResId) + } + } +} + +/** + * Applies the default style to the given view if the default style has not already been applied. + * + * This function checks the `view`'s tag to determine if a style has been previously applied. If the + * tag is an integer, it will apply the default style specified by `defaultStyleResId`. After + * applying the style, it resets the view's tag to `null` to indicate that the default style has + * been applied. + * + * @param context The context used to access resources and themes. + * @param view The view to which the default style will be applied. + * @param defaultStyleResId The resource ID of the default style to apply. + */ +private fun applyDefaultStyleIfNotApplied( + context: Context, + view: View, + defaultStyleResId: Int, +) { + (view.tag as? Int)?.let { + QuestionItemDefaultStyle().applyStyle(context, view, defaultStyleResId) + view.tag = null + } +} + +/** + * Retrieves the resource ID of a style given its name. + * + * This function uses the `getIdentifier` method to look up the style resource ID based on the style + * name provided. If the style name is not found, it returns 0. + * + * @param context The context used to access resources. + * @param styleName The name of the style whose resource ID is to be retrieved. + * @return The resource ID of the style, or 0 if the style name is not found. + */ +private fun getStyleResIdByName(context: Context, styleName: String): Int { + return context.resources.getIdentifier(styleName, "style", context.packageName) +} + +/** + * Retrieves the style resource ID associated with a specific attribute from the current theme. + * + * This function obtains the style resource ID that is linked to a given attribute in the current + * theme. It uses the `obtainStyledAttributes` method to fetch the attributes and extract the + * resource ID. + * + * @param context The context to access the current theme and resources. + * @param attr The attribute whose associated style resource ID is to be retrieved. + * @return The resource ID of the style associated with the specified attribute, or 0 if not found. + */ +internal fun getStyleResIdFromAttribute(context: Context, attr: Int): Int { + val typedArray = context.theme.obtainStyledAttributes(intArrayOf(attr)) + val styleResId = typedArray.getResourceId(0, 0) + typedArray.recycle() + return styleResId +} + +internal abstract class QuestionItemStyle { + + /** + * Applies a style to a view. + * + * @param context The context used to apply the style. + * @param view The view to which the style will be applied. + * @param styleResId The resource ID of the style to apply. + */ + abstract fun applyStyle(context: Context, view: View, styleResId: Int) + + /** + * Applies the style from a TypedArray to a view. + * + * @param context The context used to apply the style. + * @param view The view to which the style will be applied. + * @param typedArray The TypedArray containing the style attributes. + */ + internal fun applyStyle(context: Context, view: View, typedArray: TypedArray) { + applyGenericViewStyle(context, view, typedArray) + if (view is TextView) { + applyTextViewSpecificStyle(view, typedArray) + } + typedArray.recycle() + } + + /** + * Abstract function to apply generic view styles from a TypedArray. + * + * @param context The context used to apply the style. + * @param view The view to which the style will be applied. + * @param typedArray The TypedArray containing the style attributes. + */ + abstract fun applyGenericViewStyle(context: Context, view: View, typedArray: TypedArray) + + /** + * Abstract function to apply TextView-specific styles from a TypedArray. + * + * @param textView The TextView to which the style will be applied. + * @param typedArray The TypedArray containing the style attributes. + */ + abstract fun applyTextViewSpecificStyle(textView: TextView, typedArray: TypedArray) + + /** + * Applies the background color from a TypedArray to a view. + * + * @param context The context used to apply the background color. + * @param view The view to which the background color will be applied. + * @param typedArray The TypedArray containing the background color attribute. + * @param index The index of the background color attribute in the TypedArray. + */ + protected fun applyBackgroundColor( + context: Context, + view: View, + typedArray: TypedArray, + index: Int, + ) { + val backgroundColor = + typedArray.getColor(index, ContextCompat.getColor(context, android.R.color.transparent)) + view.setBackgroundColor(backgroundColor) + } + + /** + * Applies the text appearance from a TypedArray to a TextView. + * + * @param textView The TextView to which the text appearance will be applied. + * @param typedArray The TypedArray containing the text appearance attribute. + * @param index The index of the text appearance attribute in the TypedArray. + */ + protected fun applyTextAppearance(textView: TextView, typedArray: TypedArray, index: Int) { + val textAppearance = typedArray.getResourceId(index, -1) + if (textAppearance != -1) { + textView.setTextAppearance(textAppearance) + } + } +} + +internal class QuestionItemCustomStyle : QuestionItemStyle() { + private enum class CustomStyleViewAttributes(val attrId: Int) { + TEXT_APPEARANCE(R.styleable.QuestionnaireCustomStyle_questionnaire_textAppearance), + BACKGROUND(R.styleable.QuestionnaireCustomStyle_questionnaire_background), + } + + override fun applyStyle(context: Context, view: View, styleResId: Int) { + val typedArray = + context.obtainStyledAttributes(styleResId, R.styleable.QuestionnaireCustomStyle) + applyStyle(context, view, typedArray) + } + + override fun applyGenericViewStyle(context: Context, view: View, typedArray: TypedArray) { + for (i in 0 until typedArray.indexCount) { + when (typedArray.getIndex(i)) { + CustomStyleViewAttributes.BACKGROUND.attrId -> { + applyBackgroundColor(context, view, typedArray, i) + } + } + } + } + + override fun applyTextViewSpecificStyle( + textView: TextView, + typedArray: TypedArray, + ) { + for (i in 0 until typedArray.indexCount) { + when (typedArray.getIndex(i)) { + CustomStyleViewAttributes.TEXT_APPEARANCE.attrId -> { + applyTextAppearance(textView, typedArray, i) + } + } + } + } +} + +internal class QuestionItemDefaultStyle : QuestionItemStyle() { + private enum class DefaultStyleViewAttributes(val attrId: Int) { + TEXT_APPEARANCE(android.R.attr.textAppearance), + BACKGROUND(android.R.attr.background), + // Add other attributes you want to apply + } + + override fun applyStyle(context: Context, view: View, styleResId: Int) { + val attrs = DefaultStyleViewAttributes.values().map { it.attrId }.toIntArray() + val typedArray: TypedArray = context.obtainStyledAttributes(styleResId, attrs) + applyStyle(context, view, typedArray) + } + + override fun applyGenericViewStyle(context: Context, view: View, typedArray: TypedArray) { + for (i in 0 until typedArray.indexCount) { + when (DefaultStyleViewAttributes.values()[i]) { + DefaultStyleViewAttributes.BACKGROUND -> { + applyBackgroundColor(context, view, typedArray, i) + } + else -> { + // Ignore view specific attributes. + } + } + } + } + + override fun applyTextViewSpecificStyle( + textView: TextView, + typedArray: TypedArray, + ) { + for (i in 0 until typedArray.indexCount) { + when (DefaultStyleViewAttributes.values()[i]) { + DefaultStyleViewAttributes.TEXT_APPEARANCE -> { + applyTextAppearance(textView, typedArray, i) + } + else -> { + // applyGenericViewDefaultStyle for other attributes. + } + } + } + } +} diff --git a/datacapture/src/main/java/com/google/android/fhir/datacapture/extensions/MoreQuestionnaireItemComponents.kt b/datacapture/src/main/java/com/google/android/fhir/datacapture/extensions/MoreQuestionnaireItemComponents.kt index 55dd4e5738..0acd26c93f 100644 --- a/datacapture/src/main/java/com/google/android/fhir/datacapture/extensions/MoreQuestionnaireItemComponents.kt +++ b/datacapture/src/main/java/com/google/android/fhir/datacapture/extensions/MoreQuestionnaireItemComponents.kt @@ -61,6 +61,13 @@ internal const val EXTENSION_ITEM_CONTROL_URL_ANDROID_FHIR = internal const val EXTENSION_ITEM_CONTROL_SYSTEM_ANDROID_FHIR = "https://github.com/google/android-fhir/questionnaire-item-control" +internal enum class StyleUrl(val url: String) { + BASE("https://github.com/google/android-fhir/tree/master/datacapture/android-style"), + PREFIX_TEXT_VIEW("prefix_text_view"), + QUESTION_TEXT_VIEW("question_text_view"), + SUBTITLE_TEXT_VIEW("subtitle_text_view"), +} + // Below URLs exist and are supported by HL7 internal const val EXTENSION_ANSWER_EXPRESSION_URL: String = @@ -1017,3 +1024,17 @@ val Resource.logicalId: String get() { return this.idElement?.idPart.orEmpty() } + +internal fun QuestionnaireItemComponent.readCustomStyleExtension(styleUrl: StyleUrl): String? { + // Find the base extension + val baseExtension = extension.find { it.url == StyleUrl.BASE.url } + baseExtension?.let { ext -> + // Extract nested extension based on the given StyleUrl + ext.extension.forEach { nestedExt -> + if (nestedExt.url == styleUrl.url) { + return nestedExt.value.asStringValue() + } + } + } + return null +} diff --git a/datacapture/src/main/java/com/google/android/fhir/datacapture/views/GroupHeaderView.kt b/datacapture/src/main/java/com/google/android/fhir/datacapture/views/GroupHeaderView.kt index 4fff12a122..da85cbac4e 100644 --- a/datacapture/src/main/java/com/google/android/fhir/datacapture/views/GroupHeaderView.kt +++ b/datacapture/src/main/java/com/google/android/fhir/datacapture/views/GroupHeaderView.kt @@ -23,6 +23,7 @@ import android.widget.LinearLayout import android.widget.TextView import com.google.android.fhir.datacapture.QuestionnaireViewHolderType import com.google.android.fhir.datacapture.R +import com.google.android.fhir.datacapture.extensions.applyCustomOrDefaultStyle import com.google.android.fhir.datacapture.extensions.getHeaderViewVisibility import com.google.android.fhir.datacapture.extensions.getLocalizedInstructionsSpanned import com.google.android.fhir.datacapture.extensions.initHelpViews @@ -60,5 +61,11 @@ class GroupHeaderView(context: Context, attrs: AttributeSet?) : LinearLayout(con questionnaireViewItem.enabledDisplayItems.getLocalizedInstructionsSpanned(), ) visibility = getHeaderViewVisibility(prefix, question, hint) + applyCustomOrDefaultStyle( + questionnaireViewItem.questionnaireItem, + prefixTextView = prefix, + questionTextView = question, + instructionTextView = hint, + ) } } diff --git a/datacapture/src/main/java/com/google/android/fhir/datacapture/views/HeaderView.kt b/datacapture/src/main/java/com/google/android/fhir/datacapture/views/HeaderView.kt index c48e546f38..7e5e77231d 100644 --- a/datacapture/src/main/java/com/google/android/fhir/datacapture/views/HeaderView.kt +++ b/datacapture/src/main/java/com/google/android/fhir/datacapture/views/HeaderView.kt @@ -23,6 +23,7 @@ import android.widget.LinearLayout import android.widget.TextView import com.google.android.fhir.datacapture.R import com.google.android.fhir.datacapture.extensions.appendAsteriskToQuestionText +import com.google.android.fhir.datacapture.extensions.applyCustomOrDefaultStyle import com.google.android.fhir.datacapture.extensions.getHeaderViewVisibility import com.google.android.fhir.datacapture.extensions.getLocalizedInstructionsSpanned import com.google.android.fhir.datacapture.extensions.initHelpViews @@ -64,6 +65,12 @@ class HeaderView(context: Context, attrs: AttributeSet?) : LinearLayout(context, // Make the entire view GONE if there is nothing to show. This is to avoid an empty row in the // questionnaire. visibility = getHeaderViewVisibility(prefix, question, hint) + applyCustomOrDefaultStyle( + questionnaireViewItem.questionnaireItem, + prefixTextView = prefix, + questionTextView = question, + instructionTextView = hint, + ) } /** diff --git a/datacapture/src/main/res/values/attrs.xml b/datacapture/src/main/res/values/attrs.xml index 46d076ed59..42cc7e5fb1 100644 --- a/datacapture/src/main/res/values/attrs.xml +++ b/datacapture/src/main/res/values/attrs.xml @@ -188,4 +188,24 @@ extend Theme.Questionnaire. If unspecified, Theme.Questionnaire will be used. --> + + + + + + + + + diff --git a/docs/use/SDCL/Customize-how-a-Questionnaire-is-displayed.md b/docs/use/SDCL/Customize-how-a-Questionnaire-is-displayed.md index 3ed391bded..4ac8947ef7 100644 --- a/docs/use/SDCL/Customize-how-a-Questionnaire-is-displayed.md +++ b/docs/use/SDCL/Customize-how-a-Questionnaire-is-displayed.md @@ -67,6 +67,140 @@ the new theme you just created: ``` +## Custom Style per Question Item + +With this change, you can apply individual custom styles per question item. If a custom style is not mentioned in the question item, the default style will be applied, which is present in the DataCapture module or overridden in the application. + +### Add a Custom Style Extension to the Question Item + +```json +{ + "extension": [ + { + "url": "https://github.com/google/android-fhir/tree/master/datacapture/android-style", + "extension": [ + { + "url": "prefix_text_view", + "valueString": "CustomStyle_1" + }, + { + "url": "question_text_view", + "valueString": "CustomStyle_1" + }, + { + "url": "subtitle_text_view", + "valueString": "CustomStyle_2" + } + ] + } + ] +} +``` +### Custom Style Extension URL +"https://github.com/google/android-fhir/tree/master/datacapture/android-style" + +It identifies extensions for applying the custom style to a given questionnaire item. + +### Question Item View +* `prefix_text_view`: Used to show the prefix value of the question item. +* `question_text_view`: Used to show the text value of the question item. +* `subtitle_text_view`: Used to show the instructions of the question item. + For more information about supported views, please see the [Question Item View](https://github.com/google/android-fhir/blob/master/datacapture/src/main/java/com/google/android/fhir/datacapture/extensions/MoreQuestionnaireItemComponents.kt). + +### Custom Style Values +In the above example: + +`CustomStyle_1` is the custom style for prefix_text_view and question_text_view. +`CustomStyle_2` is the custom style for subtitle_text_view. +Both styles are defined in the application. + +### Custom Style Attributes +* `questionnaire_textAppearance`: Specifies the text appearance for the questionnaire text. Example: `@style/TextAppearance.AppCompat.Headline` +* `questionnaire_background`: Specifies the background for the view. Example: `@color/background_color or #FFFFFF` + +For more information on custom style attributes, please see the [QuestionnaireCustomStyle](https://github.com/google/android-fhir/blob/master/datacapture/src/main/res/values/attrs.xml) + +### Example Custom Styles + +``` + + + + + + + + +``` + +The above custom styles are defined in the `res/values/styles.xml` of the application. + +### questionnaire.json with custom style +``` +{ + "resourceType": "Questionnaire", + "item": [ + { + "linkId": "1", + "text": "Question text custom style", + "type": "display", + "extension": [ + { + "url": "https://github.com/google/android-fhir/tree/master/datacapture/android-style", + "extension": [ + { + "url": "prefix_text_view", + "valueString": "CustomStyle_1" + }, + { + "url": "question_text_view", + "valueString": "CustomStyle_1" + }, + { + "url": "subtitle_text_view", + "valueString": "CustomStyle_2" + } + ] + } + ], + "item": [ + { + "extension": [ + { + "url": "http://hl7.org/fhir/StructureDefinition/questionnaire-displayCategory", + "valueCodeableConcept": { + "coding": [ + { + "system": "http://hl7.org/fhir/questionnaire-display-category", + "code": "instructions" + } + ] + } + } + ], + "linkId": "1.3", + "text": "Instructions custom style.", + "type": "display" + } + ] + } ] +} +``` + ## Custom questionnaire components The Structured Data Capture Library uses