Skip to content

Commit 7a65a51

Browse files
committed
Attempt to give accessibility focus to the first item of the RecyclerView when the recycler view is updated (screen change), to improve screen reader behavior.
1 parent 20fedc8 commit 7a65a51

File tree

2 files changed

+113
-2
lines changed

2 files changed

+113
-2
lines changed

vector/src/main/java/im/vector/app/features/crypto/verification/self/SelfVerificationFragment.kt

Lines changed: 60 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,12 +21,18 @@ import android.os.Bundle
2121
import android.view.LayoutInflater
2222
import android.view.View
2323
import android.view.ViewGroup
24+
import com.airbnb.epoxy.OnModelBuildFinishedListener
25+
import com.airbnb.mvrx.Fail
26+
import com.airbnb.mvrx.Loading
27+
import com.airbnb.mvrx.Success
28+
import com.airbnb.mvrx.Uninitialized
2429
import com.airbnb.mvrx.parentFragmentViewModel
2530
import com.airbnb.mvrx.withState
2631
import dagger.hilt.android.AndroidEntryPoint
2732
import im.vector.app.R
2833
import im.vector.app.core.extensions.cleanup
2934
import im.vector.app.core.extensions.configureWith
35+
import im.vector.app.core.extensions.giveAccessibilityFocus
3036
import im.vector.app.core.extensions.registerStartForActivityResult
3137
import im.vector.app.core.platform.VectorBaseFragment
3238
import im.vector.app.core.utils.PERMISSIONS_FOR_TAKING_PHOTO
@@ -36,15 +42,26 @@ import im.vector.app.core.utils.registerForPermissionsResult
3642
import im.vector.app.databinding.BottomSheetVerificationChildFragmentBinding
3743
import im.vector.app.features.crypto.verification.VerificationAction
3844
import im.vector.app.features.qrcode.QrCodeScannerActivity
45+
import org.matrix.android.sdk.api.session.crypto.verification.EVerificationState
3946
import timber.log.Timber
4047
import javax.inject.Inject
4148

