Skip to content

Commit

Permalink
feat: new offset prop for KeyboardGestureArea on Android (#531)
Browse files Browse the repository at this point in the history
## 📜 Description

Added `offset` property for `KeyboardGestureArea` component.

## 💡 Motivation and Context

Add requested changes in
#250
only on Android (for now). Later on I'll try to add similar
functionality to iOS.

On Android such functionality can be easily implemented, because we have
a full control over the keyboard position - so we just need to take an
additional variable in math calculations and that's all 😎

I am still not sure if adding negative number to the linear interpolator
should change the behavior of linear interpolator - it's open question
and I can fix it later on. Right now I'm happy to have such changes,
because it definitely improves UX so :shipit:

## 📢 Changelog

<!-- High level overview of important changes -->
<!-- For example: fixed status bar manipulation; added new types
declarations; -->
<!-- If your changes don't affect one of platform/language below - then
remove this platform/language -->

### Docs

- added info about `offset` property;

### JS

- update `KeyboardGestureArea` spec;
- update `types.ts` to include `offset` property for
`KeyboardGestureArea`;

### Android

- added setter for new `offset` property;
- added new param to interpolator interface;

## 🤔 How Has This Been Tested?

Tested manually on Pixel 7 Pro (API 34).

## 📸 Screenshots (if appropriate):

|Before|After|
|-------|-----|
|<video
src="https://github.com/user-attachments/assets/ba8fb69b-0c59-4ad0-a123-cc187c1e516d">|<video
src="https://github.com/user-attachments/assets/f3a7bb61-b0d0-406a-b3ca-02502dd8a78e">|

## 📝 Checklist

- [x] CI successfully passed
- [x] I added new mocks and corresponding unit-tests if library API was
changed
  • Loading branch information
kirillzyusko authored Aug 2, 2024
1 parent 265b93c commit 2f901a9
Show file tree
Hide file tree
Showing 12 changed files with 43 additions and 5 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -87,10 +87,11 @@ function InteractiveKeyboard({ navigation }: Props) {
return (
<View style={styles.container}>
<KeyboardGestureArea
testID="chat.gesture"
style={styles.content}
testID="chat.gesture"
interpolator={interpolator}
showOnSwipeUp
offset={50}
>
<Reanimated.ScrollView
testID="chat.scroll"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,11 @@ class KeyboardGestureAreaViewManager(mReactContext: ReactApplicationContext) :
return manager.createViewInstance(context)
}

@ReactProp(name = "offset")
override fun setOffset(view: ReactViewGroup, value: Double) {
manager.setOffset(view as KeyboardGestureAreaReactViewGroup, value)
}

@ReactProp(name = "interpolator")
override fun setInterpolator(view: ReactViewGroup, value: String?) {
manager.setInterpolator(view as KeyboardGestureAreaReactViewGroup, value ?: "linear")
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,8 @@ interface Interpolator {
* @param dy the distance that the finger has moved relative to the previous location.
* @param absoluteFingerPosition current position of the finger.
* @param keyboardPosition current keyboard position.
* @param offset extra space to the keyboard to activate a gesture
* @return the distance the keyboard should be moved from its current location.
*/
fun interpolate(dy: Int, absoluteFingerPosition: Int, keyboardPosition: Int): Int
fun interpolate(dy: Int, absoluteFingerPosition: Int, keyboardPosition: Int, offset: Int): Int
}
Original file line number Diff line number Diff line change
Expand Up @@ -5,9 +5,10 @@ class IosInterpolator : Interpolator {
dy: Int,
absoluteFingerPosition: Int,
keyboardPosition: Int,
offset: Int,
): Int {
if (
absoluteFingerPosition <= keyboardPosition || // user over scrolled keyboard
absoluteFingerPosition <= keyboardPosition + offset || // user over scrolled keyboard
dy <= 0 // user scrolls up
) {
return dy
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ class LinearInterpolator : Interpolator {
dy: Int,
absoluteFingerPosition: Int,
keyboardPosition: Int,
offset: Int,
): Int {
return dy
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,10 @@ class KeyboardGestureAreaViewManagerImpl(mReactContext: ReactApplicationContext)
return KeyboardGestureAreaReactViewGroup(reactContext)
}

fun setOffset(view: KeyboardGestureAreaReactViewGroup, offset: Double) {
view.setOffset(offset)
}

fun setInterpolator(view: KeyboardGestureAreaReactViewGroup, interpolator: String) {
view.setInterpolator(interpolator)
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ import androidx.core.view.WindowInsetsCompat
import com.facebook.react.uimanager.ThemedReactContext
import com.facebook.react.views.view.ReactViewGroup
import com.reactnativekeyboardcontroller.extensions.copyBoundsInWindow
import com.reactnativekeyboardcontroller.extensions.px
import com.reactnativekeyboardcontroller.interactive.KeyboardAnimationController
import com.reactnativekeyboardcontroller.interactive.interpolators.Interpolator
import com.reactnativekeyboardcontroller.interactive.interpolators.IosInterpolator
Expand All @@ -35,6 +36,7 @@ class KeyboardGestureAreaReactViewGroup(private val reactContext: ThemedReactCon
private var keyboardHeight = 0

// react props
private var offset = 0
private var interpolator: Interpolator = LinearInterpolator()
private var scrollKeyboardOnScreenWhenNotVisible = false
private var scrollKeyboardOffScreenWhenVisible = true
Expand Down Expand Up @@ -63,6 +65,10 @@ class KeyboardGestureAreaReactViewGroup(private val reactContext: ThemedReactCon
}

// region Props setters
fun setOffset(offset: Double) {
this.offset = offset.toFloat().px.toInt()
}

fun setInterpolator(interpolator: String) {
this.interpolator = interpolators[interpolator] ?: LinearInterpolator()
}
Expand Down Expand Up @@ -125,6 +131,7 @@ class KeyboardGestureAreaReactViewGroup(private val reactContext: ThemedReactCon
dy.roundToInt(),
this.getWindowHeight() - event.rawY.toInt(),
controller.getCurrentKeyboardHeight(),
offset,
)

if (moveBy != 0) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,11 @@ class KeyboardGestureAreaViewManager(mReactContext: ReactApplicationContext) : R
return manager.createViewInstance(reactContext)
}

@ReactProp(name = "offset")
fun setInterpolator(view: KeyboardGestureAreaReactViewGroup, offset: Double) {
manager.setOffset(view, offset)
}

@ReactProp(name = "interpolator")
fun setInterpolator(view: KeyboardGestureAreaReactViewGroup, interpolator: String) {
manager.setInterpolator(view, interpolator)
Expand Down
6 changes: 5 additions & 1 deletion docs/docs/api/keyboard-gesture-area.md
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,10 @@ This component is available only for Android >= 11. For iOS and Android < 11 it

## Props

### `offset`

Extra distance to the keyboard. Default is `0`.

### `interpolator`

String with possible values `linear` and `ios`:
Expand All @@ -42,7 +46,7 @@ A boolean prop which allows to customize interactive keyboard behavior. If set t
## Example

```tsx
<KeyboardGestureArea interpolator="ios">
<KeyboardGestureArea interpolator="ios" offset={50}>
<ScrollView>
{/* The other UI components of application in your tree */}
</ScrollView>
Expand Down
1 change: 1 addition & 0 deletions example/src/screens/Examples/InteractiveKeyboard/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -91,6 +91,7 @@ function InteractiveKeyboard({ navigation }: Props) {
testID="chat.gesture"
interpolator={interpolator}
showOnSwipeUp
offset={50}
>
<Reanimated.ScrollView
testID="chat.scroll"
Expand Down
6 changes: 5 additions & 1 deletion src/specs/KeyboardGestureAreaNativeComponent.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,12 +2,16 @@ import codegenNativeComponent from "react-native/Libraries/Utilities/codegenNati

import type { HostComponent } from "react-native";
import type { ViewProps } from "react-native/Libraries/Components/View/ViewPropTypes";
import type { WithDefault } from "react-native/Libraries/Types/CodegenTypes";
import type {
Double,
WithDefault,
} from "react-native/Libraries/Types/CodegenTypes";

export interface NativeProps extends ViewProps {
interpolator?: WithDefault<"linear" | "ios", "linear">;
showOnSwipeUp?: boolean;
enableSwipeToDismiss?: boolean;
offset?: Double;
}

export default codegenNativeComponent<NativeProps>("KeyboardGestureArea", {
Expand Down
4 changes: 4 additions & 0 deletions src/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -101,6 +101,10 @@ export type KeyboardGestureAreaProps = {
* Defaults to `true`.
*/
enableSwipeToDismiss?: boolean;
/**
* Extra distance to the keyboard.
*/
offset?: number;
} & ViewProps;

export type Direction = "next" | "prev" | "current";
Expand Down

0 comments on commit 2f901a9

Please sign in to comment.