1616package uk.gov.hmrc.components.molecule.input
1717
1818import android.content.Context
19+ import android.os.Build.VERSION
20+ import android.os.Build.VERSION_CODES.O
1921import android.os.Bundle
2022import android.os.Parcelable
2123import android.text.InputFilter
@@ -52,14 +54,15 @@ open class TextInputView @JvmOverloads constructor(
5254 binding.root.id = value
5355 }
5456
57+ private var hintContentDescription: String? = null
58+
5559 init {
5660
5761 attrs?.let {
5862 val typedArray = context.theme.obtainStyledAttributes(it, R .styleable.TextInputView , 0 , 0 )
5963
6064 val textString = typedArray.getString(R .styleable.TextInputView_text ) ? : " "
61- val overrideHintContentDescription = typedArray.getString(
62- R .styleable.TextInputView_overrideHintContentDescription )
65+ val hintTextContentDescription = typedArray.getString(R .styleable.TextInputView_hintContentDescription )
6366 val hintText = typedArray.getString(R .styleable.TextInputView_hintText ) ? : " "
6467 val errorText = typedArray.getString(R .styleable.TextInputView_errorText ) ? : " "
6568 val counterMaxLength = typedArray.getInt(R .styleable.TextInputView_counterMaxLength , NO_MAX_LENGTH )
@@ -70,8 +73,7 @@ open class TextInputView @JvmOverloads constructor(
7073 val maxLength = typedArray.getInt(R .styleable.TextInputView_android_maxLength , - 1 )
7174
7275 setText(textString)
73- overrideHintContentDescription(overrideHintContentDescription)
74- setHint(hintText)
76+ setHint(hintText, hintTextContentDescription)
7577 setError(errorText)
7678 setCounterMaxLength(counterMaxLength)
7779 setCounterEnabled(counterEnabled)
@@ -108,30 +110,20 @@ open class TextInputView @JvmOverloads constructor(
108110
109111 fun getText (): String? = getEditText().text?.toString()
110112
111- fun overrideHintContentDescription (contentDescription : CharSequence? ) {
112- contentDescription ? : return
113-
114- binding.root.apply {
115- setTextInputAccessibilityDelegate(object : TextInputLayout .AccessibilityDelegate (this ) {
116- override fun onInitializeAccessibilityNodeInfo (host : View , info : AccessibilityNodeInfoCompat ) {
117- super .onInitializeAccessibilityNodeInfo(host, info)
118- val before = info.text.toString()
119- val after = before.replace(hint.toString(), contentDescription.toString())
120- info.text = after
121- }
122- })
123- }
124- }
125-
126- fun setHint (hint : CharSequence ) {
113+ fun setHint (hint : CharSequence , contentDescription : CharSequence? = null) {
127114 binding.root.hint = hint
115+ hintContentDescription = (contentDescription ? : hint).toString()
116+ updateTextInputViewContentDescription()
128117 }
129118
130119 fun getError () = binding.root.error
131120
132121 fun setError (errorText : CharSequence? , errorContentDescription : CharSequence? = null) {
133122 binding.root.error = errorText
134- binding.root.errorContentDescription = errorContentDescription ? : errorText
123+ binding.root.errorContentDescription = errorContentDescription ? : if (! errorText.isNullOrEmpty()) {
124+ context.getString(R .string.accessibility_error_prefix, errorText)
125+ } else null
126+ updateTextInputViewContentDescription()
135127 }
136128
137129 fun setErrorText (@StringRes error : Int? , @StringRes errorContentDescription : Int? = null) {
@@ -145,10 +137,12 @@ open class TextInputView @JvmOverloads constructor(
145137
146138 fun setCounterMaxLength (maxLength : Int ) {
147139 binding.root.counterMaxLength = maxLength
140+ updateTextInputViewContentDescription()
148141 }
149142
150143 fun setCounterEnabled (enabled : Boolean ) {
151144 binding.root.isCounterEnabled = enabled
145+ updateTextInputViewContentDescription()
152146 }
153147
154148 fun setPrefixText (prefixText : CharSequence? ) {
@@ -177,6 +171,53 @@ open class TextInputView @JvmOverloads constructor(
177171
178172 fun getEditText (): TextInputEditText = binding.root.findViewWithTag(" edit_text" )
179173
174+ private fun updateTextInputViewContentDescription () {
175+ binding.root.apply {
176+ setTextInputAccessibilityDelegate(object : TextInputLayout .AccessibilityDelegate (this ) {
177+ override fun onInitializeAccessibilityNodeInfo (host : View , info : AccessibilityNodeInfoCompat ) {
178+ super .onInitializeAccessibilityNodeInfo(host, info)
179+
180+ val customHint = hintContentDescription ? : hint
181+
182+ val error = if (errorContentDescription.isNullOrEmpty()) " " else " , $errorContentDescription "
183+
184+ val counter = if (isCounterEnabled) {
185+ val currentChars = if (getText().isNullOrEmpty()) 0 else getText()!! .length
186+ val maxLength = counterMaxLength
187+
188+ val limitExceededText = if (currentChars > maxLength) {
189+ " ${context.getString(R .string.accessibility_counter_limit_exceeded)} "
190+ } else " "
191+
192+ val counterText = context.getString(
193+ R .string.accessibility_counter_state,
194+ currentChars.toString(),
195+ maxLength.toString())
196+
197+ " , $limitExceededText$counterText "
198+ } else " "
199+
200+ val newContentDescription = " $customHint$error$counter "
201+
202+ val showingText = ! getText().isNullOrEmpty()
203+ if (VERSION .SDK_INT >= O ) {
204+ if (showingText) {
205+ info.hintText = newContentDescription
206+ } else {
207+ info.text = newContentDescription
208+ }
209+ } else {
210+ // Due to a TalkBack bug, setHintText has no effect in APIs < 26 so we append the hint to
211+ // the text announcement. The resulting announcement is the same as in APIs >= 26.
212+ info.text = if (showingText) {
213+ getText() + " , " + newContentDescription
214+ } else newContentDescription
215+ }
216+ }
217+ })
218+ }
219+ }
220+
180221 companion object {
181222 private const val STATE_TEXT = " STATE_TEXT"
182223 private const val STATE_SUPER = " STATE_SUPER"
0 commit comments