From ec17cb7b8160890af632e3d2ec5f6b76552e7c22 Mon Sep 17 00:00:00 2001 From: Karl Valliste Date: Tue, 17 Mar 2026 13:28:50 +0200 Subject: [PATCH] Added announceForAccessibility to incoming new messages MOB-5061 --- .claude/settings.local.json | 7 ++++++- gradle/libs.versions.toml | 4 ++-- widgetssdk/build.gradle | 2 -- .../src/main/java/com/glia/widgets/chat/ChatView.kt | 11 ++++++++++- .../chat/domain/AppendHistoryChatItemUseCases.kt | 4 +++- .../widgets/chat/domain/AppendNewChatItemUseCase.kt | 2 +- .../glia/widgets/chat/domain/MapChatItemUseCases.kt | 6 ++++-- .../java/com/glia/widgets/chat/model/ChatItems.kt | 7 +++++-- .../AppendHistoryResponseCardOrTextItemUseCaseTest.kt | 8 ++++---- .../domain/AppendNewOperatorMessageUseCaseTest.kt | 2 +- .../AppendNewResponseCardOrTextItemUseCaseTest.kt | 2 +- .../chat/domain/MapOperatorPlainTextUseCaseTest.kt | 4 ++-- .../widgets/survey/viewholder/SurveyViewHolderTest.kt | 5 +++-- .../widgets/snapshotutils/SnapshotOperatorMessage.kt | 8 +++++--- 14 files changed, 47 insertions(+), 25 deletions(-) diff --git a/.claude/settings.local.json b/.claude/settings.local.json index 799337066..dc9fe12aa 100644 --- a/.claude/settings.local.json +++ b/.claude/settings.local.json @@ -2,5 +2,10 @@ "enableAllProjectMcpServers": true, "enabledMcpjsonServers": [ "mobile-mcp" - ] + ], + "permissions": { + "allow": [ + "Bash(find widgetssdk/src/main/java/com/glia/widgets -name \"*.kt\" -type f -exec grep -l \"Logger\\\\.\\\\\\(e\\\\|w\\\\|d\\\\|i\\\\\\)\\(\" {} \\\\;)" + ] + } } diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index f488b67e0..465219081 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -3,7 +3,7 @@ javaVersion = "17" kotlinVersion = "2.1.21" # Kotlin and Dokka versions should have the same minor version to support the same kotlin version dokkaVersion = "2.0.0" # Kotlin and Dokka versions should have the same minor version to support the same kotlin version -agpVersion = "8.13.0" +agpVersion = "8.13.2" ktlintVersion = "13.1.0" #Publishing mavenPublish = "0.35.0" @@ -20,7 +20,7 @@ javaKtorVersion = "3.3.1" javaCoroutinesVersion = "1.10.2" javaDokkaVersion = "2.0.0" javaLifecycleProcessVersion = "2.9.4" -javaLintVersion = "31.13.0" +javaLintVersion = "31.13.2" javaMaterialVersion = "1.13.0" javaPreferenceVersion = "1.2.1" javaRxAndroid3Version = "3.0.2" diff --git a/widgetssdk/build.gradle b/widgetssdk/build.gradle index 36988e8d8..6db3536cc 100644 --- a/widgetssdk/build.gradle +++ b/widgetssdk/build.gradle @@ -18,8 +18,6 @@ android { namespace 'com.glia.widgets' defaultConfig { minSdkVersion 24 - versionCode widgetsVersionCode - versionName widgetsVersionName testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" consumerProguardFiles "consumer-rules.pro" } diff --git a/widgetssdk/src/main/java/com/glia/widgets/chat/ChatView.kt b/widgetssdk/src/main/java/com/glia/widgets/chat/ChatView.kt index 3fd6c497e..abfa5f8ea 100644 --- a/widgetssdk/src/main/java/com/glia/widgets/chat/ChatView.kt +++ b/widgetssdk/src/main/java/com/glia/widgets/chat/ChatView.kt @@ -49,6 +49,7 @@ import com.glia.widgets.chat.model.ChatInputMode import com.glia.widgets.chat.model.ChatItem import com.glia.widgets.chat.model.ChatState import com.glia.widgets.chat.model.CustomCardChatItem +import com.glia.widgets.chat.model.OperatorMessageItem import com.glia.widgets.databinding.ChatViewBinding import com.glia.widgets.di.Dependencies import com.glia.widgets.entrywidget.EntryWidgetContract @@ -161,6 +162,14 @@ internal class ChatView(context: Context, attrs: AttributeSet?, defStyleAttr: In override fun onItemRangeInserted(positionStart: Int, itemCount: Int) { super.onItemRangeInserted(positionStart, itemCount) + for (i in positionStart until positionStart + itemCount) { + adapter.currentList.getOrNull(i)?.let { item -> + if (item is OperatorMessageItem.PlainText && !item.announced) { + binding.chatRecyclerView.announceForAccessibility(item.content) + item.announced = true + } + } + } val totalItemCount = adapter.itemCount val lastIndex = totalItemCount - 1 if (isInBottom && lastIndex != -1) { @@ -714,7 +723,7 @@ internal class ChatView(context: Context, attrs: AttributeSet?, defStyleAttr: In } private fun setupViewActions() { - binding.chatEditText.setAccessibilityHint(R.string.general_message) + binding.chatEditText.setAccessibilityHint(localeProvider.getString(R.string.general_message)) binding.chatEditText.addTextChangedListener(textWatcher) binding.sendButton.setOnClickListener { val message = binding.chatEditText.text.toString().trim { it <= ' ' } diff --git a/widgetssdk/src/main/java/com/glia/widgets/chat/domain/AppendHistoryChatItemUseCases.kt b/widgetssdk/src/main/java/com/glia/widgets/chat/domain/AppendHistoryChatItemUseCases.kt index d024179e5..2a563f8b8 100644 --- a/widgetssdk/src/main/java/com/glia/widgets/chat/domain/AppendHistoryChatItemUseCases.kt +++ b/widgetssdk/src/main/java/com/glia/widgets/chat/domain/AppendHistoryChatItemUseCases.kt @@ -13,6 +13,8 @@ import com.glia.widgets.chat.domain.gva.IsGvaUseCase import com.glia.widgets.chat.domain.gva.MapGvaUseCase import com.glia.widgets.chat.model.ChatItem import com.glia.widgets.chat.model.CustomCardChatItem +import com.glia.widgets.chat.model.OperatorChatItem +import com.glia.widgets.chat.model.OperatorMessageItem import com.glia.widgets.chat.model.OperatorStatusItem import com.glia.widgets.chat.model.SystemChatItem import com.glia.widgets.chat.model.VisitorMessageItem @@ -168,7 +170,7 @@ internal class AppendHistoryResponseCardOrTextItemUseCase( } if (message.chatMessage.content.isNotBlank()) { - chatItems += mapOperatorPlainTextUseCase(message, showChatHead && filesAttachment == null) + chatItems += mapOperatorPlainTextUseCase(message, showChatHead && filesAttachment == null, true) } } diff --git a/widgetssdk/src/main/java/com/glia/widgets/chat/domain/AppendNewChatItemUseCase.kt b/widgetssdk/src/main/java/com/glia/widgets/chat/domain/AppendNewChatItemUseCase.kt index 9d95461f6..71afe2060 100644 --- a/widgetssdk/src/main/java/com/glia/widgets/chat/domain/AppendNewChatItemUseCase.kt +++ b/widgetssdk/src/main/java/com/glia/widgets/chat/domain/AppendNewChatItemUseCase.kt @@ -124,7 +124,7 @@ internal class AppendNewResponseCardOrTextItemUseCase( val filesAttachment = message.chatMessage.attachment as? FilesAttachment if (message.chatMessage.content.isNotBlank()) { - chatItems += mapOperatorPlainTextUseCase(message, filesAttachment?.files.isNullOrEmpty()) + chatItems += mapOperatorPlainTextUseCase(message, filesAttachment?.files.isNullOrEmpty(), false) } filesAttachment?.files?.apply { diff --git a/widgetssdk/src/main/java/com/glia/widgets/chat/domain/MapChatItemUseCases.kt b/widgetssdk/src/main/java/com/glia/widgets/chat/domain/MapChatItemUseCases.kt index 2cd706ff9..5fea7d5c0 100644 --- a/widgetssdk/src/main/java/com/glia/widgets/chat/domain/MapChatItemUseCases.kt +++ b/widgetssdk/src/main/java/com/glia/widgets/chat/domain/MapChatItemUseCases.kt @@ -56,7 +56,7 @@ internal class MapVisitorAttachmentUseCase { } internal class MapOperatorPlainTextUseCase { - operator fun invoke(chatMessageInternal: ChatMessageInternal, showChatHead: Boolean): OperatorMessageItem = chatMessageInternal.run { + operator fun invoke(chatMessageInternal: ChatMessageInternal, showChatHead: Boolean, history: Boolean): OperatorMessageItem = chatMessageInternal.run { OperatorMessageItem.PlainText( chatMessage.id, chatMessage.timestamp, @@ -64,7 +64,8 @@ internal class MapOperatorPlainTextUseCase { operatorImageUrl, operatorId, operatorName, - chatMessage.content + chatMessage.content, + history ) } } @@ -80,6 +81,7 @@ internal class MapResponseCardUseCase { operatorId, operatorName, chatMessage.content, + true, attachment.options.asList(), attachment.imageUrl.getOrNull() ) diff --git a/widgetssdk/src/main/java/com/glia/widgets/chat/model/ChatItems.kt b/widgetssdk/src/main/java/com/glia/widgets/chat/model/ChatItems.kt index b416855b4..05c377e70 100644 --- a/widgetssdk/src/main/java/com/glia/widgets/chat/model/ChatItems.kt +++ b/widgetssdk/src/main/java/com/glia/widgets/chat/model/ChatItems.kt @@ -111,7 +111,8 @@ internal sealed class OperatorMessageItem : OperatorChatItem(ChatAdapter.OPERATO override val operatorProfileImgUrl: String?, override val operatorId: String?, override val operatorName: String?, - override val content: String? + override val content: String?, + var announced: Boolean ) : OperatorMessageItem() { override fun withShowChatHead(showChatHead: Boolean): OperatorChatItem = copy(showChatHead = showChatHead) } @@ -124,6 +125,7 @@ internal sealed class OperatorMessageItem : OperatorChatItem(ChatAdapter.OPERATO override val operatorId: String?, override val operatorName: String?, override val content: String?, + val announced: Boolean, val singleChoiceOptions: List, val choiceCardImageUrl: String? ) : OperatorMessageItem() { @@ -141,7 +143,8 @@ internal sealed class OperatorMessageItem : OperatorChatItem(ChatAdapter.OPERATO operatorProfileImgUrl = operatorProfileImgUrl, operatorId = operatorId, operatorName = operatorName, - content = content + content = content, + announced = announced ) } } diff --git a/widgetssdk/src/test/java/com/glia/widgets/chat/domain/AppendHistoryResponseCardOrTextItemUseCaseTest.kt b/widgetssdk/src/test/java/com/glia/widgets/chat/domain/AppendHistoryResponseCardOrTextItemUseCaseTest.kt index 4019985a6..f1f786007 100644 --- a/widgetssdk/src/test/java/com/glia/widgets/chat/domain/AppendHistoryResponseCardOrTextItemUseCaseTest.kt +++ b/widgetssdk/src/test/java/com/glia/widgets/chat/domain/AppendHistoryResponseCardOrTextItemUseCaseTest.kt @@ -56,7 +56,7 @@ class AppendHistoryResponseCardOrTextItemUseCaseTest { @Test fun `addPlainTextAndAttachments adds OperatorMessageItem_PlainText when chatMessage content is not empty`() { whenever(mockChatMessageInternal.chatMessageInternal.chatMessage.attachment) doReturn mock() - whenever(mapOperatorPlainTextUseCase.invoke(any(), any())) doReturn mock() + whenever(mapOperatorPlainTextUseCase.invoke(any(), any(), any())) doReturn mock() useCase.addPlainTextAndAttachments(items, mockChatMessageInternal.chatMessageInternal, true) assertTrue(items.count() == 1) assertTrue(items.first() is OperatorMessageItem.PlainText) @@ -66,7 +66,7 @@ class AppendHistoryResponseCardOrTextItemUseCaseTest { fun `addPlainTextAndAttachments does not add OperatorMessageItem_PlainText when chatMessage content is null or empty`() { whenever(mockChatMessageInternal.chatMessageInternal.chatMessage.attachment) doReturn mock() whenever(mockChatMessageInternal.chatMessageInternal.chatMessage.content) doReturn "" - whenever(mapOperatorPlainTextUseCase.invoke(any(), any())) doReturn mock() + whenever(mapOperatorPlainTextUseCase.invoke(any(), any(), any())) doReturn mock() useCase.addPlainTextAndAttachments(items, mockChatMessageInternal.chatMessageInternal, true) assertTrue(items.isEmpty()) } @@ -78,7 +78,7 @@ class AppendHistoryResponseCardOrTextItemUseCaseTest { whenever(filesAttachment.files) doReturn arrayOf(file) whenever(mapOperatorAttachmentUseCase.invoke(any(), any(), any())) doReturn mock() whenever(mockChatMessageInternal.chatMessageInternal.chatMessage.attachment) doReturn filesAttachment - whenever(mapOperatorPlainTextUseCase.invoke(any(), any())) doReturn mock() + whenever(mapOperatorPlainTextUseCase.invoke(any(), any(), any())) doReturn mock() useCase.addPlainTextAndAttachments(items, mockChatMessageInternal.chatMessageInternal, true) assertTrue(items.count() == 2) assertTrue(items.first() is OperatorAttachmentItem.File) @@ -96,7 +96,7 @@ class AppendHistoryResponseCardOrTextItemUseCaseTest { whenever(mapOperatorAttachmentUseCase.invoke(eq(file1), any(), any())) doReturn operatorAttachment1 whenever(mapOperatorAttachmentUseCase.invoke(eq(file2), any(), any())) doReturn operatorAttachment2 whenever(mockChatMessageInternal.chatMessageInternal.chatMessage.attachment) doReturn filesAttachment - whenever(mapOperatorPlainTextUseCase.invoke(any(), any())) doReturn mock() + whenever(mapOperatorPlainTextUseCase.invoke(any(), any(), any())) doReturn mock() useCase.addPlainTextAndAttachments(items, mockChatMessageInternal.chatMessageInternal, true) assertTrue(items.count() == 3) assertTrue(items.first() is OperatorAttachmentItem.File) diff --git a/widgetssdk/src/test/java/com/glia/widgets/chat/domain/AppendNewOperatorMessageUseCaseTest.kt b/widgetssdk/src/test/java/com/glia/widgets/chat/domain/AppendNewOperatorMessageUseCaseTest.kt index 4d20e934b..2c773f458 100644 --- a/widgetssdk/src/test/java/com/glia/widgets/chat/domain/AppendNewOperatorMessageUseCaseTest.kt +++ b/widgetssdk/src/test/java/com/glia/widgets/chat/domain/AppendNewOperatorMessageUseCaseTest.kt @@ -114,7 +114,7 @@ class AppendNewOperatorMessageUseCaseTest { whenever(customCardAdapterTypeUseCase(any())) doReturn null whenever(isGvaUseCase(any())) doReturn false - val operatorChatItem: OperatorChatItem = OperatorMessageItem.PlainText("id", 1, true, "img", "operator_id", "name", "content") + val operatorChatItem: OperatorChatItem = OperatorMessageItem.PlainText("id", 1, true, "img", "operator_id", "name", "content", true) state.lastMessageWithVisibleOperatorImage = operatorChatItem doAnswer { diff --git a/widgetssdk/src/test/java/com/glia/widgets/chat/domain/AppendNewResponseCardOrTextItemUseCaseTest.kt b/widgetssdk/src/test/java/com/glia/widgets/chat/domain/AppendNewResponseCardOrTextItemUseCaseTest.kt index b977c38cc..6a552075b 100644 --- a/widgetssdk/src/test/java/com/glia/widgets/chat/domain/AppendNewResponseCardOrTextItemUseCaseTest.kt +++ b/widgetssdk/src/test/java/com/glia/widgets/chat/domain/AppendNewResponseCardOrTextItemUseCaseTest.kt @@ -68,7 +68,7 @@ class AppendNewResponseCardOrTextItemUseCaseTest { whenever(chatMessage.content) doReturn "content" whenever(chatMessage.attachment) doReturn attachment - whenever(mapOperatorPlainTextUseCase(any(), any())) doReturn mock() + whenever(mapOperatorPlainTextUseCase(any(), any(), any())) doReturn mock() val operatorAttachmentItemTrue = mock().apply { whenever(showChatHead) doReturn true } val operatorAttachmentItemFalse = mock().apply { whenever(showChatHead) doReturn false } diff --git a/widgetssdk/src/test/java/com/glia/widgets/chat/domain/MapOperatorPlainTextUseCaseTest.kt b/widgetssdk/src/test/java/com/glia/widgets/chat/domain/MapOperatorPlainTextUseCaseTest.kt index 1e16ec625..b06733467 100644 --- a/widgetssdk/src/test/java/com/glia/widgets/chat/domain/MapOperatorPlainTextUseCaseTest.kt +++ b/widgetssdk/src/test/java/com/glia/widgets/chat/domain/MapOperatorPlainTextUseCaseTest.kt @@ -26,7 +26,7 @@ class MapOperatorPlainTextUseCaseTest { @Test fun `invoke returns OperatorMessageItem_PlainText with showChatHead true when true is passed`() { mockChatMessageInternal.apply { - val message = useCase(chatMessageInternal, true) + val message = useCase(chatMessageInternal, true, true) assertTrue(message is OperatorMessageItem.PlainText) @@ -43,7 +43,7 @@ class MapOperatorPlainTextUseCaseTest { @Test fun `invoke returns OperatorMessageItem_PlainText with showChatHead false when false is passed`() { mockChatMessageInternal.apply { - val message = useCase(chatMessageInternal, false) + val message = useCase(chatMessageInternal, false, true) assertTrue(message is OperatorMessageItem.PlainText) assertEquals(message.showChatHead, false) diff --git a/widgetssdk/src/test/java/com/glia/widgets/survey/viewholder/SurveyViewHolderTest.kt b/widgetssdk/src/test/java/com/glia/widgets/survey/viewholder/SurveyViewHolderTest.kt index b888b3613..28b2f513b 100644 --- a/widgetssdk/src/test/java/com/glia/widgets/survey/viewholder/SurveyViewHolderTest.kt +++ b/widgetssdk/src/test/java/com/glia/widgets/survey/viewholder/SurveyViewHolderTest.kt @@ -8,6 +8,7 @@ import com.glia.androidsdk.engagement.Survey import com.glia.widgets.R import com.glia.widgets.di.Dependencies import com.glia.widgets.helper.setAccessibilityHint +import com.glia.widgets.helper.setLocaleAccessibilityHint import com.glia.widgets.helper.setLocaleContentDescription import com.glia.widgets.helper.setLocaleText import com.glia.widgets.locale.LocaleProvider @@ -60,7 +61,7 @@ class SurveyViewHolderTest { every { requiredErrorView.setLocaleText(any()) } just Runs every { titleView.setLocaleContentDescription(any()) } just Runs - every { titleView.setAccessibilityHint(any()) } just Runs + every { titleView.setLocaleAccessibilityHint(any()) } just Runs listener = mockk(relaxed = true) } @@ -129,7 +130,7 @@ class SurveyViewHolderTest { viewHolder.onBind(questionItem, listener) - verify(exactly = 0) { titleView.setAccessibilityHint(any()) } + verify(exactly = 0) { titleView.setLocaleAccessibilityHint(any()) } } @Test diff --git a/widgetssdk/src/testSnapshot/java/com/glia/widgets/snapshotutils/SnapshotOperatorMessage.kt b/widgetssdk/src/testSnapshot/java/com/glia/widgets/snapshotutils/SnapshotOperatorMessage.kt index a88763eea..7905c0cad 100644 --- a/widgetssdk/src/testSnapshot/java/com/glia/widgets/snapshotutils/SnapshotOperatorMessage.kt +++ b/widgetssdk/src/testSnapshot/java/com/glia/widgets/snapshotutils/SnapshotOperatorMessage.kt @@ -14,9 +14,10 @@ internal interface SnapshotOperatorMessage : SnapshotStrings { operatorProfileImgUrl: String? = null, operatorId: String? = "operatorId", operatorName: String? = "Snap Shot", - content: String? + content: String?, + announced: Boolean = false ) = OperatorMessageItem.PlainText( - id, timestamp, showChatHead, operatorProfileImgUrl, operatorId, operatorName, content + id, timestamp, showChatHead, operatorProfileImgUrl, operatorId, operatorName, content, announced ) fun operatorMessageResponseCard( @@ -27,10 +28,11 @@ internal interface SnapshotOperatorMessage : SnapshotStrings { operatorId: String? = "operatorId", operatorName: String? = "Snap Shot", content: String?, + announced: Boolean = false, singleChoiceOptions: List = shortLengthTexts().mapIndexed { i, s -> singleChoiceOption(s, i.toString()) }, choiceCardImageUrl: String? = null ) = OperatorMessageItem.ResponseCard( - id, timestamp, showChatHead, operatorProfileImgUrl, operatorId, operatorName, content, singleChoiceOptions, choiceCardImageUrl + id, timestamp, showChatHead, operatorProfileImgUrl, operatorId, operatorName, content, announced, singleChoiceOptions, choiceCardImageUrl ) fun singleChoiceOption(