4249
@AndroidEntryPoint
43-
class SelfVerificationFragment : VectorBaseFragment<BottomSheetVerificationChildFragmentBinding>(),
50+
class SelfVerificationFragment : VectorBaseFragment<BottomSheetVerificationChildFragmentBinding>(),
4451
SelfVerificationController.InteractionListener {
4552

4653
@Inject lateinit var controller: SelfVerificationController
4754

55+
private var requestAccessibilityFocus: Boolean = false
56+
private val modelBuildListener: OnModelBuildFinishedListener = OnModelBuildFinishedListener {
57+
if (requestAccessibilityFocus) {
58+
// Do not use giveAccessibilityFocusOnce() here.
59+
views.bottomSheetVerificationRecyclerView.layoutManager?.findViewByPosition(0)?.giveAccessibilityFocus()
60+
requestAccessibilityFocus = false
61+
// Note: it does not work when the recycler view is displayed for the first time, because findViewByPosition(0) is null
62+
}
63+
}
64+
4865
private val viewModel by parentFragmentViewModel(SelfVerificationViewModel::class)
4966

5067
override fun getBinding(inflater: LayoutInflater, container: ViewGroup?): BottomSheetVerificationChildFragmentBinding {
@@ -58,17 +75,22 @@ class SelfVerificationFragment : VectorBaseFragment<BottomSheetVerificationChil
5875

5976
override fun onDestroyView() {
6077
views.bottomSheetVerificationRecyclerView.cleanup()
78+
controller.removeModelBuildListener(modelBuildListener)
6179
controller.listener = null
6280
super.onDestroyView()
6381
}
6482

6583
private fun setupRecyclerView() {
6684
views.bottomSheetVerificationRecyclerView.configureWith(controller, hasFixedSize = false, disableItemAnimation = true)
85+
controller.addModelBuildListener(modelBuildListener)
6786
controller.listener = this
6887
}
6988

7089
override fun invalidate() = withState(viewModel) { state ->
7190
// Timber.w("VALR: invalidate with State: ${state.pendingRequest}")
91+
if (state.isNewScreen()) {
92+
requestAccessibilityFocus = true
93+
}
7294
controller.update(state)
7395
}
7496

@@ -176,4 +198,41 @@ class SelfVerificationFragment : VectorBaseFragment<BottomSheetVerificationChil
176198
override fun declineRequest() {
177199
viewModel.handle(VerificationAction.CancelPendingVerification)
178200
}
201+
202+
private var currentScreenIndex = -1
203+
204+
private fun SelfVerificationViewState.isNewScreen(): Boolean {
205+
val newIndex = toScreenIndex()
206+
if (currentScreenIndex == newIndex) {
207+
return false
208+
}
209+
currentScreenIndex = newIndex
210+
return true
211+
}
212+
213+
private fun SelfVerificationViewState.toScreenIndex(): Int {
214+
return if (activeAction !is UserAction.None) {
215+
when (activeAction) {
216+
UserAction.ConfirmCancel -> 30
217+
UserAction.None -> 31
218+
}
219+
} else {
220+
when (pendingRequest) {
221+
is Fail -> 0
222+
is Loading -> 1
223+
is Success -> when (pendingRequest.invoke().state) {
224+
EVerificationState.WaitingForReady -> 10
225+
EVerificationState.Requested -> 11
226+
EVerificationState.Ready -> 12
227+
EVerificationState.Started -> 13
228+
EVerificationState.WeStarted -> 14
229+
EVerificationState.WaitingForDone -> 15
230+
EVerificationState.Done -> 16
231+
EVerificationState.Cancelled -> 17
232+
EVerificationState.HandledByOtherSession -> 18
233+
}
234+
Uninitialized -> 2
235+
}
236+
}
237+
}
179238
}

vector/src/main/java/im/vector/app/features/crypto/verification/user/UserVerificationFragment.kt

Lines changed: 53 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,12 +21,18 @@ import android.os.Bundle
2121
import android.view.LayoutInflater
2222
import android.view.View
2323
import android.view.ViewGroup
24+
import com.airbnb.epoxy.OnModelBuildFinishedListener
25+
import com.airbnb.mvrx.Fail
26+
import com.airbnb.mvrx.Loading
27+
import com.airbnb.mvrx.Success
28+
import com.airbnb.mvrx.Uninitialized
2429
import com.airbnb.mvrx.parentFragmentViewModel
2530
import com.airbnb.mvrx.withState
2631
import dagger.hilt.android.AndroidEntryPoint
2732
import im.vector.app.R
2833
import im.vector.app.core.extensions.cleanup
2934
import im.vector.app.core.extensions.configureWith
35+
import im.vector.app.core.extensions.giveAccessibilityFocus
3036
import im.vector.app.core.extensions.registerStartForActivityResult
3137
import im.vector.app.core.platform.VectorBaseFragment
3238
import im.vector.app.core.utils.PERMISSIONS_FOR_TAKING_PHOTO
@@ -36,6 +42,7 @@ import im.vector.app.core.utils.registerForPermissionsResult
3642
import im.vector.app.databinding.BottomSheetVerificationChildFragmentBinding
3743
import im.vector.app.features.crypto.verification.VerificationAction
3844
import im.vector.app.features.qrcode.QrCodeScannerActivity
45+
import org.matrix.android.sdk.api.session.crypto.verification.EVerificationState
3946
import timber.log.Timber
4047
import javax.inject.Inject
4148

@@ -45,6 +52,16 @@ class UserVerificationFragment : VectorBaseFragment<BottomSheetVerificationChild
4552

4653
@Inject lateinit var controller: UserVerificationController
4754

55+
private var requestAccessibilityFocus: Boolean = false
56+
private val modelBuildListener: OnModelBuildFinishedListener = OnModelBuildFinishedListener {
57+
if (requestAccessibilityFocus) {
58+
// Do not use giveAccessibilityFocusOnce() here.
59+
views.bottomSheetVerificationRecyclerView.layoutManager?.findViewByPosition(0)?.giveAccessibilityFocus()
60+
requestAccessibilityFocus = false
61+
// Note: it does not work when the recycler view is displayed for the first time, because findViewByPosition(0) is null
62+
}
63+
}
64+
4865
private val viewModel by parentFragmentViewModel(UserVerificationViewModel::class)
4966

5067
override fun getBinding(inflater: LayoutInflater, container: ViewGroup?): BottomSheetVerificationChildFragmentBinding {
@@ -58,17 +75,22 @@ class UserVerificationFragment : VectorBaseFragment<BottomSheetVerificationChild
5875

5976
override fun onDestroyView() {
6077
views.bottomSheetVerificationRecyclerView.cleanup()
78+
controller.removeModelBuildListener(modelBuildListener)
6179
controller.listener = null
6280
super.onDestroyView()
6381
}
6482

6583
private fun setupRecyclerView() {
6684
views.bottomSheetVerificationRecyclerView.configureWith(controller, hasFixedSize = false, disableItemAnimation = true)
85+
controller.addModelBuildListener(modelBuildListener)
6786
controller.listener = this
6887
}
6988

7089
override fun invalidate() = withState(viewModel) { state ->
7190
// Timber.w("VALR: invalidate with State: ${state.pendingRequest}")
91+
if (state.isNewScreen()) {
92+
requestAccessibilityFocus = true
93+
}
7294
controller.update(state)
7395
}
7496

@@ -142,10 +164,40 @@ class UserVerificationFragment : VectorBaseFragment<BottomSheetVerificationChild
142164
}
143165

144166
override fun onUserDeniesQrCodeScanned() {
145-
viewModel.handle(VerificationAction.OtherUserDidNotScanned)
167+
viewModel.handle(VerificationAction.OtherUserDidNotScanned)
146168
}
147169

148170
override fun onUserConfirmsQrCodeScanned() {
149171
viewModel.handle(VerificationAction.OtherUserScannedSuccessfully)
150172
}
173+
174+
private var currentScreenIndex = -1
175+
176+
private fun UserVerificationViewState.isNewScreen(): Boolean {
177+
val newIndex = toScreenIndex()
178+
if (currentScreenIndex == newIndex) {
179+
return false
180+
}
181+
currentScreenIndex = newIndex
182+
return true
183+
}
184+
185+
private fun UserVerificationViewState.toScreenIndex(): Int {
186+
return when (pendingRequest) {
187+
is Fail -> 0
188+
is Loading -> 1
189+
is Success -> when (pendingRequest.invoke().state) {
190+
EVerificationState.WaitingForReady -> 10
191+
EVerificationState.Requested -> 11
192+
EVerificationState.Ready -> 12
193+
EVerificationState.Started -> 13
194+
EVerificationState.WeStarted -> 14
195+
EVerificationState.WaitingForDone -> 15
196+
EVerificationState.Done -> 16
197+
EVerificationState.Cancelled -> 17
198+
EVerificationState.HandledByOtherSession -> 18
199+
}
200+
Uninitialized -> 2
201+
}
202+
}
151203
}

0 commit comments

Comments
 (0)