mirror of
https://github.com/SchildiChat/SchildiChat-android.git
synced 2024-11-22 01:15:54 +03:00
Merge pull request #8426 from vector-im/feature/bma/a11yFixes
A11y fixes
This commit is contained in:
commit
7c3ecec92a
24 changed files with 242 additions and 55 deletions
1
changelog.d/8426.misc
Normal file
1
changelog.d/8426.misc
Normal file
|
@ -0,0 +1 @@
|
|||
Improve keyboard navigation and accessibility when using a screen reader.
|
|
@ -1470,6 +1470,9 @@
|
|||
<string name="reason_colon">Reason: %1$s</string>
|
||||
|
||||
<string name="avatar">Avatar</string>
|
||||
<string name="avatar_of_space">Avatar of space %1$s</string>
|
||||
<string name="avatar_of_room">Avatar of room %1$s</string>
|
||||
<string name="avatar_of_user">Profile picture of user %1$s</string>
|
||||
|
||||
<!-- Consent modal -->
|
||||
<string name="dialog_user_consent_content">To continue using the %1$s homeserver you must review and agree to the terms and conditions.</string>
|
||||
|
|
|
@ -4,6 +4,9 @@
|
|||
<style name="Widget.Vector.AppBarLayout" parent="Widget.MaterialComponents.AppBarLayout.Primary">
|
||||
<item name="android:background">?vctr_toolbar_background</item>
|
||||
<item name="elevation">4dp</item>
|
||||
|
||||
<!-- a11y -->
|
||||
<item name="android:touchscreenBlocksFocus">false</item>
|
||||
</style>
|
||||
|
||||
</resources>
|
|
@ -12,6 +12,9 @@
|
|||
<item name="subtitleTextAppearance">@style/TextAppearance.Vector.Widget.ActionBarSubTitle</item>
|
||||
|
||||
<item name="navigationIconTint">?vctr_content_secondary</item>
|
||||
|
||||
<!-- a11y -->
|
||||
<item name="android:touchscreenBlocksFocus">false</item>
|
||||
</style>
|
||||
|
||||
<!-- Default toolbar style -->
|
||||
|
@ -39,14 +42,24 @@
|
|||
<item name="android:textSize">12sp</item>
|
||||
</style>
|
||||
|
||||
<!-- Material 3 -->
|
||||
<!-- CollapsingToolbar -->
|
||||
|
||||
<style name="Widget.Vector.Material3.Toolbar" parent="Widget.Material3.Toolbar" />
|
||||
<style name="Widget.Vector.CollapsingToolbar" parent="Widget.Material3.CollapsingToolbar">
|
||||
<!-- a11y -->
|
||||
<item name="android:touchscreenBlocksFocus">false</item>
|
||||
</style>
|
||||
|
||||
<style name="Widget.Vector.Material3.CollapsingToolbar.Medium" parent="Widget.Material3.CollapsingToolbar.Medium">
|
||||
<style name="Widget.Vector.CollapsingToolbar.Medium" parent="Widget.Material3.CollapsingToolbar.Medium">
|
||||
<item name="expandedTitleTextAppearance">@style/TextAppearance.Vector.Title.Medium</item>
|
||||
<item name="expandedTitleMarginBottom">20dp</item>
|
||||
<item name="collapsedTitleTextAppearance">@style/TextAppearance.Vector.Headline.Bold</item>
|
||||
<!-- a11y -->
|
||||
<item name="android:touchscreenBlocksFocus">false</item>
|
||||
</style>
|
||||
|
||||
<style name="Widget.Vector.CollapsingToolbar.Large" parent="Widget.Material3.CollapsingToolbar.Large">
|
||||
<!-- a11y -->
|
||||
<item name="android:touchscreenBlocksFocus">false</item>
|
||||
</style>
|
||||
|
||||
</resources>
|
||||
|
|
|
@ -83,6 +83,9 @@
|
|||
<item name="android:textViewStyle">@style/Widget.Vector.TextView.Body</item>
|
||||
<item name="materialButtonStyle">@style/Widget.Vector.Button</item>
|
||||
<item name="toolbarStyle">@style/Widget.Vector.Toolbar</item>
|
||||
<item name="collapsingToolbarLayoutStyle">@style/Widget.Vector.CollapsingToolbar</item>
|
||||
<item name="collapsingToolbarLayoutMediumStyle">@style/Widget.Vector.CollapsingToolbar.Medium</item>
|
||||
<item name="collapsingToolbarLayoutLargeStyle">@style/Widget.Vector.CollapsingToolbar.Large</item>
|
||||
<item name="bottomNavigationStyle">@style/BottomNavigation.Vector</item>
|
||||
<item name="searchViewStyle">@style/Widget.Vector.SearchView</item>
|
||||
<item name="textInputStyle">@style/Widget.Vector.TextInputLayout</item>
|
||||
|
|
|
@ -83,6 +83,9 @@
|
|||
<item name="android:textViewStyle">@style/Widget.Vector.TextView.Body</item>
|
||||
<item name="materialButtonStyle">@style/Widget.Vector.Button</item>
|
||||
<item name="toolbarStyle">@style/Widget.Vector.Toolbar</item>
|
||||
<item name="collapsingToolbarLayoutStyle">@style/Widget.Vector.CollapsingToolbar</item>
|
||||
<item name="collapsingToolbarLayoutMediumStyle">@style/Widget.Vector.CollapsingToolbar.Medium</item>
|
||||
<item name="collapsingToolbarLayoutLargeStyle">@style/Widget.Vector.CollapsingToolbar.Large</item>
|
||||
<item name="bottomNavigationStyle">@style/BottomNavigation.Vector</item>
|
||||
<item name="searchViewStyle">@style/Widget.Vector.SearchView</item>
|
||||
<item name="textInputStyle">@style/Widget.Vector.TextInputLayout</item>
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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<VB : ViewBinding> : 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()
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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()
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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) {
|
||||
|
|
|
@ -118,5 +118,6 @@ class BootstrapEnterPassphraseFragment :
|
|||
}
|
||||
}
|
||||
}
|
||||
views.bootstrapDescriptionText.giveAccessibilityFocusOnce()
|
||||
}
|
||||
}
|
||||
|
|
|
@ -141,6 +141,7 @@ class BootstrapMigrateBackupFragment :
|
|||
|
||||
views.bootstrapMigrateUseFile.isVisible = false
|
||||
}
|
||||
views.bootstrapDescriptionText.giveAccessibilityFocusOnce()
|
||||
}
|
||||
|
||||
private val importFileStartForActivityResult = registerStartForActivityResult { activityResult ->
|
||||
|
|
|
@ -78,5 +78,6 @@ class BootstrapReAuthFragment :
|
|||
views.bootstrapCancelButton.isVisible = true
|
||||
views.bootstrapRetryButton.isVisible = true
|
||||
}
|
||||
views.bootstrapDescriptionText.giveAccessibilityFocusOnce()
|
||||
}
|
||||
}
|
||||
|
|
|
@ -117,5 +117,6 @@ class BootstrapSaveRecoveryKeyFragment :
|
|||
|
||||
views.recoveryContinue.isVisible = step.isSaved
|
||||
views.bootstrapRecoveryKeyText.text = state.recoveryKeyCreationInfo?.recoveryKey?.formatRecoveryKey()
|
||||
views.bootstrapSaveText.giveAccessibilityFocusOnce()
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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) {
|
||||
|
|
|
@ -52,5 +52,6 @@ class BootstrapWaitingFragment :
|
|||
views.bootstrapDescriptionText.isVisible = false
|
||||
}
|
||||
}
|
||||
views.bootstrapDescriptionText.giveAccessibilityFocusOnce()
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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<FragmentProgressBinding>() {
|
||||
override fun getBinding(inflater: LayoutInflater, container: ViewGroup?): FragmentProgressBinding {
|
||||
return FragmentProgressBinding.inflate(inflater, container, false)
|
||||
}
|
||||
}
|
|
@ -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 SelfVerificationFragment : VectorBaseFragment<BottomSheetVerificationChil
|
|||
|
||||
@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<BottomSheetVerificationChil
|
|||
|
||||
override fun onDestroyView() {
|
||||
views.bottomSheetVerificationRecyclerView.cleanup()
|
||||
controller.removeModelBuildListener(modelBuildListener)
|
||||
controller.listener = null
|
||||
super.onDestroyView()
|
||||
}
|
||||
|
||||
private fun setupRecyclerView() {
|
||||
views.bottomSheetVerificationRecyclerView.configureWith(controller, hasFixedSize = false, disableItemAnimation = true)
|
||||
controller.addModelBuildListener(modelBuildListener)
|
||||
controller.listener = this
|
||||
}
|
||||
|
||||
override fun invalidate() = withState(viewModel) { state ->
|
||||
// Timber.w("VALR: invalidate with State: ${state.pendingRequest}")
|
||||
if (state.isNewScreen()) {
|
||||
requestAccessibilityFocus = true
|
||||
}
|
||||
controller.update(state)
|
||||
}
|
||||
|
||||
|
@ -176,4 +198,41 @@ class SelfVerificationFragment : VectorBaseFragment<BottomSheetVerificationChil
|
|||
override fun declineRequest() {
|
||||
viewModel.handle(VerificationAction.CancelPendingVerification)
|
||||
}
|
||||
|
||||
private var currentScreenIndex = -1
|
||||
|
||||
private fun SelfVerificationViewState.isNewScreen(): Boolean {
|
||||
val newIndex = toScreenIndex()
|
||||
if (currentScreenIndex == newIndex) {
|
||||
return false
|
||||
}
|
||||
currentScreenIndex = newIndex
|
||||
return true
|
||||
}
|
||||
|
||||
private fun SelfVerificationViewState.toScreenIndex(): Int {
|
||||
return if (activeAction !is UserAction.None) {
|
||||
when (activeAction) {
|
||||
UserAction.ConfirmCancel -> 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
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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<BottomSheetVerificationChild
|
|||
|
||||
@Inject lateinit var controller: UserVerificationController
|
||||
|
||||
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(UserVerificationViewModel::class)
|
||||
|
||||
override fun getBinding(inflater: LayoutInflater, container: ViewGroup?): BottomSheetVerificationChildFragmentBinding {
|
||||
|
@ -58,17 +75,22 @@ class UserVerificationFragment : VectorBaseFragment<BottomSheetVerificationChild
|
|||
|
||||
override fun onDestroyView() {
|
||||
views.bottomSheetVerificationRecyclerView.cleanup()
|
||||
controller.removeModelBuildListener(modelBuildListener)
|
||||
controller.listener = null
|
||||
super.onDestroyView()
|
||||
}
|
||||
|
||||
private fun setupRecyclerView() {
|
||||
views.bottomSheetVerificationRecyclerView.configureWith(controller, hasFixedSize = false, disableItemAnimation = true)
|
||||
controller.addModelBuildListener(modelBuildListener)
|
||||
controller.listener = this
|
||||
}
|
||||
|
||||
override fun invalidate() = withState(viewModel) { state ->
|
||||
// Timber.w("VALR: invalidate with State: ${state.pendingRequest}")
|
||||
if (state.isNewScreen()) {
|
||||
requestAccessibilityFocus = true
|
||||
}
|
||||
controller.update(state)
|
||||
}
|
||||
|
||||
|
@ -148,4 +170,34 @@ class UserVerificationFragment : VectorBaseFragment<BottomSheetVerificationChild
|
|||
override fun onUserConfirmsQrCodeScanned() {
|
||||
viewModel.handle(VerificationAction.OtherUserScannedSuccessfully)
|
||||
}
|
||||
|
||||
private var currentScreenIndex = -1
|
||||
|
||||
private fun UserVerificationViewState.isNewScreen(): Boolean {
|
||||
val newIndex = toScreenIndex()
|
||||
if (currentScreenIndex == newIndex) {
|
||||
return false
|
||||
}
|
||||
currentScreenIndex = newIndex
|
||||
return true
|
||||
}
|
||||
|
||||
private fun UserVerificationViewState.toScreenIndex(): Int {
|
||||
return 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
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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<View>(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) {
|
||||
|
|
|
@ -49,15 +49,14 @@
|
|||
app:layout_constraintTop_toBottomOf="@id/syncStateView">
|
||||
|
||||
<com.google.android.material.appbar.CollapsingToolbarLayout
|
||||
style="?attr/collapsingToolbarLayoutMediumStyle"
|
||||
android:id="@+id/collapsing_toolbar"
|
||||
style="@style/Widget.Vector.Material3.CollapsingToolbar.Medium"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="?attr/collapsingToolbarLayoutMediumSize"
|
||||
app:layout_scrollFlags="scroll|exitUntilCollapsed">
|
||||
|
||||
<com.google.android.material.appbar.MaterialToolbar
|
||||
android:id="@+id/toolbar"
|
||||
style="@style/Widget.Vector.Material3.Toolbar"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="?attr/actionBarSize"
|
||||
android:elevation="0dp"
|
||||
|
|
|
@ -1,14 +0,0 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent">
|
||||
|
||||
<ProgressBar
|
||||
android:id="@+id/progressBar"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_centerInParent="true"
|
||||
android:layout_marginTop="@dimen/layout_vertical_margin_big"
|
||||
android:indeterminate="true" />
|
||||
|
||||
</RelativeLayout>
|
Loading…
Reference in a new issue