From bc83c9b1ae401c02709af512ff9310cdb10eae8d Mon Sep 17 00:00:00 2001 From: Tymoteusz Boba Date: Wed, 20 Sep 2023 09:56:55 +0200 Subject: [PATCH] feat(Android): Add focused states on page transitions (#1894) ## Description Currently, on Android TV devices when user is switching between views the last focused child is lost. This seems like an incorrect behavior - In #1706 people are reporting that on Android TV the last focused element should be stored. This PR introduces the change to remember last focused element on previous screen. For now I've added a statement that the last element should be stored only for Android TV, but we should discuss if we also should store those elements for classic Android - I've noticed that it's possible to focus an element on a normal screen, but I've achieved it only by switching between elements with arrows on keyboard (so is it possible to focus an element just by touching a screen? Maybe TalkBack is also focusing the elements?).
Focusing elements on Android https://github.com/software-mansion/react-native-screens/assets/23281839/4f62ae66-f411-4c45-b678-1e47c3fa4ff2
Fixes #1706. ## Changes - Added `isTelevision` method that checks if user is currently using Android TV. - Added `lastFocusedChild` variable with an implementation for finding last focused elements. ## Screenshots / GIFs Here you can add screenshots / GIFs documenting your change. You can add before / after section if you're changing some behavior. ### Before https://github.com/software-mansion/react-native-screens/assets/23281839/0e3e0ad4-a202-43c3-b688-6c66f76a43f8 ### After https://github.com/software-mansion/react-native-screens/assets/23281839/998dfa5c-6def-47f2-b129-425a2257319a ## Test code and steps to reproduce You can test those changes on `Example` application by switching between examples with arrows on the keyboard. ## Checklist - [ ] Ensured that CI passes --- .../rnscreens/ScreenStackFragment.kt | 25 +++++++++++++++++++ .../swmansion/rnscreens/utils/DeviceUtils.kt | 12 +++++++++ 2 files changed, 37 insertions(+) create mode 100644 android/src/main/java/com/swmansion/rnscreens/utils/DeviceUtils.kt diff --git a/android/src/main/java/com/swmansion/rnscreens/ScreenStackFragment.kt b/android/src/main/java/com/swmansion/rnscreens/ScreenStackFragment.kt index 725bcf879a..358e200fed 100644 --- a/android/src/main/java/com/swmansion/rnscreens/ScreenStackFragment.kt +++ b/android/src/main/java/com/swmansion/rnscreens/ScreenStackFragment.kt @@ -19,6 +19,7 @@ import androidx.coordinatorlayout.widget.CoordinatorLayout import com.facebook.react.uimanager.PixelUtil import com.google.android.material.appbar.AppBarLayout import com.google.android.material.appbar.AppBarLayout.ScrollingViewBehavior +import com.swmansion.rnscreens.utils.DeviceUtils class ScreenStackFragment : ScreenFragment, ScreenStackFragmentWrapper { private var mAppBarLayout: AppBarLayout? = null @@ -26,6 +27,8 @@ class ScreenStackFragment : ScreenFragment, ScreenStackFragmentWrapper { private var mShadowHidden = false private var mIsTranslucent = false + private var mLastFocusedChild: View? = null + var searchView: CustomSearchView? = null var onSearchViewCreate: ((searchView: CustomSearchView) -> Unit)? = null @@ -89,6 +92,11 @@ class ScreenStackFragment : ScreenFragment, ScreenStackFragmentWrapper { } } + override fun onStart() { + mLastFocusedChild?.requestFocus() + super.onStart() + } + override fun onCreateView( inflater: LayoutInflater, container: ViewGroup?, @@ -123,6 +131,13 @@ class ScreenStackFragment : ScreenFragment, ScreenStackFragmentWrapper { return view } + override fun onStop() { + if (DeviceUtils.isPlatformAndroidTV(context)) + mLastFocusedChild = findLastFocusedChild() + + super.onStop() + } + override fun onPrepareOptionsMenu(menu: Menu) { updateToolbarMenu(menu) return super.onPrepareOptionsMenu(menu) @@ -163,6 +178,16 @@ class ScreenStackFragment : ScreenFragment, ScreenStackFragmentWrapper { } } + private fun findLastFocusedChild(): View? { + var view: View? = screen + while (view != null) { + if (view.isFocused) return view + view = if (view is ViewGroup) view.focusedChild else null + } + + return null + } + override fun canNavigateBack(): Boolean { val container: ScreenContainer? = screen.container check(container is ScreenStack) { "ScreenStackFragment added into a non-stack container" } diff --git a/android/src/main/java/com/swmansion/rnscreens/utils/DeviceUtils.kt b/android/src/main/java/com/swmansion/rnscreens/utils/DeviceUtils.kt new file mode 100644 index 0000000000..57ddaaa5d3 --- /dev/null +++ b/android/src/main/java/com/swmansion/rnscreens/utils/DeviceUtils.kt @@ -0,0 +1,12 @@ +package com.swmansion.rnscreens.utils + +import android.content.Context +import android.content.pm.PackageManager + +object DeviceUtils { + + fun isPlatformAndroidTV(context: Context?): Boolean { + return context?.packageManager?.hasSystemFeature(PackageManager.FEATURE_LEANBACK) == true + } + +}