Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix: ignore duplicated events #298

Draft
wants to merge 3 commits into
base: main
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -4,25 +4,29 @@ import com.facebook.react.bridge.Arguments
import com.facebook.react.bridge.WritableMap
import com.facebook.react.uimanager.events.Event

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 event: String,
private val height: Double,
private val progress: Double,
private val duration: Int,
private val target: Int,
private val data: KeyboardTransitionEventData,
) : Event<KeyboardTransitionEvent>(surfaceId, viewId) {
override fun getEventName() = event
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", progress)
putDouble("height", height)
putInt("duration", duration)
putInt("target", target)
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
@@ -1,6 +1,9 @@
package com.reactnativekeyboardcontroller.extensions

import android.util.Log
import android.view.View
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
Expand All @@ -14,3 +17,9 @@ fun ThemedReactContext?.dispatchEvent(viewId: Int, event: Event<*>) {
UIManagerHelper.getEventDispatcherForReactTag(this, viewId)
eventDispatcher?.dispatchEvent(event)
}

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

Log.i("ThemedReactContext", event)
}
Original file line number Diff line number Diff line change
Expand Up @@ -13,15 +13,16 @@ import androidx.core.view.WindowInsetsAnimationCompat
import androidx.core.view.WindowInsetsCompat
import com.facebook.react.bridge.Arguments
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.views.textinput.ReactEditText
import com.facebook.react.views.view.ReactViewGroup
import com.reactnativekeyboardcontroller.InteractiveKeyboardProvider
import com.reactnativekeyboardcontroller.events.KeyboardTransitionEvent
import com.reactnativekeyboardcontroller.events.KeyboardTransitionEventData
import com.reactnativekeyboardcontroller.extensions.dispatchEvent
import com.reactnativekeyboardcontroller.extensions.dp
import com.reactnativekeyboardcontroller.extensions.emitEvent
import com.reactnativekeyboardcontroller.extensions.isKeyboardAnimation
import kotlin.math.abs

