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="reason_colon">Reason: %1$s</string>
|
||||||
|
|
||||||
<string name="avatar">Avatar</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 -->
|
<!-- 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>
|
<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">
|
<style name="Widget.Vector.AppBarLayout" parent="Widget.MaterialComponents.AppBarLayout.Primary">
|
||||||
<item name="android:background">?vctr_toolbar_background</item>
|
<item name="android:background">?vctr_toolbar_background</item>
|
||||||
<item name="elevation">4dp</item>
|
<item name="elevation">4dp</item>
|
||||||
|
|
||||||
|
<!-- a11y -->
|
||||||
|
<item name="android:touchscreenBlocksFocus">false</item>
|
||||||
</style>
|
</style>
|
||||||
|
|
||||||
</resources>
|
</resources>
|
||||||
|
|
|
@ -12,6 +12,9 @@
|
||||||
<item name="subtitleTextAppearance">@style/TextAppearance.Vector.Widget.ActionBarSubTitle</item>
|
<item name="subtitleTextAppearance">@style/TextAppearance.Vector.Widget.ActionBarSubTitle</item>
|
||||||
|
|
||||||
<item name="navigationIconTint">?vctr_content_secondary</item>
|
<item name="navigationIconTint">?vctr_content_secondary</item>
|
||||||
|
|
||||||
|
<!-- a11y -->
|
||||||
|
<item name="android:touchscreenBlocksFocus">false</item>
|
||||||
</style>
|
</style>
|
||||||
|
|
||||||
<!-- Default toolbar style -->
|
<!-- Default toolbar style -->
|
||||||
|
@ -39,14 +42,24 @@
|
||||||
<item name="android:textSize">12sp</item>
|
<item name="android:textSize">12sp</item>
|
||||||
</style>
|
</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="expandedTitleTextAppearance">@style/TextAppearance.Vector.Title.Medium</item>
|
||||||
<item name="expandedTitleMarginBottom">20dp</item>
|
<item name="expandedTitleMarginBottom">20dp</item>
|
||||||
<item name="collapsedTitleTextAppearance">@style/TextAppearance.Vector.Headline.Bold</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>
|
</style>
|
||||||
|
|
||||||
</resources>
|
</resources>
|
||||||
|
|
|
@ -83,6 +83,9 @@
|
||||||
<item name="android:textViewStyle">@style/Widget.Vector.TextView.Body</item>
|
<item name="android:textViewStyle">@style/Widget.Vector.TextView.Body</item>
|
||||||
<item name="materialButtonStyle">@style/Widget.Vector.Button</item>
|
<item name="materialButtonStyle">@style/Widget.Vector.Button</item>
|
||||||
<item name="toolbarStyle">@style/Widget.Vector.Toolbar</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="bottomNavigationStyle">@style/BottomNavigation.Vector</item>
|
||||||
<item name="searchViewStyle">@style/Widget.Vector.SearchView</item>
|
<item name="searchViewStyle">@style/Widget.Vector.SearchView</item>
|
||||||
<item name="textInputStyle">@style/Widget.Vector.TextInputLayout</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="android:textViewStyle">@style/Widget.Vector.TextView.Body</item>
|
||||||
<item name="materialButtonStyle">@style/Widget.Vector.Button</item>
|
<item name="materialButtonStyle">@style/Widget.Vector.Button</item>
|
||||||
<item name="toolbarStyle">@style/Widget.Vector.Toolbar</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="bottomNavigationStyle">@style/BottomNavigation.Vector</item>
|
||||||
<item name="searchViewStyle">@style/Widget.Vector.SearchView</item>
|
<item name="searchViewStyle">@style/Widget.Vector.SearchView</item>
|
||||||
<item name="textInputStyle">@style/Widget.Vector.TextInputLayout</item>
|
<item name="textInputStyle">@style/Widget.Vector.TextInputLayout</item>
|
||||||
|
|
|
@ -20,6 +20,8 @@ import android.graphics.drawable.Drawable
|
||||||
import android.text.InputType
|
import android.text.InputType
|
||||||
import android.view.View
|
import android.view.View
|
||||||
import android.view.ViewGroup
|
import android.view.ViewGroup
|
||||||
|
import android.view.accessibility.AccessibilityEvent
|
||||||
|
import android.view.accessibility.AccessibilityNodeInfo
|
||||||
import android.widget.EditText
|
import android.widget.EditText
|
||||||
import android.widget.ImageView
|
import android.widget.ImageView
|
||||||
import androidx.annotation.AttrRes
|
import androidx.annotation.AttrRes
|
||||||
|
@ -28,6 +30,7 @@ import androidx.annotation.DrawableRes
|
||||||
import androidx.appcompat.widget.SearchView
|
import androidx.appcompat.widget.SearchView
|
||||||
import androidx.core.content.ContextCompat
|
import androidx.core.content.ContextCompat
|
||||||
import androidx.core.graphics.drawable.DrawableCompat
|
import androidx.core.graphics.drawable.DrawableCompat
|
||||||
|
import androidx.core.view.ViewCompat
|
||||||
import androidx.core.view.isVisible
|
import androidx.core.view.isVisible
|
||||||
import androidx.transition.ChangeBounds
|
import androidx.transition.ChangeBounds
|
||||||
import androidx.transition.Fade
|
import androidx.transition.Fade
|
||||||
|
@ -97,6 +100,14 @@ fun View.setAttributeBackground(@AttrRes attributeId: Int) {
|
||||||
setBackgroundResource(attribute.resourceId)
|
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) {
|
fun ViewGroup.animateLayoutChange(animationDuration: Long, transitionComplete: (() -> Unit)? = null) {
|
||||||
val transition = TransitionSet().apply {
|
val transition = TransitionSet().apply {
|
||||||
ordering = TransitionSet.ORDERING_SEQUENTIAL
|
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.di.ActivityEntryPoint
|
||||||
import im.vector.app.core.dialogs.UnrecognizedCertificateDialog
|
import im.vector.app.core.dialogs.UnrecognizedCertificateDialog
|
||||||
import im.vector.app.core.error.ErrorFormatter
|
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.singletonEntryPoint
|
||||||
import im.vector.app.core.extensions.toMvRxBundle
|
import im.vector.app.core.extensions.toMvRxBundle
|
||||||
import im.vector.app.core.utils.ToolbarConfig
|
import im.vector.app.core.utils.ToolbarConfig
|
||||||
|
@ -318,4 +319,19 @@ abstract class VectorBaseFragment<VB : ViewBinding> : Fragment(), MavericksView
|
||||||
.setPositiveButton(R.string.ok, null)
|
.setPositiveButton(R.string.ok, null)
|
||||||
.show()
|
.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()
|
.toSpannable()
|
||||||
.colorizeMatchingText(getString(R.string.recovery_passphrase), colorProvider.getColorFromAttribute(android.R.attr.textColorLink))
|
.colorizeMatchingText(getString(R.string.recovery_passphrase), colorProvider.getColorFromAttribute(android.R.attr.textColorLink))
|
||||||
.colorizeMatchingText(getString(R.string.message_key), 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.ssssPassphraseSecurityProgress.isGone = true
|
||||||
|
|
||||||
views.bootstrapDescriptionText.text = getString(R.string.set_a_security_phrase_again_notice)
|
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)
|
views.ssssPassphraseEnterEdittext.hint = getString(R.string.set_a_security_phrase_hint)
|
||||||
|
|
||||||
withState(sharedViewModel) {
|
withState(sharedViewModel) {
|
||||||
|
|
|
@ -118,5 +118,6 @@ class BootstrapEnterPassphraseFragment :
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
views.bootstrapDescriptionText.giveAccessibilityFocusOnce()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -141,6 +141,7 @@ class BootstrapMigrateBackupFragment :
|
||||||
|
|
||||||
views.bootstrapMigrateUseFile.isVisible = false
|
views.bootstrapMigrateUseFile.isVisible = false
|
||||||
}
|
}
|
||||||
|
views.bootstrapDescriptionText.giveAccessibilityFocusOnce()
|
||||||
}
|
}
|
||||||
|
|
||||||
private val importFileStartForActivityResult = registerStartForActivityResult { activityResult ->
|
private val importFileStartForActivityResult = registerStartForActivityResult { activityResult ->
|
||||||
|
|
|
@ -78,5 +78,6 @@ class BootstrapReAuthFragment :
|
||||||
views.bootstrapCancelButton.isVisible = true
|
views.bootstrapCancelButton.isVisible = true
|
||||||
views.bootstrapRetryButton.isVisible = true
|
views.bootstrapRetryButton.isVisible = true
|
||||||
}
|
}
|
||||||
|
views.bootstrapDescriptionText.giveAccessibilityFocusOnce()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -117,5 +117,6 @@ class BootstrapSaveRecoveryKeyFragment :
|
||||||
|
|
||||||
views.recoveryContinue.isVisible = step.isSaved
|
views.recoveryContinue.isVisible = step.isSaved
|
||||||
views.bootstrapRecoveryKeyText.text = state.recoveryKeyCreationInfo?.recoveryKey?.formatRecoveryKey()
|
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
|
// Choose between create a passphrase or use a recovery key
|
||||||
renderBackupMethodActions(firstFormStep.methods)
|
renderBackupMethodActions(firstFormStep.methods)
|
||||||
}
|
}
|
||||||
|
views.bootstrapSetupSecureText.giveAccessibilityFocusOnce()
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun renderStateWithExistingKeyBackup() = with(views) {
|
private fun renderStateWithExistingKeyBackup() = with(views) {
|
||||||
|
|
|
@ -52,5 +52,6 @@ class BootstrapWaitingFragment :
|
||||||
views.bootstrapDescriptionText.isVisible = false
|
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.LayoutInflater
|
||||||
import android.view.View
|
import android.view.View
|
||||||
import android.view.ViewGroup
|
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.parentFragmentViewModel
|
||||||
import com.airbnb.mvrx.withState
|
import com.airbnb.mvrx.withState
|
||||||
import dagger.hilt.android.AndroidEntryPoint
|
import dagger.hilt.android.AndroidEntryPoint
|
||||||
import im.vector.app.R
|
import im.vector.app.R
|
||||||
import im.vector.app.core.extensions.cleanup
|
import im.vector.app.core.extensions.cleanup
|
||||||
import im.vector.app.core.extensions.configureWith
|
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.extensions.registerStartForActivityResult
|
||||||
import im.vector.app.core.platform.VectorBaseFragment
|
import im.vector.app.core.platform.VectorBaseFragment
|
||||||
import im.vector.app.core.utils.PERMISSIONS_FOR_TAKING_PHOTO
|
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.databinding.BottomSheetVerificationChildFragmentBinding
|
||||||
import im.vector.app.features.crypto.verification.VerificationAction
|
import im.vector.app.features.crypto.verification.VerificationAction
|
||||||
import im.vector.app.features.qrcode.QrCodeScannerActivity
|
import im.vector.app.features.qrcode.QrCodeScannerActivity
|
||||||
|
import org.matrix.android.sdk.api.session.crypto.verification.EVerificationState
|
||||||
import timber.log.Timber
|
import timber.log.Timber
|
||||||
import javax.inject.Inject
|
import javax.inject.Inject
|
||||||
|
|
||||||
@AndroidEntryPoint
|
@AndroidEntryPoint
|
||||||
class SelfVerificationFragment : VectorBaseFragment<BottomSheetVerificationChildFragmentBinding>(),
|
class SelfVerificationFragment : VectorBaseFragment<BottomSheetVerificationChildFragmentBinding>(),
|
||||||
SelfVerificationController.InteractionListener {
|
SelfVerificationController.InteractionListener {
|
||||||
|
|
||||||
@Inject lateinit var controller: SelfVerificationController
|
@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)
|
private val viewModel by parentFragmentViewModel(SelfVerificationViewModel::class)
|
||||||
|
|
||||||
override fun getBinding(inflater: LayoutInflater, container: ViewGroup?): BottomSheetVerificationChildFragmentBinding {
|
override fun getBinding(inflater: LayoutInflater, container: ViewGroup?): BottomSheetVerificationChildFragmentBinding {
|
||||||
|
@ -58,17 +75,22 @@ class SelfVerificationFragment : VectorBaseFragment<BottomSheetVerificationChil
|
||||||
|
|
||||||
override fun onDestroyView() {
|
override fun onDestroyView() {
|
||||||
views.bottomSheetVerificationRecyclerView.cleanup()
|
views.bottomSheetVerificationRecyclerView.cleanup()
|
||||||
|
controller.removeModelBuildListener(modelBuildListener)
|
||||||
controller.listener = null
|
controller.listener = null
|
||||||
super.onDestroyView()
|
super.onDestroyView()
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun setupRecyclerView() {
|
private fun setupRecyclerView() {
|
||||||
views.bottomSheetVerificationRecyclerView.configureWith(controller, hasFixedSize = false, disableItemAnimation = true)
|
views.bottomSheetVerificationRecyclerView.configureWith(controller, hasFixedSize = false, disableItemAnimation = true)
|
||||||
|
controller.addModelBuildListener(modelBuildListener)
|
||||||
controller.listener = this
|
controller.listener = this
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun invalidate() = withState(viewModel) { state ->
|
override fun invalidate() = withState(viewModel) { state ->
|
||||||
// Timber.w("VALR: invalidate with State: ${state.pendingRequest}")
|
// Timber.w("VALR: invalidate with State: ${state.pendingRequest}")
|
||||||
|
if (state.isNewScreen()) {
|
||||||
|
requestAccessibilityFocus = true
|
||||||
|
}
|
||||||
controller.update(state)
|
controller.update(state)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -176,4 +198,41 @@ class SelfVerificationFragment : VectorBaseFragment<BottomSheetVerificationChil
|
||||||
override fun declineRequest() {
|
override fun declineRequest() {
|
||||||
viewModel.handle(VerificationAction.CancelPendingVerification)
|
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.LayoutInflater
|
||||||
import android.view.View
|
import android.view.View
|
||||||
import android.view.ViewGroup
|
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.parentFragmentViewModel
|
||||||
import com.airbnb.mvrx.withState
|
import com.airbnb.mvrx.withState
|
||||||
import dagger.hilt.android.AndroidEntryPoint
|
import dagger.hilt.android.AndroidEntryPoint
|
||||||
import im.vector.app.R
|
import im.vector.app.R
|
||||||
import im.vector.app.core.extensions.cleanup
|
import im.vector.app.core.extensions.cleanup
|
||||||
import im.vector.app.core.extensions.configureWith
|
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.extensions.registerStartForActivityResult
|
||||||
import im.vector.app.core.platform.VectorBaseFragment
|
import im.vector.app.core.platform.VectorBaseFragment
|
||||||
import im.vector.app.core.utils.PERMISSIONS_FOR_TAKING_PHOTO
|
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.databinding.BottomSheetVerificationChildFragmentBinding
|
||||||
import im.vector.app.features.crypto.verification.VerificationAction
|
import im.vector.app.features.crypto.verification.VerificationAction
|
||||||
import im.vector.app.features.qrcode.QrCodeScannerActivity
|
import im.vector.app.features.qrcode.QrCodeScannerActivity
|
||||||
|
import org.matrix.android.sdk.api.session.crypto.verification.EVerificationState
|
||||||
import timber.log.Timber
|
import timber.log.Timber
|
||||||
import javax.inject.Inject
|
import javax.inject.Inject
|
||||||
|
|
||||||
|
@ -45,6 +52,16 @@ class UserVerificationFragment : VectorBaseFragment<BottomSheetVerificationChild
|
||||||
|
|
||||||
@Inject lateinit var controller: UserVerificationController
|
@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)
|
private val viewModel by parentFragmentViewModel(UserVerificationViewModel::class)
|
||||||
|
|
||||||
override fun getBinding(inflater: LayoutInflater, container: ViewGroup?): BottomSheetVerificationChildFragmentBinding {
|
override fun getBinding(inflater: LayoutInflater, container: ViewGroup?): BottomSheetVerificationChildFragmentBinding {
|
||||||
|
@ -58,17 +75,22 @@ class UserVerificationFragment : VectorBaseFragment<BottomSheetVerificationChild
|
||||||
|
|
||||||
override fun onDestroyView() {
|
override fun onDestroyView() {
|
||||||
views.bottomSheetVerificationRecyclerView.cleanup()
|
views.bottomSheetVerificationRecyclerView.cleanup()
|
||||||
|
controller.removeModelBuildListener(modelBuildListener)
|
||||||
controller.listener = null
|
controller.listener = null
|
||||||
super.onDestroyView()
|
super.onDestroyView()
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun setupRecyclerView() {
|
private fun setupRecyclerView() {
|
||||||
views.bottomSheetVerificationRecyclerView.configureWith(controller, hasFixedSize = false, disableItemAnimation = true)
|
views.bottomSheetVerificationRecyclerView.configureWith(controller, hasFixedSize = false, disableItemAnimation = true)
|
||||||
|
controller.addModelBuildListener(modelBuildListener)
|
||||||
controller.listener = this
|
controller.listener = this
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun invalidate() = withState(viewModel) { state ->
|
override fun invalidate() = withState(viewModel) { state ->
|
||||||
// Timber.w("VALR: invalidate with State: ${state.pendingRequest}")
|
// Timber.w("VALR: invalidate with State: ${state.pendingRequest}")
|
||||||
|
if (state.isNewScreen()) {
|
||||||
|
requestAccessibilityFocus = true
|
||||||
|
}
|
||||||
controller.update(state)
|
controller.update(state)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -142,10 +164,40 @@ class UserVerificationFragment : VectorBaseFragment<BottomSheetVerificationChild
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onUserDeniesQrCodeScanned() {
|
override fun onUserDeniesQrCodeScanned() {
|
||||||
viewModel.handle(VerificationAction.OtherUserDidNotScanned)
|
viewModel.handle(VerificationAction.OtherUserDidNotScanned)
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onUserConfirmsQrCodeScanned() {
|
override fun onUserConfirmsQrCodeScanned() {
|
||||||
viewModel.handle(VerificationAction.OtherUserScannedSuccessfully)
|
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.DrawableImageViewTarget
|
||||||
import com.bumptech.glide.request.target.Target
|
import com.bumptech.glide.request.target.Target
|
||||||
import com.bumptech.glide.signature.ObjectKey
|
import com.bumptech.glide.signature.ObjectKey
|
||||||
|
import im.vector.app.R
|
||||||
import im.vector.app.core.contacts.MappedContact
|
import im.vector.app.core.contacts.MappedContact
|
||||||
import im.vector.app.core.di.ActiveSessionHolder
|
import im.vector.app.core.di.ActiveSessionHolder
|
||||||
import im.vector.app.core.glide.AvatarPlaceholder
|
import im.vector.app.core.glide.AvatarPlaceholder
|
||||||
import im.vector.app.core.glide.GlideApp
|
import im.vector.app.core.glide.GlideApp
|
||||||
import im.vector.app.core.glide.GlideRequest
|
import im.vector.app.core.glide.GlideRequest
|
||||||
import im.vector.app.core.glide.GlideRequests
|
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.core.utils.DimensionConverter
|
||||||
|
import im.vector.app.features.displayname.getBestName
|
||||||
import im.vector.app.features.home.room.detail.timeline.helper.MatrixItemColorProvider
|
import im.vector.app.features.home.room.detail.timeline.helper.MatrixItemColorProvider
|
||||||
import jp.wasabeef.glide.transformations.BlurTransformation
|
import jp.wasabeef.glide.transformations.BlurTransformation
|
||||||
import jp.wasabeef.glide.transformations.ColorFilterTransformation
|
import jp.wasabeef.glide.transformations.ColorFilterTransformation
|
||||||
|
@ -58,7 +61,8 @@ import javax.inject.Inject
|
||||||
class AvatarRenderer @Inject constructor(
|
class AvatarRenderer @Inject constructor(
|
||||||
private val activeSessionHolder: ActiveSessionHolder,
|
private val activeSessionHolder: ActiveSessionHolder,
|
||||||
private val matrixItemColorProvider: MatrixItemColorProvider,
|
private val matrixItemColorProvider: MatrixItemColorProvider,
|
||||||
private val dimensionConverter: DimensionConverter
|
private val dimensionConverter: DimensionConverter,
|
||||||
|
private val stringProvider: StringProvider,
|
||||||
) {
|
) {
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
|
@ -67,6 +71,7 @@ class AvatarRenderer @Inject constructor(
|
||||||
|
|
||||||
@UiThread
|
@UiThread
|
||||||
fun render(matrixItem: MatrixItem, imageView: ImageView) {
|
fun render(matrixItem: MatrixItem, imageView: ImageView) {
|
||||||
|
imageView.setContentDescription(matrixItem)
|
||||||
render(
|
render(
|
||||||
GlideApp.with(imageView),
|
GlideApp.with(imageView),
|
||||||
matrixItem,
|
matrixItem,
|
||||||
|
@ -100,6 +105,7 @@ class AvatarRenderer @Inject constructor(
|
||||||
|
|
||||||
@UiThread
|
@UiThread
|
||||||
fun render(matrixItem: MatrixItem, imageView: ImageView, glideRequests: GlideRequests) {
|
fun render(matrixItem: MatrixItem, imageView: ImageView, glideRequests: GlideRequests) {
|
||||||
|
imageView.setContentDescription(matrixItem)
|
||||||
render(
|
render(
|
||||||
glideRequests,
|
glideRequests,
|
||||||
matrixItem,
|
matrixItem,
|
||||||
|
@ -109,6 +115,7 @@ class AvatarRenderer @Inject constructor(
|
||||||
|
|
||||||
@UiThread
|
@UiThread
|
||||||
fun render(matrixItem: MatrixItem, localUri: Uri?, imageView: ImageView) {
|
fun render(matrixItem: MatrixItem, localUri: Uri?, imageView: ImageView) {
|
||||||
|
imageView.setContentDescription(matrixItem)
|
||||||
val placeholder = getPlaceholderDrawable(matrixItem)
|
val placeholder = getPlaceholderDrawable(matrixItem)
|
||||||
GlideApp.with(imageView)
|
GlideApp.with(imageView)
|
||||||
.load(localUri?.let { File(localUri.path!!) })
|
.load(localUri?.let { File(localUri.path!!) })
|
||||||
|
@ -295,4 +302,28 @@ class AvatarRenderer @Inject constructor(
|
||||||
return activeSessionHolder.getSafeActiveSession()?.contentUrlResolver()
|
return activeSessionHolder.getSafeActiveSession()?.contentUrlResolver()
|
||||||
?.resolveThumbnail(avatarUrl, THUMBNAIL_SIZE, THUMBNAIL_SIZE, ContentUrlResolver.ThumbnailMethod.SCALE)
|
?.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.os.Looper
|
||||||
import android.view.View
|
import android.view.View
|
||||||
import android.view.WindowInsetsController.APPEARANCE_LIGHT_STATUS_BARS
|
import android.view.WindowInsetsController.APPEARANCE_LIGHT_STATUS_BARS
|
||||||
|
import androidx.core.view.ViewCompat
|
||||||
import com.tapadoo.alerter.Alerter
|
import com.tapadoo.alerter.Alerter
|
||||||
import im.vector.app.R
|
import im.vector.app.R
|
||||||
|
import im.vector.app.core.extensions.giveAccessibilityFocus
|
||||||
import im.vector.app.core.platform.VectorBaseActivity
|
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.core.utils.isAnimationEnabled
|
||||||
import im.vector.app.features.MainActivity
|
import im.vector.app.features.MainActivity
|
||||||
import im.vector.app.features.analytics.ui.consent.AnalyticsOptInActivity
|
import im.vector.app.features.analytics.ui.consent.AnalyticsOptInActivity
|
||||||
|
@ -46,6 +49,7 @@ import javax.inject.Singleton
|
||||||
@Singleton
|
@Singleton
|
||||||
class PopupAlertManager @Inject constructor(
|
class PopupAlertManager @Inject constructor(
|
||||||
private val clock: Clock,
|
private val clock: Clock,
|
||||||
|
private val stringProvider: StringProvider,
|
||||||
) {
|
) {
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
|
@ -282,6 +286,9 @@ class PopupAlertManager @Inject constructor(
|
||||||
}
|
}
|
||||||
currentIsDismissed()
|
currentIsDismissed()
|
||||||
}
|
}
|
||||||
|
.setOnShowListener {
|
||||||
|
handleAccessibility(activity, animate)
|
||||||
|
}
|
||||||
.enableSwipeToDismiss()
|
.enableSwipeToDismiss()
|
||||||
.enableInfiniteDuration(true)
|
.enableInfiniteDuration(true)
|
||||||
.apply {
|
.apply {
|
||||||
|
@ -297,6 +304,29 @@ class PopupAlertManager @Inject constructor(
|
||||||
.show()
|
.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() {
|
private fun currentIsDismissed() {
|
||||||
// current alert has been hidden
|
// current alert has been hidden
|
||||||
if (currentAlerter?.isLight == false) {
|
if (currentAlerter?.isLight == false) {
|
||||||
|
|
|
@ -43,4 +43,4 @@
|
||||||
|
|
||||||
</androidx.constraintlayout.widget.ConstraintLayout>
|
</androidx.constraintlayout.widget.ConstraintLayout>
|
||||||
|
|
||||||
</androidx.coordinatorlayout.widget.CoordinatorLayout>
|
</androidx.coordinatorlayout.widget.CoordinatorLayout>
|
||||||
|
|
|
@ -49,15 +49,14 @@
|
||||||
app:layout_constraintTop_toBottomOf="@id/syncStateView">
|
app:layout_constraintTop_toBottomOf="@id/syncStateView">
|
||||||
|
|
||||||
<com.google.android.material.appbar.CollapsingToolbarLayout
|
<com.google.android.material.appbar.CollapsingToolbarLayout
|
||||||
|
style="?attr/collapsingToolbarLayoutMediumStyle"
|
||||||
android:id="@+id/collapsing_toolbar"
|
android:id="@+id/collapsing_toolbar"
|
||||||
style="@style/Widget.Vector.Material3.CollapsingToolbar.Medium"
|
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="?attr/collapsingToolbarLayoutMediumSize"
|
android:layout_height="?attr/collapsingToolbarLayoutMediumSize"
|
||||||
app:layout_scrollFlags="scroll|exitUntilCollapsed">
|
app:layout_scrollFlags="scroll|exitUntilCollapsed">
|
||||||
|
|
||||||
<com.google.android.material.appbar.MaterialToolbar
|
<com.google.android.material.appbar.MaterialToolbar
|
||||||
android:id="@+id/toolbar"
|
android:id="@+id/toolbar"
|
||||||
style="@style/Widget.Vector.Material3.Toolbar"
|
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="?attr/actionBarSize"
|
android:layout_height="?attr/actionBarSize"
|
||||||
android:elevation="0dp"
|
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