Skip to content

Commit

Permalink
feat: keep progress
Browse files Browse the repository at this point in the history
  • Loading branch information
kirillzyusko committed Oct 15, 2023
1 parent 0ab0d18 commit 3934a03
Show file tree
Hide file tree
Showing 13 changed files with 248 additions and 60 deletions.
60 changes: 60 additions & 0 deletions FabricExample/__tests__/focused-input.spec.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
import '@testing-library/jest-native/extend-expect';
import React from 'react';
import Reanimated, { useAnimatedStyle } from 'react-native-reanimated';
import { render } from '@testing-library/react-native';

import { useReanimatedFocusedInput } from 'react-native-keyboard-controller';

function RectangleWithFocusedInputLayout() {
const input = useReanimatedFocusedInput();
const style = useAnimatedStyle(
() => {
const layout = input.value?.layout;

return {
top: layout?.y,
left: layout?.x,
height: layout?.height,
width: layout?.width,
};
},
[]
);

return <Reanimated.View testID="view" style={style} />;
}

describe('`useReanimatedFocusedInput` mocking', () => {
it('should have different styles depends on `useReanimatedFocusedInput`', () => {
const { getByTestId, update } = render(<RectangleWithFocusedInputLayout />);

expect(getByTestId('view')).toHaveStyle({
top: 0,
left: 0,
width: 200,
height: 40,
});

(useReanimatedFocusedInput as jest.Mock).mockReturnValue({
value: {
target: 2,
layout: {
x: 10,
y: 100,
width: 190,
height: 80,
absoluteX: 100,
absoluteY: 200,
},
},
});
update(<RectangleWithFocusedInputLayout />);

expect(getByTestId('view')).toHaveStyle({
top: 100,
left: 10,
width: 190,
height: 80,
});
});
});
Original file line number Diff line number Diff line change
Expand Up @@ -130,7 +130,7 @@ const KeyboardAwareScrollView: FC<ScrollViewProps> = ({
keyboardHeight.value = e.height;
}

if (focusWasChanged && e.target !== -1) {
if (focusWasChanged && e.target !== -1 && !keyboardWillAppear) {
console.log("focus was changed -> scrolling");
maybeScroll(e.height, true);
}
Expand Down
45 changes: 42 additions & 3 deletions TODO
Original file line number Diff line number Diff line change
Expand Up @@ -8,13 +8,52 @@
- (?) when dispatching focusInput -> compare by height? because keyboardWillShow triggers when user types first letter <- we anyway plan to update layout when user types letters?
- (x) contentContainerStyle -> paddingBottom should depend on keyboard size <- added additional view
- (x) focus on 3, focus on 5 -> scroll looks strange <- persist scroll on tag changes as well
- (x) jest -> add new mocks + new unit tests
- (x) improve events precision (int -> float, pageY, width точно float или double)
- (x) console.log("focus was changed -> scrolling") (even when keyboard appears) <- `!keyboardWillAppear` condition added
- scrollResponderScrollNativeHandleToKeyboard
- think about renaming FocusedInputLayoutChangedObserver -> FocusedInputObserver
-
- send `null` when there is no input in focus
- jest -> add new mocks + new unit tests
- improve events precision (int -> float, pageY, width точно float или double)
- const {update} = useReanimatedFocusedInput(); <- requires REA3
- crash on every file changes AwareScrollView/hot reload
- console.log("focus was changed -> scrolling") (even when keyboard appears)
- focus on field -> type any symbol -> close keyboard => no back transition animation
- android - dispatch event when keyboard changes size
- enable hook under feature flag?

```
KeyboardTransitionEventData(
event = "topKeyboardMoveStart",
height = this.persistentKeyboardHeight,
progress = 1.0,
duration = 0,
target = viewTagFocused
),

data class KeyboardTransitionEventData(
val event: String,
val height: Double,
val progress: Double,
val duration: Int,
val target: Int,
)

@Suppress("detekt:LongParameterList")
class KeyboardTransitionEvent(
surfaceId: Int,
viewId: Int,
private val data: KeyboardTransitionEventData,
) : Event<KeyboardTransitionEvent>(surfaceId, viewId) {
override fun getEventName() = data.event

// All events for a given view can be coalesced?
override fun getCoalescingKey(): Short = 0

override fun getEventData(): WritableMap? = Arguments.createMap().apply {
putDouble("progress", data.progress)
putDouble("height", data.height)
putInt("duration", data.duration)
putInt("target", data.target)
}
}
```
Original file line number Diff line number Diff line change
Expand Up @@ -7,12 +7,12 @@ import com.facebook.react.uimanager.events.Event
class FocusedInputLayoutChangedEvent(
surfaceId: Int,
viewId: Int,
private val x: Int,
private val y: Int,
private val width: Int,
private val height: Int,
private val absoluteX: Int,
private val absoluteY: Int,
private val x: Double,
private val y: Double,
private val width: Double,
private val height: Double,
private val absoluteX: Double,
private val absoluteY: Double,
private val target: Int,
) : Event<KeyboardTransitionEvent>(surfaceId, viewId) {
override fun getEventName() = "topFocusedInputLayoutChanged"
Expand All @@ -25,12 +25,12 @@ class FocusedInputLayoutChangedEvent(
putMap(
"layout",
Arguments.createMap().apply {
putInt("x", x)
putInt("y", y)
putInt("width", width)
putInt("height", height)
putInt("absoluteX", absoluteX)
putInt("absoluteY", absoluteY)
putDouble("x", x)
putDouble("y", y)
putDouble("width", width)
putDouble("height", height)
putDouble("absoluteX", absoluteX)
putDouble("absoluteY", absoluteY)
},
)
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,15 @@ package com.reactnativekeyboardcontroller.extensions

import android.view.View
import com.facebook.react.uimanager.ThemedReactContext
import com.facebook.react.uimanager.UIManagerHelper
import com.facebook.react.uimanager.events.Event
import com.facebook.react.uimanager.events.EventDispatcher

val ThemedReactContext.rootView: View?
get() = this.currentActivity?.window?.decorView?.rootView

fun ThemedReactContext?.dispatchEvent(viewId: Int, event: Event<*>) {
val eventDispatcher: EventDispatcher? =
UIManagerHelper.getEventDispatcherForReactTag(this, viewId)
eventDispatcher?.dispatchEvent(event)
}
Original file line number Diff line number Diff line change
Expand Up @@ -3,11 +3,10 @@ package com.reactnativekeyboardcontroller.listeners
import android.view.View.OnLayoutChangeListener
import com.facebook.react.uimanager.ThemedReactContext
import com.facebook.react.uimanager.UIManagerHelper
import com.facebook.react.uimanager.events.Event
import com.facebook.react.uimanager.events.EventDispatcher
import com.facebook.react.views.textinput.ReactEditText
import com.facebook.react.views.view.ReactViewGroup
import com.reactnativekeyboardcontroller.events.FocusedInputLayoutChangedEvent
import com.reactnativekeyboardcontroller.extensions.dispatchEvent
import com.reactnativekeyboardcontroller.extensions.dp
import com.reactnativekeyboardcontroller.extensions.screenLocation

Expand Down Expand Up @@ -42,13 +41,19 @@ class FocusedInputLayoutObserver(val view: ReactViewGroup, private val context:
val input = lastFocusedInput ?: return

val (x, y) = input.screenLocation
this.sendEventToJS(FocusedInputLayoutChangedEvent(surfaceId, view.id, input.x.dp.toInt(), input.y.dp.toInt(), input.width.toFloat().dp.toInt(), input.height.toFloat().dp.toInt(), x.toFloat().dp.toInt(), y.toFloat().dp.toInt(), input.id))
}

// TODO: remove code duplication
private fun sendEventToJS(event: Event<*>) {
val eventDispatcher: EventDispatcher? =
UIManagerHelper.getEventDispatcherForReactTag(context, view.id)
eventDispatcher?.dispatchEvent(event)
context.dispatchEvent(
view.id,
FocusedInputLayoutChangedEvent(
surfaceId,
view.id,
input.x.dp,
input.y.dp,
input.width.toFloat().dp,
input.height.toFloat().dp,
x.toFloat().dp,
y.toFloat().dp,
input.id,
),
)
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -15,12 +15,11 @@ import com.facebook.react.bridge.WritableMap
import com.facebook.react.modules.core.DeviceEventManagerModule
import com.facebook.react.uimanager.ThemedReactContext
import com.facebook.react.uimanager.UIManagerHelper
import com.facebook.react.uimanager.events.Event
import com.facebook.react.uimanager.events.EventDispatcher
import com.facebook.react.views.textinput.ReactEditText
import com.facebook.react.views.view.ReactViewGroup
import com.reactnativekeyboardcontroller.InteractiveKeyboardProvider
import com.reactnativekeyboardcontroller.events.KeyboardTransitionEvent
import com.reactnativekeyboardcontroller.extensions.dispatchEvent
import com.reactnativekeyboardcontroller.extensions.dp
import kotlin.math.abs

Expand Down Expand Up @@ -57,7 +56,8 @@ class KeyboardAnimationCallback(
// 2. event should be send only when keyboard is visible, since this event arrives earlier -> `tag` will be
// 100% included in onStart/onMove/onEnd lifecycles, but triggering onStart/onEnd several time
// can bring breaking changes
this.sendEventToJS(
context.dispatchEvent(
view.id,
KeyboardTransitionEvent(
surfaceId,
view.id,
Expand All @@ -68,7 +68,8 @@ class KeyboardAnimationCallback(
viewTagFocused,
),
)
this.sendEventToJS(
context.dispatchEvent(
view.id,
KeyboardTransitionEvent(
surfaceId,
view.id,
Expand Down Expand Up @@ -117,7 +118,8 @@ class KeyboardAnimationCallback(
val duration = DEFAULT_ANIMATION_TIME.toInt()

this.emitEvent("KeyboardController::keyboardWillShow", getEventParams(keyboardHeight))
this.sendEventToJS(
context.dispatchEvent(
view.id,
KeyboardTransitionEvent(
surfaceId,
view.id,
Expand All @@ -133,7 +135,8 @@ class KeyboardAnimationCallback(
ValueAnimator.ofFloat(this.persistentKeyboardHeight.toFloat(), keyboardHeight.toFloat())
animation.addUpdateListener { animator ->
val toValue = animator.animatedValue as Float
this.sendEventToJS(
context.dispatchEvent(
view.id,
KeyboardTransitionEvent(
surfaceId,
view.id,
Expand All @@ -147,7 +150,8 @@ class KeyboardAnimationCallback(
}
animation.doOnEnd {
this.emitEvent("KeyboardController::keyboardDidShow", getEventParams(keyboardHeight))
this.sendEventToJS(
context.dispatchEvent(
view.id,
KeyboardTransitionEvent(
surfaceId,
view.id,
Expand Down Expand Up @@ -188,7 +192,8 @@ class KeyboardAnimationCallback(
)

Log.i(TAG, "HEIGHT:: $keyboardHeight TAG:: $viewTagFocused")
this.sendEventToJS(
context.dispatchEvent(
view.id,
KeyboardTransitionEvent(
surfaceId,
view.id,
Expand Down Expand Up @@ -235,7 +240,8 @@ class KeyboardAnimationCallback(
)

val event = if (InteractiveKeyboardProvider.isInteractive) "topKeyboardMoveInteractive" else "topKeyboardMove"
this.sendEventToJS(
context.dispatchEvent(
view.id,
KeyboardTransitionEvent(
surfaceId,
view.id,
Expand Down Expand Up @@ -274,7 +280,8 @@ class KeyboardAnimationCallback(
"KeyboardController::" + if (!isKeyboardVisible) "keyboardDidHide" else "keyboardDidShow",
getEventParams(keyboardHeight),
)
this.sendEventToJS(
context.dispatchEvent(
view.id,
KeyboardTransitionEvent(
surfaceId,
view.id,
Expand Down Expand Up @@ -305,12 +312,6 @@ class KeyboardAnimationCallback(
return (keyboardHeight - navigationBar).toFloat().dp.coerceAtLeast(0.0)
}

private fun sendEventToJS(event: Event<*>) {
val eventDispatcher: EventDispatcher? =
UIManagerHelper.getEventDispatcherForReactTag(context, view.id)
eventDispatcher?.dispatchEvent(event)
}

private fun emitEvent(event: String, params: WritableMap) {
context?.getJSModule(DeviceEventManagerModule.RCTDeviceEventEmitter::class.java)?.emit(event, params)

Expand Down
8 changes: 4 additions & 4 deletions docs/docs/api/hooks/input/use-reanimated-focused-input.md
Original file line number Diff line number Diff line change
Expand Up @@ -18,16 +18,16 @@ The value from `useReanimatedFocusedInput` will be always updated before keyboar

## Event structure

Value from this hook is returning as `SharedValue`. The returned data has next structure:
Value from this hook is returned as `SharedValue`. The returned data has next structure:

```ts
type KeyboardEventData = {
target: number; // tag of the focused TextInput
layout: {
layout: { // layout of the focused TextInput
x: number; // `x` coordinate inside the parent component
y: number; // `y` coordinate inside the parent component
width: number; // width of the TextInput
height: number; // height of the TextInput
width: number; // `width` of the TextInput
height: number; // `height` of the TextInput
absoluteX: number; // `x` coordinate on the screen
absoluteY: number; // `y` coordinate on the screen
};
Expand Down
Loading

0 comments on commit 3934a03

Please sign in to comment.