Expand All @@ -43,6 +44,7 @@ class KeyboardAnimationCallback(
private var duration = 0
private var viewTagFocused = -1
private var animation: ValueAnimator? = null
private var lastEventDispatched: KeyboardTransitionEventData? = null

// listeners
private val focusListener = OnGlobalFocusChangeListener { oldFocus, newFocus ->
Expand All @@ -56,32 +58,26 @@ 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
context.dispatchEvent(
view.id,
KeyboardTransitionEvent(
surfaceId,
view.id,
this.dispatchEventToJS(
KeyboardTransitionEventData(
"topKeyboardMoveStart",
this.persistentKeyboardHeight,
1.0,
0,
viewTagFocused,
),
)
context.dispatchEvent(
view.id,
KeyboardTransitionEvent(
surfaceId,
view.id,
this.dispatchEventToJS(
KeyboardTransitionEventData(
"topKeyboardMoveEnd",
this.persistentKeyboardHeight,
1.0,
0,
viewTagFocused,
),
)
this.emitEvent("KeyboardController::keyboardWillShow", getEventParams(this.persistentKeyboardHeight))
this.emitEvent("KeyboardController::keyboardDidShow", getEventParams(this.persistentKeyboardHeight))
context.emitEvent("KeyboardController::keyboardWillShow", getEventParams(this.persistentKeyboardHeight))
context.emitEvent("KeyboardController::keyboardDidShow", getEventParams(this.persistentKeyboardHeight))
}
}
}
Expand Down Expand Up @@ -158,17 +154,14 @@ class KeyboardAnimationCallback(
}

layoutObserver?.syncUpLayout()
this.emitEvent(
context.emitEvent(
"KeyboardController::" + if (!isKeyboardVisible) "keyboardWillHide" else "keyboardWillShow",
getEventParams(keyboardHeight),
)

Log.i(TAG, "HEIGHT:: $keyboardHeight TAG:: $viewTagFocused")
context.dispatchEvent(
view.id,
KeyboardTransitionEvent(
surfaceId,
view.id,
this.dispatchEventToJS(
KeyboardTransitionEventData(
"topKeyboardMoveStart",
keyboardHeight,
if (!isKeyboardVisible) 0.0 else 1.0,
Expand Down Expand Up @@ -215,11 +208,8 @@ class KeyboardAnimationCallback(
)

val event = if (InteractiveKeyboardProvider.isInteractive) "topKeyboardMoveInteractive" else "topKeyboardMove"
context.dispatchEvent(
view.id,
KeyboardTransitionEvent(
surfaceId,
view.id,
this.dispatchEventToJS(
KeyboardTransitionEventData(
event,
height,
progress,
Expand Down Expand Up @@ -255,15 +245,12 @@ class KeyboardAnimationCallback(
}
isKeyboardVisible = isKeyboardVisible || isKeyboardShown

this.emitEvent(
context.emitEvent(
"KeyboardController::" + if (!isKeyboardVisible) "keyboardDidHide" else "keyboardDidShow",
getEventParams(keyboardHeight),
)
context.dispatchEvent(
view.id,
KeyboardTransitionEvent(
surfaceId,
view.id,
this.dispatchEventToJS(
KeyboardTransitionEventData(
"topKeyboardMoveEnd",
keyboardHeight,
if (!isKeyboardVisible) 0.0 else 1.0,
Expand Down Expand Up @@ -299,12 +286,9 @@ class KeyboardAnimationCallback(
this.animation?.cancel()
}

this.emitEvent("KeyboardController::keyboardWillShow", getEventParams(keyboardHeight))
context.dispatchEvent(
view.id,
KeyboardTransitionEvent(
surfaceId,
view.id,
context.emitEvent("KeyboardController::keyboardWillShow", getEventParams(keyboardHeight))
this.dispatchEventToJS(
KeyboardTransitionEventData(
"topKeyboardMoveStart",
keyboardHeight,
1.0,
Expand All @@ -317,11 +301,8 @@ class KeyboardAnimationCallback(
ValueAnimator.ofFloat(this.persistentKeyboardHeight.toFloat(), keyboardHeight.toFloat())
animation.addUpdateListener { animator ->
val toValue = animator.animatedValue as Float
context.dispatchEvent(
view.id,
KeyboardTransitionEvent(
surfaceId,
view.id,
this.dispatchEventToJS(
KeyboardTransitionEventData(
"topKeyboardMove",
toValue.toDouble(),
toValue.toDouble() / keyboardHeight,
Expand All @@ -331,12 +312,9 @@ class KeyboardAnimationCallback(
)
}
animation.doOnEnd {
this.emitEvent("KeyboardController::keyboardDidShow", getEventParams(keyboardHeight))
context.dispatchEvent(
view.id,
KeyboardTransitionEvent(
surfaceId,
view.id,
context.emitEvent("KeyboardController::keyboardDidShow", getEventParams(keyboardHeight))
this.dispatchEventToJS(
KeyboardTransitionEventData(
"topKeyboardMoveEnd",
keyboardHeight,
1.0,
Expand Down Expand Up @@ -368,12 +346,6 @@ class KeyboardAnimationCallback(
return (keyboardHeight - navigationBar).toFloat().dp.coerceAtLeast(0.0)
}

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

Log.i(TAG, event)
}

private fun getEventParams(height: Double): WritableMap {
val params: WritableMap = Arguments.createMap()
params.putDouble("height", height)
Expand All @@ -384,6 +356,20 @@ class KeyboardAnimationCallback(
return params
}

private fun dispatchEventToJS(event: KeyboardTransitionEventData) {
if (event != lastEventDispatched) {
lastEventDispatched = event
context.dispatchEvent(
view.id,
KeyboardTransitionEvent(
surfaceId,
view.id,
data = event,
),
)
}
}

companion object {
private const val DEFAULT_ANIMATION_TIME = 250
}
Expand Down
22 changes: 19 additions & 3 deletions example/src/screens/Examples/InteractiveKeyboard/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,9 @@ import {
useKeyboardHandler,
} from 'react-native-keyboard-controller';
import Reanimated, {
scrollTo,
useAnimatedRef,
useAnimatedScrollHandler,
useAnimatedStyle,
useSharedValue,
} from 'react-native-reanimated';
Expand All @@ -17,7 +20,7 @@ import styles from './styles';

const AnimatedTextInput = Reanimated.createAnimatedComponent(TextInput);

const useKeyboardAnimation = () => {
const useKeyboardAnimation = ({ref, scroll}) => {
const progress = useSharedValue(0);
const height = useSharedValue(0);
useKeyboardHandler({
Expand All @@ -30,6 +33,8 @@ const useKeyboardAnimation = () => {
onInteractive: (e) => {
'worklet';

scrollTo(ref, 0, scroll.value, false);

progress.value = e.progress;
height.value = e.height;
},
Expand All @@ -41,8 +46,16 @@ const useKeyboardAnimation = () => {
type Props = StackScreenProps<ExamplesStackParamList>;

function InteractiveKeyboard({ navigation }: Props) {
const aRef = useAnimatedRef();
const scroll = useSharedValue(0);
const [interpolator, setInterpolator] = useState<'ios' | 'linear'>('linear');
const { height } = useKeyboardAnimation();
const { height } = useKeyboardAnimation({ref: aRef, scroll: scroll});

const onScroll = useAnimatedScrollHandler({
onScroll: (e) => {
scroll.value = e.contentOffset.y;
},
})

useEffect(() => {
navigation.setOptions({
Expand Down Expand Up @@ -76,7 +89,8 @@ function InteractiveKeyboard({ navigation }: Props) {
);
const fakeView = useAnimatedStyle(
() => ({
height: height.value,
// TODO: don't update when onInteractive is fired
// height: height.value,
}),
[]
);
Expand All @@ -89,6 +103,8 @@ function InteractiveKeyboard({ navigation }: Props) {
showOnSwipeUp
>
<Reanimated.ScrollView
ref={aRef}
onScroll={onScroll}
showsVerticalScrollIndicator={false}
style={scrollViewStyle}
>
Expand Down