diff --git a/changelog.d/8426.misc b/changelog.d/8426.misc
new file mode 100644
index 0000000000..6e3b5013c8
--- /dev/null
+++ b/changelog.d/8426.misc
@@ -0,0 +1 @@
+Improve keyboard navigation and accessibility when using a screen reader.
diff --git a/library/ui-strings/src/main/res/values/strings.xml b/library/ui-strings/src/main/res/values/strings.xml
index 1a1546459e..3d1a36d4c3 100644
--- a/library/ui-strings/src/main/res/values/strings.xml
+++ b/library/ui-strings/src/main/res/values/strings.xml
@@ -1470,6 +1470,9 @@
Reason: %1$s
Avatar
+ Avatar of space %1$s
+ Avatar of room %1$s
+ Profile picture of user %1$s
To continue using the %1$s homeserver you must review and agree to the terms and conditions.
diff --git a/library/ui-styles/src/main/res/values/styles_app_bar_layout.xml b/library/ui-styles/src/main/res/values/styles_app_bar_layout.xml
index 973a2c5e4a..431480ed6b 100644
--- a/library/ui-styles/src/main/res/values/styles_app_bar_layout.xml
+++ b/library/ui-styles/src/main/res/values/styles_app_bar_layout.xml
@@ -4,6 +4,9 @@
-
\ No newline at end of file
+
diff --git a/library/ui-styles/src/main/res/values/styles_toolbar.xml b/library/ui-styles/src/main/res/values/styles_toolbar.xml
index 893de92aae..dfe5c33733 100644
--- a/library/ui-styles/src/main/res/values/styles_toolbar.xml
+++ b/library/ui-styles/src/main/res/values/styles_toolbar.xml
@@ -12,6 +12,9 @@
- @style/TextAppearance.Vector.Widget.ActionBarSubTitle
- ?vctr_content_secondary
+
+
+ - false
@@ -39,14 +42,24 @@
- 12sp
-
+
-
+
-
+
+
diff --git a/library/ui-styles/src/main/res/values/theme_dark.xml b/library/ui-styles/src/main/res/values/theme_dark.xml
index 9665b7335c..24758edc62 100644
--- a/library/ui-styles/src/main/res/values/theme_dark.xml
+++ b/library/ui-styles/src/main/res/values/theme_dark.xml
@@ -83,6 +83,9 @@
- @style/Widget.Vector.TextView.Body
- @style/Widget.Vector.Button
- @style/Widget.Vector.Toolbar
+ - @style/Widget.Vector.CollapsingToolbar
+ - @style/Widget.Vector.CollapsingToolbar.Medium
+ - @style/Widget.Vector.CollapsingToolbar.Large
- @style/BottomNavigation.Vector
- @style/Widget.Vector.SearchView
- @style/Widget.Vector.TextInputLayout
diff --git a/library/ui-styles/src/main/res/values/theme_light.xml b/library/ui-styles/src/main/res/values/theme_light.xml
index c19fe8a111..23782ee34b 100644
--- a/library/ui-styles/src/main/res/values/theme_light.xml
+++ b/library/ui-styles/src/main/res/values/theme_light.xml
@@ -83,6 +83,9 @@
- @style/Widget.Vector.TextView.Body
- @style/Widget.Vector.Button
- @style/Widget.Vector.Toolbar
+ - @style/Widget.Vector.CollapsingToolbar
+ - @style/Widget.Vector.CollapsingToolbar.Medium
+ - @style/Widget.Vector.CollapsingToolbar.Large
- @style/BottomNavigation.Vector
- @style/Widget.Vector.SearchView
- @style/Widget.Vector.TextInputLayout
diff --git a/vector/src/main/java/im/vector/app/core/extensions/ViewExtensions.kt b/vector/src/main/java/im/vector/app/core/extensions/ViewExtensions.kt
index 156809d5ad..55ad5294d9 100644
--- a/vector/src/main/java/im/vector/app/core/extensions/ViewExtensions.kt
+++ b/vector/src/main/java/im/vector/app/core/extensions/ViewExtensions.kt
@@ -20,6 +20,8 @@ import android.graphics.drawable.Drawable
import android.text.InputType
import android.view.View
import android.view.ViewGroup
+import android.view.accessibility.AccessibilityEvent
+import android.view.accessibility.AccessibilityNodeInfo
import android.widget.EditText
import android.widget.ImageView
import androidx.annotation.AttrRes
@@ -28,6 +30,7 @@ import androidx.annotation.DrawableRes
import androidx.appcompat.widget.SearchView
import androidx.core.content.ContextCompat
import androidx.core.graphics.drawable.DrawableCompat
+import androidx.core.view.ViewCompat
import androidx.core.view.isVisible
import androidx.transition.ChangeBounds
import androidx.transition.Fade
@@ -97,6 +100,14 @@ fun View.setAttributeBackground(@AttrRes attributeId: Int) {
setBackgroundResource(attribute.resourceId)
}
+/**
+ * Inspired from https://stackoverflow.com/a/64597532/1472514. Safer to call the 2 available API.
+ */
+fun View.giveAccessibilityFocus() {
+ ViewCompat.performAccessibilityAction(this, AccessibilityNodeInfo.ACTION_ACCESSIBILITY_FOCUS, null)
+ sendAccessibilityEvent(AccessibilityEvent.TYPE_VIEW_SELECTED)
+}
+
fun ViewGroup.animateLayoutChange(animationDuration: Long, transitionComplete: (() -> Unit)? = null) {
val transition = TransitionSet().apply {
ordering = TransitionSet.ORDERING_SEQUENTIAL
diff --git a/vector/src/main/java/im/vector/app/core/platform/VectorBaseFragment.kt b/vector/src/main/java/im/vector/app/core/platform/VectorBaseFragment.kt
index a82cef54e5..b57f288c09 100644
--- a/vector/src/main/java/im/vector/app/core/platform/VectorBaseFragment.kt
+++ b/vector/src/main/java/im/vector/app/core/platform/VectorBaseFragment.kt
@@ -45,6 +45,7 @@ import im.vector.app.R
import im.vector.app.core.di.ActivityEntryPoint
import im.vector.app.core.dialogs.UnrecognizedCertificateDialog
import im.vector.app.core.error.ErrorFormatter
+import im.vector.app.core.extensions.giveAccessibilityFocus
import im.vector.app.core.extensions.singletonEntryPoint
import im.vector.app.core.extensions.toMvRxBundle
import im.vector.app.core.utils.ToolbarConfig
@@ -318,4 +319,19 @@ abstract class VectorBaseFragment : Fragment(), MavericksView
.setPositiveButton(R.string.ok, null)
.show()
}
+
+ /* ==========================================================================================
+ * Accessibility - a11y
+ * ========================================================================================== */
+
+ private var hasBeenAccessibilityFocused = false
+
+ /**
+ * Ensure the View get the accessibility focus. This method has effect only once per fragment instance.
+ */
+ protected fun View.giveAccessibilityFocusOnce() {
+ if (hasBeenAccessibilityFocused) return
+ hasBeenAccessibilityFocused = true
+ giveAccessibilityFocus()
+ }
}
diff --git a/vector/src/main/java/im/vector/app/features/crypto/recover/BootstrapConclusionFragment.kt b/vector/src/main/java/im/vector/app/features/crypto/recover/BootstrapConclusionFragment.kt
index 22ecd3dafd..fd1eb821db 100644
--- a/vector/src/main/java/im/vector/app/features/crypto/recover/BootstrapConclusionFragment.kt
+++ b/vector/src/main/java/im/vector/app/features/crypto/recover/BootstrapConclusionFragment.kt
@@ -60,5 +60,6 @@ class BootstrapConclusionFragment :
.toSpannable()
.colorizeMatchingText(getString(R.string.recovery_passphrase), colorProvider.getColorFromAttribute(android.R.attr.textColorLink))
.colorizeMatchingText(getString(R.string.message_key), colorProvider.getColorFromAttribute(android.R.attr.textColorLink))
+ views.bootstrapConclusionText.giveAccessibilityFocusOnce()
}
}
diff --git a/vector/src/main/java/im/vector/app/features/crypto/recover/BootstrapConfirmPassphraseFragment.kt b/vector/src/main/java/im/vector/app/features/crypto/recover/BootstrapConfirmPassphraseFragment.kt
index 285721ee75..3942fc6528 100644
--- a/vector/src/main/java/im/vector/app/features/crypto/recover/BootstrapConfirmPassphraseFragment.kt
+++ b/vector/src/main/java/im/vector/app/features/crypto/recover/BootstrapConfirmPassphraseFragment.kt
@@ -52,6 +52,7 @@ class BootstrapConfirmPassphraseFragment :
views.ssssPassphraseSecurityProgress.isGone = true
views.bootstrapDescriptionText.text = getString(R.string.set_a_security_phrase_again_notice)
+ views.bootstrapDescriptionText.giveAccessibilityFocusOnce()
views.ssssPassphraseEnterEdittext.hint = getString(R.string.set_a_security_phrase_hint)
withState(sharedViewModel) {
diff --git a/vector/src/main/java/im/vector/app/features/crypto/recover/BootstrapEnterPassphraseFragment.kt b/vector/src/main/java/im/vector/app/features/crypto/recover/BootstrapEnterPassphraseFragment.kt
index 78f0bc6284..262abd306d 100644
--- a/vector/src/main/java/im/vector/app/features/crypto/recover/BootstrapEnterPassphraseFragment.kt
+++ b/vector/src/main/java/im/vector/app/features/crypto/recover/BootstrapEnterPassphraseFragment.kt
@@ -118,5 +118,6 @@ class BootstrapEnterPassphraseFragment :
}
}
}
+ views.bootstrapDescriptionText.giveAccessibilityFocusOnce()
}
}
diff --git a/vector/src/main/java/im/vector/app/features/crypto/recover/BootstrapMigrateBackupFragment.kt b/vector/src/main/java/im/vector/app/features/crypto/recover/BootstrapMigrateBackupFragment.kt
index 2802c872ee..8df5ce7ad3 100644
--- a/vector/src/main/java/im/vector/app/features/crypto/recover/BootstrapMigrateBackupFragment.kt
+++ b/vector/src/main/java/im/vector/app/features/crypto/recover/BootstrapMigrateBackupFragment.kt
@@ -141,6 +141,7 @@ class BootstrapMigrateBackupFragment :
views.bootstrapMigrateUseFile.isVisible = false
}
+ views.bootstrapDescriptionText.giveAccessibilityFocusOnce()
}
private val importFileStartForActivityResult = registerStartForActivityResult { activityResult ->
diff --git a/vector/src/main/java/im/vector/app/features/crypto/recover/BootstrapReAuthFragment.kt b/vector/src/main/java/im/vector/app/features/crypto/recover/BootstrapReAuthFragment.kt
index d5e60631a5..f32ba735a1 100644
--- a/vector/src/main/java/im/vector/app/features/crypto/recover/BootstrapReAuthFragment.kt
+++ b/vector/src/main/java/im/vector/app/features/crypto/recover/BootstrapReAuthFragment.kt
@@ -78,5 +78,6 @@ class BootstrapReAuthFragment :
views.bootstrapCancelButton.isVisible = true
views.bootstrapRetryButton.isVisible = true
}
+ views.bootstrapDescriptionText.giveAccessibilityFocusOnce()
}
}
diff --git a/vector/src/main/java/im/vector/app/features/crypto/recover/BootstrapSaveRecoveryKeyFragment.kt b/vector/src/main/java/im/vector/app/features/crypto/recover/BootstrapSaveRecoveryKeyFragment.kt
index 21d68dfe99..88db55a291 100644
--- a/vector/src/main/java/im/vector/app/features/crypto/recover/BootstrapSaveRecoveryKeyFragment.kt
+++ b/vector/src/main/java/im/vector/app/features/crypto/recover/BootstrapSaveRecoveryKeyFragment.kt
@@ -117,5 +117,6 @@ class BootstrapSaveRecoveryKeyFragment :
views.recoveryContinue.isVisible = step.isSaved
views.bootstrapRecoveryKeyText.text = state.recoveryKeyCreationInfo?.recoveryKey?.formatRecoveryKey()
+ views.bootstrapSaveText.giveAccessibilityFocusOnce()
}
}
diff --git a/vector/src/main/java/im/vector/app/features/crypto/recover/BootstrapSetupRecoveryKeyFragment.kt b/vector/src/main/java/im/vector/app/features/crypto/recover/BootstrapSetupRecoveryKeyFragment.kt
index b03cbe87a8..a28a7c8f56 100644
--- a/vector/src/main/java/im/vector/app/features/crypto/recover/BootstrapSetupRecoveryKeyFragment.kt
+++ b/vector/src/main/java/im/vector/app/features/crypto/recover/BootstrapSetupRecoveryKeyFragment.kt
@@ -68,6 +68,7 @@ class BootstrapSetupRecoveryKeyFragment :
// Choose between create a passphrase or use a recovery key
renderBackupMethodActions(firstFormStep.methods)
}
+ views.bootstrapSetupSecureText.giveAccessibilityFocusOnce()
}
private fun renderStateWithExistingKeyBackup() = with(views) {
diff --git a/vector/src/main/java/im/vector/app/features/crypto/recover/BootstrapWaitingFragment.kt b/vector/src/main/java/im/vector/app/features/crypto/recover/BootstrapWaitingFragment.kt
index 310bb5ac37..9d9cf6cdd2 100644
--- a/vector/src/main/java/im/vector/app/features/crypto/recover/BootstrapWaitingFragment.kt
+++ b/vector/src/main/java/im/vector/app/features/crypto/recover/BootstrapWaitingFragment.kt
@@ -52,5 +52,6 @@ class BootstrapWaitingFragment :
views.bootstrapDescriptionText.isVisible = false
}
}
+ views.bootstrapDescriptionText.giveAccessibilityFocusOnce()
}
}
diff --git a/vector/src/main/java/im/vector/app/features/crypto/verification/QuadSLoadingFragment.kt b/vector/src/main/java/im/vector/app/features/crypto/verification/QuadSLoadingFragment.kt
deleted file mode 100644
index 5b6902df98..0000000000
--- a/vector/src/main/java/im/vector/app/features/crypto/verification/QuadSLoadingFragment.kt
+++ /dev/null
@@ -1,31 +0,0 @@
-/*
- * Copyright (c) 2020 New Vector Ltd
- *
- * 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 im.vector.app.features.crypto.verification
-
-import android.view.LayoutInflater
-import android.view.ViewGroup
-import dagger.hilt.android.AndroidEntryPoint
-import im.vector.app.core.platform.VectorBaseFragment
-import im.vector.app.databinding.FragmentProgressBinding
-
-@AndroidEntryPoint
-class QuadSLoadingFragment :
- VectorBaseFragment() {
- override fun getBinding(inflater: LayoutInflater, container: ViewGroup?): FragmentProgressBinding {
- return FragmentProgressBinding.inflate(inflater, container, false)
- }
-}
diff --git a/vector/src/main/java/im/vector/app/features/crypto/verification/self/SelfVerificationFragment.kt b/vector/src/main/java/im/vector/app/features/crypto/verification/self/SelfVerificationFragment.kt
index ae20955d45..8badddba8b 100644
--- a/vector/src/main/java/im/vector/app/features/crypto/verification/self/SelfVerificationFragment.kt
+++ b/vector/src/main/java/im/vector/app/features/crypto/verification/self/SelfVerificationFragment.kt
@@ -21,12 +21,18 @@ import android.os.Bundle
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
+import com.airbnb.epoxy.OnModelBuildFinishedListener
+import com.airbnb.mvrx.Fail
+import com.airbnb.mvrx.Loading
+import com.airbnb.mvrx.Success
+import com.airbnb.mvrx.Uninitialized
import com.airbnb.mvrx.parentFragmentViewModel
import com.airbnb.mvrx.withState
import dagger.hilt.android.AndroidEntryPoint
import im.vector.app.R
import im.vector.app.core.extensions.cleanup
import im.vector.app.core.extensions.configureWith
+import im.vector.app.core.extensions.giveAccessibilityFocus
import im.vector.app.core.extensions.registerStartForActivityResult
import im.vector.app.core.platform.VectorBaseFragment
import im.vector.app.core.utils.PERMISSIONS_FOR_TAKING_PHOTO
@@ -36,15 +42,26 @@ import im.vector.app.core.utils.registerForPermissionsResult
import im.vector.app.databinding.BottomSheetVerificationChildFragmentBinding
import im.vector.app.features.crypto.verification.VerificationAction
import im.vector.app.features.qrcode.QrCodeScannerActivity
+import org.matrix.android.sdk.api.session.crypto.verification.EVerificationState
import timber.log.Timber
import javax.inject.Inject
@AndroidEntryPoint
-class SelfVerificationFragment : VectorBaseFragment(),
+class SelfVerificationFragment : VectorBaseFragment(),
SelfVerificationController.InteractionListener {
@Inject lateinit var controller: SelfVerificationController
+ private var requestAccessibilityFocus: Boolean = false
+ private val modelBuildListener: OnModelBuildFinishedListener = OnModelBuildFinishedListener {
+ if (requestAccessibilityFocus) {
+ // Do not use giveAccessibilityFocusOnce() here.
+ views.bottomSheetVerificationRecyclerView.layoutManager?.findViewByPosition(0)?.giveAccessibilityFocus()
+ requestAccessibilityFocus = false
+ // Note: it does not work when the recycler view is displayed for the first time, because findViewByPosition(0) is null
+ }
+ }
+
private val viewModel by parentFragmentViewModel(SelfVerificationViewModel::class)
override fun getBinding(inflater: LayoutInflater, container: ViewGroup?): BottomSheetVerificationChildFragmentBinding {
@@ -58,17 +75,22 @@ class SelfVerificationFragment : VectorBaseFragment
// Timber.w("VALR: invalidate with State: ${state.pendingRequest}")
+ if (state.isNewScreen()) {
+ requestAccessibilityFocus = true
+ }
controller.update(state)
}
@@ -176,4 +198,41 @@ class SelfVerificationFragment : VectorBaseFragment 30
+ UserAction.None -> 31
+ }
+ } else {
+ when (pendingRequest) {
+ is Fail -> 0
+ is Loading -> 1
+ is Success -> when (pendingRequest.invoke().state) {
+ EVerificationState.WaitingForReady -> 10
+ EVerificationState.Requested -> 11
+ EVerificationState.Ready -> 12
+ EVerificationState.Started -> 13
+ EVerificationState.WeStarted -> 14
+ EVerificationState.WaitingForDone -> 15
+ EVerificationState.Done -> 16
+ EVerificationState.Cancelled -> 17
+ EVerificationState.HandledByOtherSession -> 18
+ }
+ Uninitialized -> 2
+ }
+ }
+ }
}
diff --git a/vector/src/main/java/im/vector/app/features/crypto/verification/user/UserVerificationFragment.kt b/vector/src/main/java/im/vector/app/features/crypto/verification/user/UserVerificationFragment.kt
index 415b8069e9..7aac6dcb58 100644
--- a/vector/src/main/java/im/vector/app/features/crypto/verification/user/UserVerificationFragment.kt
+++ b/vector/src/main/java/im/vector/app/features/crypto/verification/user/UserVerificationFragment.kt
@@ -21,12 +21,18 @@ import android.os.Bundle
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
+import com.airbnb.epoxy.OnModelBuildFinishedListener
+import com.airbnb.mvrx.Fail
+import com.airbnb.mvrx.Loading
+import com.airbnb.mvrx.Success
+import com.airbnb.mvrx.Uninitialized
import com.airbnb.mvrx.parentFragmentViewModel
import com.airbnb.mvrx.withState
import dagger.hilt.android.AndroidEntryPoint
import im.vector.app.R
import im.vector.app.core.extensions.cleanup
import im.vector.app.core.extensions.configureWith
+import im.vector.app.core.extensions.giveAccessibilityFocus
import im.vector.app.core.extensions.registerStartForActivityResult
import im.vector.app.core.platform.VectorBaseFragment
import im.vector.app.core.utils.PERMISSIONS_FOR_TAKING_PHOTO
@@ -36,6 +42,7 @@ import im.vector.app.core.utils.registerForPermissionsResult
import im.vector.app.databinding.BottomSheetVerificationChildFragmentBinding
import im.vector.app.features.crypto.verification.VerificationAction
import im.vector.app.features.qrcode.QrCodeScannerActivity
+import org.matrix.android.sdk.api.session.crypto.verification.EVerificationState
import timber.log.Timber
import javax.inject.Inject
@@ -45,6 +52,16 @@ class UserVerificationFragment : VectorBaseFragment
// Timber.w("VALR: invalidate with State: ${state.pendingRequest}")
+ if (state.isNewScreen()) {
+ requestAccessibilityFocus = true
+ }
controller.update(state)
}
@@ -142,10 +164,40 @@ class UserVerificationFragment : VectorBaseFragment 0
+ is Loading -> 1
+ is Success -> when (pendingRequest.invoke().state) {
+ EVerificationState.WaitingForReady -> 10
+ EVerificationState.Requested -> 11
+ EVerificationState.Ready -> 12
+ EVerificationState.Started -> 13
+ EVerificationState.WeStarted -> 14
+ EVerificationState.WaitingForDone -> 15
+ EVerificationState.Done -> 16
+ EVerificationState.Cancelled -> 17
+ EVerificationState.HandledByOtherSession -> 18
+ }
+ Uninitialized -> 2
+ }
+ }
}
diff --git a/vector/src/main/java/im/vector/app/features/home/AvatarRenderer.kt b/vector/src/main/java/im/vector/app/features/home/AvatarRenderer.kt
index f4ffbb826a..7214db41ef 100644
--- a/vector/src/main/java/im/vector/app/features/home/AvatarRenderer.kt
+++ b/vector/src/main/java/im/vector/app/features/home/AvatarRenderer.kt
@@ -35,13 +35,16 @@ import com.bumptech.glide.request.RequestOptions
import com.bumptech.glide.request.target.DrawableImageViewTarget
import com.bumptech.glide.request.target.Target
import com.bumptech.glide.signature.ObjectKey
+import im.vector.app.R
import im.vector.app.core.contacts.MappedContact
import im.vector.app.core.di.ActiveSessionHolder
import im.vector.app.core.glide.AvatarPlaceholder
import im.vector.app.core.glide.GlideApp
import im.vector.app.core.glide.GlideRequest
import im.vector.app.core.glide.GlideRequests
+import im.vector.app.core.resources.StringProvider
import im.vector.app.core.utils.DimensionConverter
+import im.vector.app.features.displayname.getBestName
import im.vector.app.features.home.room.detail.timeline.helper.MatrixItemColorProvider
import jp.wasabeef.glide.transformations.BlurTransformation
import jp.wasabeef.glide.transformations.ColorFilterTransformation
@@ -58,7 +61,8 @@ import javax.inject.Inject
class AvatarRenderer @Inject constructor(
private val activeSessionHolder: ActiveSessionHolder,
private val matrixItemColorProvider: MatrixItemColorProvider,
- private val dimensionConverter: DimensionConverter
+ private val dimensionConverter: DimensionConverter,
+ private val stringProvider: StringProvider,
) {
companion object {
@@ -67,6 +71,7 @@ class AvatarRenderer @Inject constructor(
@UiThread
fun render(matrixItem: MatrixItem, imageView: ImageView) {
+ imageView.setContentDescription(matrixItem)
render(
GlideApp.with(imageView),
matrixItem,
@@ -100,6 +105,7 @@ class AvatarRenderer @Inject constructor(
@UiThread
fun render(matrixItem: MatrixItem, imageView: ImageView, glideRequests: GlideRequests) {
+ imageView.setContentDescription(matrixItem)
render(
glideRequests,
matrixItem,
@@ -109,6 +115,7 @@ class AvatarRenderer @Inject constructor(
@UiThread
fun render(matrixItem: MatrixItem, localUri: Uri?, imageView: ImageView) {
+ imageView.setContentDescription(matrixItem)
val placeholder = getPlaceholderDrawable(matrixItem)
GlideApp.with(imageView)
.load(localUri?.let { File(localUri.path!!) })
@@ -295,4 +302,28 @@ class AvatarRenderer @Inject constructor(
return activeSessionHolder.getSafeActiveSession()?.contentUrlResolver()
?.resolveThumbnail(avatarUrl, THUMBNAIL_SIZE, THUMBNAIL_SIZE, ContentUrlResolver.ThumbnailMethod.SCALE)
}
+
+ /**
+ * Accessibility management.
+ */
+ private fun ImageView.setContentDescription(matrixItem: MatrixItem) {
+ // Do not set contentDescription if the ImageView should be ignored regarding accessibility.
+ if (isImportantForAccessibility.not()) return
+ when (matrixItem) {
+ is MatrixItem.SpaceItem -> {
+ contentDescription = stringProvider.getString(R.string.avatar_of_space, matrixItem.getBestName())
+ }
+ is MatrixItem.RoomAliasItem,
+ is MatrixItem.RoomItem -> {
+ contentDescription = stringProvider.getString(R.string.avatar_of_room, matrixItem.getBestName())
+ }
+ is MatrixItem.UserItem -> {
+ contentDescription = stringProvider.getString(R.string.avatar_of_user, matrixItem.getBestName())
+ }
+ is MatrixItem.EveryoneInRoomItem,
+ is MatrixItem.EventItem -> {
+ // NA
+ }
+ }
+ }
}
diff --git a/vector/src/main/java/im/vector/app/features/popup/PopupAlertManager.kt b/vector/src/main/java/im/vector/app/features/popup/PopupAlertManager.kt
index d3c6e83589..02275c933e 100644
--- a/vector/src/main/java/im/vector/app/features/popup/PopupAlertManager.kt
+++ b/vector/src/main/java/im/vector/app/features/popup/PopupAlertManager.kt
@@ -21,9 +21,12 @@ import android.os.Handler
import android.os.Looper
import android.view.View
import android.view.WindowInsetsController.APPEARANCE_LIGHT_STATUS_BARS
+import androidx.core.view.ViewCompat
import com.tapadoo.alerter.Alerter
import im.vector.app.R
+import im.vector.app.core.extensions.giveAccessibilityFocus
import im.vector.app.core.platform.VectorBaseActivity
+import im.vector.app.core.resources.StringProvider
import im.vector.app.core.utils.isAnimationEnabled
import im.vector.app.features.MainActivity
import im.vector.app.features.analytics.ui.consent.AnalyticsOptInActivity
@@ -46,6 +49,7 @@ import javax.inject.Singleton
@Singleton
class PopupAlertManager @Inject constructor(
private val clock: Clock,
+ private val stringProvider: StringProvider,
) {
companion object {
@@ -282,6 +286,9 @@ class PopupAlertManager @Inject constructor(
}
currentIsDismissed()
}
+ .setOnShowListener {
+ handleAccessibility(activity, animate)
+ }
.enableSwipeToDismiss()
.enableInfiniteDuration(true)
.apply {
@@ -297,6 +304,29 @@ class PopupAlertManager @Inject constructor(
.show()
}
+ /* a11y */
+ private fun handleAccessibility(activity: Activity, giveFocus: Boolean) {
+ activity.window.decorView.findViewById(R.id.llAlertBackground)?.let { alertView ->
+ alertView.importantForAccessibility = View.IMPORTANT_FOR_ACCESSIBILITY_YES
+
+ // Add close action for a11y (same action than swipe). User can select the action by swiping on the screen vertically,
+ // and double tap to perform the action
+ ViewCompat.addAccessibilityAction(
+ alertView,
+ stringProvider.getString(R.string.action_close)
+ ) { _, _ ->
+ currentIsDismissed()
+ Alerter.hide()
+ true
+ }
+
+ // And give focus to the alert right now, only for first display, i.e. when there is an animation.
+ if (giveFocus) {
+ alertView.giveAccessibilityFocus()
+ }
+ }
+ }
+
private fun currentIsDismissed() {
// current alert has been hidden
if (currentAlerter?.isLight == false) {
diff --git a/vector/src/main/res/layout/activity_filtered_rooms.xml b/vector/src/main/res/layout/activity_filtered_rooms.xml
index 07a2bc7a2e..5a3dae13dc 100644
--- a/vector/src/main/res/layout/activity_filtered_rooms.xml
+++ b/vector/src/main/res/layout/activity_filtered_rooms.xml
@@ -43,4 +43,4 @@
-
\ No newline at end of file
+
diff --git a/vector/src/main/res/layout/fragment_new_home_detail.xml b/vector/src/main/res/layout/fragment_new_home_detail.xml
index 8f603e57ab..d20223a382 100644
--- a/vector/src/main/res/layout/fragment_new_home_detail.xml
+++ b/vector/src/main/res/layout/fragment_new_home_detail.xml
@@ -49,15 +49,14 @@
app:layout_constraintTop_toBottomOf="@id/syncStateView">
-
-
-
-
-