react-native-keyboard-controller 1.16.1 → 1.16.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/android/build.gradle +6 -0
- package/android/react-native-helpers.gradle +5 -1
- package/android/src/base/java/com/reactnativekeyboardcontroller/KeyboardControllerPackage.kt +61 -0
- package/android/src/fabric/java/com/reactnativekeyboardcontroller/KeyboardControllerViewManager.kt +5 -0
- package/android/src/main/java/com/reactnativekeyboardcontroller/managers/KeyboardControllerViewManagerImpl.kt +4 -0
- package/android/src/main/java/com/reactnativekeyboardcontroller/views/EdgeToEdgeReactViewGroup.kt +13 -11
- package/android/src/paper/java/com/reactnativekeyboardcontroller/KeyboardControllerViewManager.kt +6 -0
- package/ios/KeyboardControllerModuleImpl.swift +3 -2
- package/ios/extensions/UIResponder.swift +2 -21
- package/ios/extensions/UIView.swift +12 -0
- package/ios/observers/FocusedInputObserver.swift +1 -24
- package/package.json +14 -6
- package/android/src/test/java/com/reactnativekeyboardcontroller/traversal/FocusedInputHolderTest.kt +0 -45
- package/android/src/test/java/com/reactnativekeyboardcontroller/traversal/ViewHierarchyNavigatorTest.kt +0 -198
- /package/android/src/{main → turbo}/java/com/reactnativekeyboardcontroller/KeyboardControllerPackage.kt +0 -0
package/android/build.gradle
CHANGED
|
@@ -39,4 +39,8 @@ project.ext.resolveReactNativeDirectory = { ->
|
|
|
39
39
|
|
|
40
40
|
project.ext.shouldConsumeReactNativeFromMavenCentral = { ->
|
|
41
41
|
return REACT_NATIVE_MINOR_VERSION >= 71
|
|
42
|
-
}
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
project.ext.shouldUseBaseReactPackage = { ->
|
|
45
|
+
return REACT_NATIVE_MINOR_VERSION >= 74
|
|
46
|
+
}
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
package com.reactnativekeyboardcontroller
|
|
2
|
+
|
|
3
|
+
import com.facebook.react.BaseReactPackage
|
|
4
|
+
import com.facebook.react.bridge.NativeModule
|
|
5
|
+
import com.facebook.react.bridge.ReactApplicationContext
|
|
6
|
+
import com.facebook.react.module.model.ReactModuleInfo
|
|
7
|
+
import com.facebook.react.module.model.ReactModuleInfoProvider
|
|
8
|
+
import com.facebook.react.uimanager.ViewManager
|
|
9
|
+
import com.reactnativekeyboardcontroller.modules.KeyboardControllerModuleImpl
|
|
10
|
+
import com.reactnativekeyboardcontroller.modules.StatusBarManagerCompatModuleImpl
|
|
11
|
+
|
|
12
|
+
class KeyboardControllerPackage : BaseReactPackage() {
|
|
13
|
+
override fun getModule(
|
|
14
|
+
name: String,
|
|
15
|
+
reactContext: ReactApplicationContext,
|
|
16
|
+
): NativeModule? =
|
|
17
|
+
when (name) {
|
|
18
|
+
KeyboardControllerModuleImpl.NAME -> {
|
|
19
|
+
KeyboardControllerModule(reactContext)
|
|
20
|
+
}
|
|
21
|
+
StatusBarManagerCompatModuleImpl.NAME -> {
|
|
22
|
+
StatusBarManagerCompatModule(reactContext)
|
|
23
|
+
}
|
|
24
|
+
else -> {
|
|
25
|
+
null
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
override fun getReactModuleInfoProvider(): ReactModuleInfoProvider =
|
|
30
|
+
ReactModuleInfoProvider {
|
|
31
|
+
val moduleInfos: MutableMap<String, ReactModuleInfo> = HashMap()
|
|
32
|
+
val isTurboModule = BuildConfig.IS_NEW_ARCHITECTURE_ENABLED
|
|
33
|
+
|
|
34
|
+
moduleInfos[KeyboardControllerModuleImpl.NAME] =
|
|
35
|
+
ReactModuleInfo(
|
|
36
|
+
KeyboardControllerModuleImpl.NAME,
|
|
37
|
+
KeyboardControllerModuleImpl.NAME,
|
|
38
|
+
false, // canOverrideExistingModule
|
|
39
|
+
false, // needsEagerInit
|
|
40
|
+
false, // isCxxModule
|
|
41
|
+
isTurboModule, // isTurboModule
|
|
42
|
+
)
|
|
43
|
+
moduleInfos[StatusBarManagerCompatModuleImpl.NAME] =
|
|
44
|
+
ReactModuleInfo(
|
|
45
|
+
StatusBarManagerCompatModuleImpl.NAME,
|
|
46
|
+
StatusBarManagerCompatModuleImpl.NAME,
|
|
47
|
+
false, // canOverrideExistingModule
|
|
48
|
+
false, // needsEagerInit
|
|
49
|
+
false, // isCxxModule
|
|
50
|
+
isTurboModule, // isTurboModule
|
|
51
|
+
)
|
|
52
|
+
moduleInfos
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
override fun createViewManagers(reactContext: ReactApplicationContext): List<ViewManager<*, *>> =
|
|
56
|
+
listOf(
|
|
57
|
+
KeyboardControllerViewManager(reactContext),
|
|
58
|
+
KeyboardGestureAreaViewManager(reactContext),
|
|
59
|
+
OverKeyboardViewManager(reactContext),
|
|
60
|
+
)
|
|
61
|
+
}
|
package/android/src/fabric/java/com/reactnativekeyboardcontroller/KeyboardControllerViewManager.kt
CHANGED
|
@@ -24,6 +24,11 @@ class KeyboardControllerViewManager(
|
|
|
24
24
|
|
|
25
25
|
override fun createViewInstance(context: ThemedReactContext): ReactViewGroup = manager.createViewInstance(context)
|
|
26
26
|
|
|
27
|
+
override fun onAfterUpdateTransaction(view: ReactViewGroup) {
|
|
28
|
+
super.onAfterUpdateTransaction(view)
|
|
29
|
+
manager.setEdgeToEdge(view as EdgeToEdgeReactViewGroup)
|
|
30
|
+
}
|
|
31
|
+
|
|
27
32
|
@ReactProp(name = "statusBarTranslucent")
|
|
28
33
|
override fun setStatusBarTranslucent(
|
|
29
34
|
view: ReactViewGroup,
|
|
@@ -44,6 +44,10 @@ class KeyboardControllerViewManagerImpl(
|
|
|
44
44
|
view.setPreserveEdgeToEdge(isPreservingEdgeToEdge)
|
|
45
45
|
}
|
|
46
46
|
|
|
47
|
+
fun setEdgeToEdge(view: EdgeToEdgeReactViewGroup) {
|
|
48
|
+
view.setEdgeToEdge()
|
|
49
|
+
}
|
|
50
|
+
|
|
47
51
|
fun getExportedCustomDirectEventTypeConstants(): MutableMap<String, Any> {
|
|
48
52
|
val map: MutableMap<String, Any> =
|
|
49
53
|
MapBuilder.of(
|
package/android/src/main/java/com/reactnativekeyboardcontroller/views/EdgeToEdgeReactViewGroup.kt
CHANGED
|
@@ -33,6 +33,7 @@ class EdgeToEdgeReactViewGroup(
|
|
|
33
33
|
private var isNavigationBarTranslucent = false
|
|
34
34
|
private var isPreservingEdgeToEdge = false
|
|
35
35
|
private var active = false
|
|
36
|
+
private var isEdgeToEdge = false
|
|
36
37
|
|
|
37
38
|
// internal class members
|
|
38
39
|
private var eventView: ReactViewGroup? = null
|
|
@@ -124,12 +125,18 @@ class EdgeToEdgeReactViewGroup(
|
|
|
124
125
|
}
|
|
125
126
|
}
|
|
126
127
|
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
128
|
+
fun setEdgeToEdge() {
|
|
129
|
+
val nextValue = active || isPreservingEdgeToEdge
|
|
130
|
+
|
|
131
|
+
if (isEdgeToEdge != nextValue) {
|
|
132
|
+
isEdgeToEdge = nextValue
|
|
133
|
+
|
|
134
|
+
reactContext.currentActivity?.let {
|
|
135
|
+
WindowCompat.setDecorFitsSystemWindows(
|
|
136
|
+
it.window,
|
|
137
|
+
!isEdgeToEdge,
|
|
138
|
+
)
|
|
139
|
+
}
|
|
133
140
|
}
|
|
134
141
|
}
|
|
135
142
|
|
|
@@ -182,17 +189,12 @@ class EdgeToEdgeReactViewGroup(
|
|
|
182
189
|
|
|
183
190
|
// region State managers
|
|
184
191
|
private fun enable() {
|
|
185
|
-
this.goToEdgeToEdge(true)
|
|
186
192
|
this.setupWindowInsets()
|
|
187
193
|
this.setupKeyboardCallbacks()
|
|
188
194
|
modalAttachedWatcher.enable()
|
|
189
195
|
}
|
|
190
196
|
|
|
191
197
|
private fun disable() {
|
|
192
|
-
if (!isPreservingEdgeToEdge) {
|
|
193
|
-
this.goToEdgeToEdge(false)
|
|
194
|
-
}
|
|
195
|
-
|
|
196
198
|
this.setupWindowInsets()
|
|
197
199
|
this.removeKeyboardCallbacks()
|
|
198
200
|
modalAttachedWatcher.disable()
|
package/android/src/paper/java/com/reactnativekeyboardcontroller/KeyboardControllerViewManager.kt
CHANGED
|
@@ -3,6 +3,7 @@ package com.reactnativekeyboardcontroller
|
|
|
3
3
|
import com.facebook.react.bridge.ReactApplicationContext
|
|
4
4
|
import com.facebook.react.uimanager.ThemedReactContext
|
|
5
5
|
import com.facebook.react.uimanager.annotations.ReactProp
|
|
6
|
+
import com.facebook.react.views.view.ReactViewGroup
|
|
6
7
|
import com.facebook.react.views.view.ReactViewManager
|
|
7
8
|
import com.reactnativekeyboardcontroller.managers.KeyboardControllerViewManagerImpl
|
|
8
9
|
import com.reactnativekeyboardcontroller.views.EdgeToEdgeReactViewGroup
|
|
@@ -17,6 +18,11 @@ class KeyboardControllerViewManager(
|
|
|
17
18
|
override fun createViewInstance(reactContext: ThemedReactContext): EdgeToEdgeReactViewGroup =
|
|
18
19
|
manager.createViewInstance(reactContext)
|
|
19
20
|
|
|
21
|
+
override fun onAfterUpdateTransaction(view: ReactViewGroup) {
|
|
22
|
+
super.onAfterUpdateTransaction(view)
|
|
23
|
+
manager.setEdgeToEdge(view as EdgeToEdgeReactViewGroup)
|
|
24
|
+
}
|
|
25
|
+
|
|
20
26
|
@ReactProp(name = "enabled")
|
|
21
27
|
fun setEnabled(
|
|
22
28
|
view: EdgeToEdgeReactViewGroup,
|
|
@@ -14,9 +14,10 @@ public class KeyboardControllerModuleImpl: NSObject {
|
|
|
14
14
|
|
|
15
15
|
@objc
|
|
16
16
|
public static func dismiss(_ keepFocus: Bool) {
|
|
17
|
-
|
|
17
|
+
let responder = UIResponder.current
|
|
18
18
|
|
|
19
19
|
if keepFocus {
|
|
20
|
+
guard let input = responder as? TextInput else { return }
|
|
20
21
|
let tapGesture = UITapGestureRecognizer(target: self, action: #selector(onTextInputTapped(_:)))
|
|
21
22
|
tapGesture.name = keyboardRevealGestureName
|
|
22
23
|
input.addGestureRecognizer(tapGesture)
|
|
@@ -31,7 +32,7 @@ public class KeyboardControllerModuleImpl: NSObject {
|
|
|
31
32
|
object: input
|
|
32
33
|
)
|
|
33
34
|
} else {
|
|
34
|
-
|
|
35
|
+
responder?.resignFirstResponder()
|
|
35
36
|
}
|
|
36
37
|
}
|
|
37
38
|
|
|
@@ -11,28 +11,9 @@ import UIKit
|
|
|
11
11
|
|
|
12
12
|
@objc
|
|
13
13
|
public extension UIResponder {
|
|
14
|
-
private weak static var _currentFirstResponder: UIResponder?
|
|
15
|
-
|
|
16
14
|
static var current: UIResponder? {
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
return UIResponder._currentFirstResponder
|
|
20
|
-
}
|
|
21
|
-
|
|
22
|
-
internal func findFirstResponder(sender _: AnyObject) {
|
|
23
|
-
let type = String(describing: type(of: self))
|
|
24
|
-
// handle `contextMenuHidden` prop - in this case the parent is considered as a first responder
|
|
25
|
-
// (but actually its children is an actual input), so we apply correction here and point out
|
|
26
|
-
// to the actual first responder (first children)
|
|
27
|
-
let isChildrenActuallyFirstResponder =
|
|
28
|
-
type == "RCTMultilineTextInputView" ||
|
|
29
|
-
type == "RCTSinglelineTextInputView" ||
|
|
30
|
-
type == "RCTTextInputComponentView"
|
|
31
|
-
if isChildrenActuallyFirstResponder {
|
|
32
|
-
UIResponder._currentFirstResponder = (self as? UIView)?.subviews[0]
|
|
33
|
-
} else {
|
|
34
|
-
UIResponder._currentFirstResponder = self
|
|
35
|
-
}
|
|
15
|
+
guard let window = UIApplication.shared.activeWindow else { return nil }
|
|
16
|
+
return window.findFirstResponder()
|
|
36
17
|
}
|
|
37
18
|
}
|
|
38
19
|
|
|
@@ -34,6 +34,18 @@ public extension UIView {
|
|
|
34
34
|
return false
|
|
35
35
|
}
|
|
36
36
|
}
|
|
37
|
+
|
|
38
|
+
func findFirstResponder() -> UIView? {
|
|
39
|
+
if isFirstResponder {
|
|
40
|
+
return self
|
|
41
|
+
}
|
|
42
|
+
for subview in subviews {
|
|
43
|
+
if let responder = subview.findFirstResponder() {
|
|
44
|
+
return responder
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
return nil
|
|
48
|
+
}
|
|
37
49
|
}
|
|
38
50
|
|
|
39
51
|
public extension Optional where Wrapped == UIView {
|
|
@@ -80,12 +80,6 @@ public class FocusedInputObserver: NSObject {
|
|
|
80
80
|
name: UIResponder.keyboardWillShowNotification,
|
|
81
81
|
object: nil
|
|
82
82
|
)
|
|
83
|
-
NotificationCenter.default.addObserver(
|
|
84
|
-
self,
|
|
85
|
-
selector: #selector(keyboardWillHide),
|
|
86
|
-
name: UIResponder.keyboardWillHideNotification,
|
|
87
|
-
object: nil
|
|
88
|
-
)
|
|
89
83
|
NotificationCenter.default.addObserver(
|
|
90
84
|
self,
|
|
91
85
|
selector: #selector(didReceiveFocus),
|
|
@@ -118,10 +112,6 @@ public class FocusedInputObserver: NSObject {
|
|
|
118
112
|
NotificationCenter.default.removeObserver(self)
|
|
119
113
|
}
|
|
120
114
|
|
|
121
|
-
@objc func keyboardWillHide(_: Notification) {
|
|
122
|
-
onBlur()
|
|
123
|
-
}
|
|
124
|
-
|
|
125
115
|
@objc func didReceiveFocus(_: Notification) {
|
|
126
116
|
if UIResponder.current == currentResponder {
|
|
127
117
|
// focus was already handled by keyboard event
|
|
@@ -131,22 +121,13 @@ public class FocusedInputObserver: NSObject {
|
|
|
131
121
|
onFocus()
|
|
132
122
|
}
|
|
133
123
|
|
|
134
|
-
/**
|
|
135
|
-
* We handle blur events in `keyboardWillHide` handler
|
|
136
|
-
* And this additional handler is needed only to have
|
|
137
|
-
* a consistent state when keyboard is not shown
|
|
138
|
-
*/
|
|
139
124
|
@objc func didReceiveBlur(_: Notification) {
|
|
140
|
-
if currentResponder == nil {
|
|
141
|
-
// blur was already handled by keyboard event
|
|
142
|
-
return
|
|
143
|
-
}
|
|
144
125
|
// blur gets triggered on endEditing, so we check if no upcoming
|
|
145
126
|
// didReceiveFocus events are coming to exclude `noFocusedInput`
|
|
146
127
|
// event when user switches between inputs
|
|
147
128
|
DispatchQueue.main.async {
|
|
148
129
|
// check that it wasn't a switch between inputs
|
|
149
|
-
if
|
|
130
|
+
if !(UIResponder.current is TextInput) {
|
|
150
131
|
self.onBlur()
|
|
151
132
|
}
|
|
152
133
|
}
|
|
@@ -173,10 +154,6 @@ public class FocusedInputObserver: NSObject {
|
|
|
173
154
|
}
|
|
174
155
|
|
|
175
156
|
func onBlur() {
|
|
176
|
-
// when we switch to next input, but `showSoftInput={false}`
|
|
177
|
-
if UIResponder.current is TextInput {
|
|
178
|
-
return
|
|
179
|
-
}
|
|
180
157
|
removeObservers(newResponder: nil)
|
|
181
158
|
currentInput = nil
|
|
182
159
|
currentResponder = nil
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "react-native-keyboard-controller",
|
|
3
|
-
"version": "1.16.
|
|
3
|
+
"version": "1.16.3",
|
|
4
4
|
"description": "Keyboard manager which works in identical way on both iOS and Android",
|
|
5
5
|
"main": "lib/commonjs/index",
|
|
6
6
|
"module": "lib/module/index",
|
|
@@ -17,6 +17,7 @@
|
|
|
17
17
|
"jest",
|
|
18
18
|
"!lib/typescript/example",
|
|
19
19
|
"!android/build",
|
|
20
|
+
"!android/src/test",
|
|
20
21
|
"!android/.gradle",
|
|
21
22
|
"!android/gradle",
|
|
22
23
|
"!android/gradlew",
|
|
@@ -79,11 +80,11 @@
|
|
|
79
80
|
},
|
|
80
81
|
"devDependencies": {
|
|
81
82
|
"@commitlint/config-conventional": "^11.0.0",
|
|
82
|
-
"@react-native/babel-preset": "0.
|
|
83
|
-
"@react-native/eslint-config": "0.
|
|
83
|
+
"@react-native/babel-preset": "0.77.0",
|
|
84
|
+
"@react-native/eslint-config": "0.77.0",
|
|
84
85
|
"@release-it/conventional-changelog": "^2.0.0",
|
|
85
86
|
"@testing-library/react-hooks": "^8.0.1",
|
|
86
|
-
"@types/jest": "^29.
|
|
87
|
+
"@types/jest": "^29.5.13",
|
|
87
88
|
"@types/react": "^18.2.6",
|
|
88
89
|
"@typescript-eslint/eslint-plugin": "^6.7.4",
|
|
89
90
|
"@typescript-eslint/parser": "^6.7.4",
|
|
@@ -104,9 +105,9 @@
|
|
|
104
105
|
"pod-install": "^0.1.0",
|
|
105
106
|
"prettier": "^2.8.8",
|
|
106
107
|
"react": "18.3.1",
|
|
107
|
-
"react-native": "0.
|
|
108
|
+
"react-native": "0.77.0",
|
|
108
109
|
"react-native-builder-bob": "^0.18.0",
|
|
109
|
-
"react-native-reanimated": "3.16.
|
|
110
|
+
"react-native-reanimated": "3.16.7",
|
|
110
111
|
"react-test-renderer": "18.2.0",
|
|
111
112
|
"release-it": "^14.2.2",
|
|
112
113
|
"typescript": "5.6.3"
|
|
@@ -169,6 +170,13 @@
|
|
|
169
170
|
"jsSrcsDir": "./src/specs",
|
|
170
171
|
"android": {
|
|
171
172
|
"javaPackageName": "com.reactnativekeyboardcontroller"
|
|
173
|
+
},
|
|
174
|
+
"ios": {
|
|
175
|
+
"componentProvider": {
|
|
176
|
+
"KeyboardControllerView": "KeyboardControllerView",
|
|
177
|
+
"KeyboardGestureArea": "KeyboardGestureArea",
|
|
178
|
+
"OverKeyboardView": "OverKeyboardView"
|
|
179
|
+
}
|
|
172
180
|
}
|
|
173
181
|
}
|
|
174
182
|
}
|
package/android/src/test/java/com/reactnativekeyboardcontroller/traversal/FocusedInputHolderTest.kt
DELETED
|
@@ -1,45 +0,0 @@
|
|
|
1
|
-
package com.reactnativekeyboardcontroller.traversal
|
|
2
|
-
|
|
3
|
-
import android.content.Context
|
|
4
|
-
import android.widget.EditText
|
|
5
|
-
import androidx.test.core.app.ApplicationProvider
|
|
6
|
-
import org.junit.Assert.assertEquals
|
|
7
|
-
import org.junit.Assert.assertFalse
|
|
8
|
-
import org.junit.Assert.assertNull
|
|
9
|
-
import org.junit.Assert.assertTrue
|
|
10
|
-
import org.junit.Test
|
|
11
|
-
import org.junit.runner.RunWith
|
|
12
|
-
import org.robolectric.RobolectricTestRunner
|
|
13
|
-
|
|
14
|
-
@RunWith(RobolectricTestRunner::class)
|
|
15
|
-
class FocusedInputHolderTest {
|
|
16
|
-
@Test
|
|
17
|
-
fun `FocusedInputHolder should hold a weak reference`() {
|
|
18
|
-
val context = ApplicationProvider.getApplicationContext<Context>()
|
|
19
|
-
var input: EditText? = EditText(context)
|
|
20
|
-
|
|
21
|
-
FocusedInputHolder.set(input as EditText)
|
|
22
|
-
|
|
23
|
-
assertEquals(FocusedInputHolder.get(), input)
|
|
24
|
-
|
|
25
|
-
input = null
|
|
26
|
-
|
|
27
|
-
@Suppress("detekt:ExplicitGarbageCollectionCall")
|
|
28
|
-
System.gc()
|
|
29
|
-
|
|
30
|
-
assertNull(FocusedInputHolder.get())
|
|
31
|
-
}
|
|
32
|
-
|
|
33
|
-
@Test
|
|
34
|
-
fun `focus() should request focus on expected field`() {
|
|
35
|
-
val context = ApplicationProvider.getApplicationContext<Context>()
|
|
36
|
-
val input = EditText(context)
|
|
37
|
-
|
|
38
|
-
assertFalse(input.hasFocus())
|
|
39
|
-
|
|
40
|
-
FocusedInputHolder.set(input)
|
|
41
|
-
FocusedInputHolder.focus()
|
|
42
|
-
|
|
43
|
-
assertTrue(input.hasFocus())
|
|
44
|
-
}
|
|
45
|
-
}
|
|
@@ -1,198 +0,0 @@
|
|
|
1
|
-
package com.reactnativekeyboardcontroller.traversal
|
|
2
|
-
|
|
3
|
-
import android.content.Context
|
|
4
|
-
import android.widget.EditText
|
|
5
|
-
import android.widget.LinearLayout
|
|
6
|
-
import androidx.test.core.app.ApplicationProvider
|
|
7
|
-
import com.reactnativekeyboardcontroller.extensions.focus
|
|
8
|
-
import org.junit.Assert.assertTrue
|
|
9
|
-
import org.junit.Before
|
|
10
|
-
import org.junit.Test
|
|
11
|
-
import org.junit.runner.RunWith
|
|
12
|
-
import org.robolectric.RobolectricTestRunner
|
|
13
|
-
import org.robolectric.shadows.ShadowLooper
|
|
14
|
-
|
|
15
|
-
@RunWith(RobolectricTestRunner::class)
|
|
16
|
-
class ViewHierarchyNavigatorTest {
|
|
17
|
-
private lateinit var layout: LinearLayout
|
|
18
|
-
private lateinit var editText1: EditText
|
|
19
|
-
private lateinit var editText2: EditText
|
|
20
|
-
private lateinit var editText3: EditText
|
|
21
|
-
private lateinit var editText4: EditText
|
|
22
|
-
private lateinit var editText5: EditText
|
|
23
|
-
private lateinit var editText6: EditText
|
|
24
|
-
private lateinit var editText7: EditText
|
|
25
|
-
private lateinit var editText8: EditText
|
|
26
|
-
private lateinit var editText9: EditText
|
|
27
|
-
private lateinit var editText10: EditText
|
|
28
|
-
private lateinit var editText11: EditText
|
|
29
|
-
private lateinit var editText12: EditText
|
|
30
|
-
private lateinit var editText13: EditText
|
|
31
|
-
|
|
32
|
-
@Suppress("detekt:CyclomaticComplexMethod")
|
|
33
|
-
@Before
|
|
34
|
-
fun setUp() {
|
|
35
|
-
val context = ApplicationProvider.getApplicationContext<Context>()
|
|
36
|
-
|
|
37
|
-
editText1 = EditText(context).apply { id = 1 }
|
|
38
|
-
editText2 = EditText(context).apply { id = 2 }
|
|
39
|
-
editText3 =
|
|
40
|
-
EditText(context).apply {
|
|
41
|
-
id = 3
|
|
42
|
-
isEnabled = false
|
|
43
|
-
}
|
|
44
|
-
editText4 =
|
|
45
|
-
EditText(context).apply {
|
|
46
|
-
id = 4
|
|
47
|
-
isEnabled = false
|
|
48
|
-
}
|
|
49
|
-
editText5 = EditText(context).apply { id = 5 }
|
|
50
|
-
editText6 = EditText(context).apply { id = 6 }
|
|
51
|
-
editText7 = EditText(context).apply { id = 7 }
|
|
52
|
-
editText8 = EditText(context).apply { id = 8 }
|
|
53
|
-
editText9 = EditText(context).apply { id = 9 }
|
|
54
|
-
editText10 = EditText(context).apply { id = 10 }
|
|
55
|
-
editText11 = EditText(context).apply { id = 11 }
|
|
56
|
-
editText12 = EditText(context).apply { id = 12 }
|
|
57
|
-
editText13 = EditText(context).apply { id = 13 }
|
|
58
|
-
|
|
59
|
-
layout =
|
|
60
|
-
LinearLayout(context).apply {
|
|
61
|
-
addView(editText1)
|
|
62
|
-
addView(editText2)
|
|
63
|
-
addView(editText3)
|
|
64
|
-
addView(editText4)
|
|
65
|
-
addView(
|
|
66
|
-
LinearLayout(context).apply {
|
|
67
|
-
addView(editText5)
|
|
68
|
-
addView(editText6)
|
|
69
|
-
addView(editText7)
|
|
70
|
-
},
|
|
71
|
-
)
|
|
72
|
-
addView(editText8)
|
|
73
|
-
addView(editText9)
|
|
74
|
-
addView(editText10)
|
|
75
|
-
addView(editText11)
|
|
76
|
-
addView(editText12)
|
|
77
|
-
addView(editText13)
|
|
78
|
-
}
|
|
79
|
-
}
|
|
80
|
-
|
|
81
|
-
@Test
|
|
82
|
-
fun `getAllInputFields returns all EditTexts in ViewGroup`() {
|
|
83
|
-
val editTexts = ViewHierarchyNavigator.getAllInputFields(layout)
|
|
84
|
-
|
|
85
|
-
// 13 (all) - 2 (disabled)
|
|
86
|
-
assertTrue(editTexts.size == 11)
|
|
87
|
-
}
|
|
88
|
-
|
|
89
|
-
@Test
|
|
90
|
-
fun `setFocusTo to 'next' should set focus to next field`() {
|
|
91
|
-
editText1.focus()
|
|
92
|
-
|
|
93
|
-
ViewHierarchyNavigator.setFocusTo("next", editText1)
|
|
94
|
-
|
|
95
|
-
ShadowLooper.runUiThreadTasksIncludingDelayedTasks()
|
|
96
|
-
|
|
97
|
-
assertTrue(editText2.hasFocus())
|
|
98
|
-
}
|
|
99
|
-
|
|
100
|
-
@Test
|
|
101
|
-
fun `setFocusTo to 'prev' should set focus to previous field`() {
|
|
102
|
-
editText2.focus()
|
|
103
|
-
|
|
104
|
-
ViewHierarchyNavigator.setFocusTo("prev", editText2)
|
|
105
|
-
|
|
106
|
-
ShadowLooper.runUiThreadTasksIncludingDelayedTasks()
|
|
107
|
-
|
|
108
|
-
assertTrue(editText1.hasFocus())
|
|
109
|
-
}
|
|
110
|
-
|
|
111
|
-
@Test
|
|
112
|
-
fun `setFocusTo to 'next' should skip non-editable fields`() {
|
|
113
|
-
editText2.focus()
|
|
114
|
-
|
|
115
|
-
ViewHierarchyNavigator.setFocusTo("next", editText2)
|
|
116
|
-
|
|
117
|
-
ShadowLooper.runUiThreadTasksIncludingDelayedTasks()
|
|
118
|
-
|
|
119
|
-
assertTrue(editText5.hasFocus())
|
|
120
|
-
}
|
|
121
|
-
|
|
122
|
-
@Test
|
|
123
|
-
fun `setFocusTo to 'prev' should skip non-editable fields`() {
|
|
124
|
-
editText5.focus()
|
|
125
|
-
|
|
126
|
-
ViewHierarchyNavigator.setFocusTo("prev", editText5)
|
|
127
|
-
|
|
128
|
-
ShadowLooper.runUiThreadTasksIncludingDelayedTasks()
|
|
129
|
-
|
|
130
|
-
assertTrue(editText2.hasFocus())
|
|
131
|
-
}
|
|
132
|
-
|
|
133
|
-
@Test
|
|
134
|
-
fun `setFocusTo to 'next' should set focus relatively to current group`() {
|
|
135
|
-
editText5.focus()
|
|
136
|
-
|
|
137
|
-
ViewHierarchyNavigator.setFocusTo("next", editText5)
|
|
138
|
-
|
|
139
|
-
ShadowLooper.runUiThreadTasksIncludingDelayedTasks()
|
|
140
|
-
|
|
141
|
-
assertTrue(editText6.hasFocus())
|
|
142
|
-
}
|
|
143
|
-
|
|
144
|
-
@Test
|
|
145
|
-
fun `setFocusTo to 'prev' should set focus relatively to current group`() {
|
|
146
|
-
editText7.focus()
|
|
147
|
-
|
|
148
|
-
ViewHierarchyNavigator.setFocusTo("prev", editText7)
|
|
149
|
-
|
|
150
|
-
ShadowLooper.runUiThreadTasksIncludingDelayedTasks()
|
|
151
|
-
|
|
152
|
-
assertTrue(editText6.hasFocus())
|
|
153
|
-
}
|
|
154
|
-
|
|
155
|
-
@Test
|
|
156
|
-
fun `setFocusTo to 'next' should correctly exit from current group`() {
|
|
157
|
-
editText7.focus()
|
|
158
|
-
|
|
159
|
-
ViewHierarchyNavigator.setFocusTo("next", editText7)
|
|
160
|
-
|
|
161
|
-
ShadowLooper.runUiThreadTasksIncludingDelayedTasks()
|
|
162
|
-
|
|
163
|
-
assertTrue(editText8.hasFocus())
|
|
164
|
-
}
|
|
165
|
-
|
|
166
|
-
@Test
|
|
167
|
-
fun `setFocusTo to 'prev' should set focus to last element in group`() {
|
|
168
|
-
editText8.focus()
|
|
169
|
-
|
|
170
|
-
ViewHierarchyNavigator.setFocusTo("prev", editText8)
|
|
171
|
-
|
|
172
|
-
ShadowLooper.runUiThreadTasksIncludingDelayedTasks()
|
|
173
|
-
|
|
174
|
-
assertTrue(editText7.hasFocus())
|
|
175
|
-
}
|
|
176
|
-
|
|
177
|
-
@Test
|
|
178
|
-
fun `setFocusTo to 'next' should do nothing if it's last element`() {
|
|
179
|
-
editText13.focus()
|
|
180
|
-
|
|
181
|
-
ViewHierarchyNavigator.setFocusTo("next", editText13)
|
|
182
|
-
|
|
183
|
-
ShadowLooper.runUiThreadTasksIncludingDelayedTasks()
|
|
184
|
-
|
|
185
|
-
assertTrue(editText13.hasFocus())
|
|
186
|
-
}
|
|
187
|
-
|
|
188
|
-
@Test
|
|
189
|
-
fun `setFocusTo to 'prev' should do nothing if it's first element`() {
|
|
190
|
-
editText1.focus()
|
|
191
|
-
|
|
192
|
-
ViewHierarchyNavigator.setFocusTo("prev", editText1)
|
|
193
|
-
|
|
194
|
-
ShadowLooper.runUiThreadTasksIncludingDelayedTasks()
|
|
195
|
-
|
|
196
|
-
assertTrue(editText1.hasFocus())
|
|
197
|
-
}
|
|
198
|
-
}
|
|
File without changes
|