Skip to content
Merged
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 @@ -8,6 +8,7 @@ import com.facebook.react.module.model.ReactModuleInfoProvider
import com.facebook.react.uimanager.ViewManager
import com.reactnativekeyboardcontroller.modules.KeyboardControllerModuleImpl
import com.reactnativekeyboardcontroller.modules.statusbar.StatusBarManagerCompatModuleImpl
import java.com.reactnativekeyboardcontroller.ClippingScrollViewDecoratorViewManager

class KeyboardControllerPackage : BaseReactPackage() {
override fun getModule(
Expand Down Expand Up @@ -58,5 +59,6 @@ class KeyboardControllerPackage : BaseReactPackage() {
KeyboardGestureAreaViewManager(),
OverKeyboardViewManager(),
KeyboardBackgroundViewManager(),
ClippingScrollViewDecoratorViewManager(),
)
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
package java.com.reactnativekeyboardcontroller

import com.facebook.react.uimanager.ThemedReactContext
import com.facebook.react.uimanager.ViewGroupManager
import com.facebook.react.uimanager.ViewManagerDelegate
import com.facebook.react.viewmanagers.ClippingScrollViewDecoratorViewManagerDelegate
import com.facebook.react.viewmanagers.ClippingScrollViewDecoratorViewManagerInterface
import com.reactnativekeyboardcontroller.managers.ClippingScrollViewDecoratorViewManagerImpl
import com.reactnativekeyboardcontroller.views.ClippingScrollViewDecoratorView

class ClippingScrollViewDecoratorViewManager :
ViewGroupManager<ClippingScrollViewDecoratorView>(),
ClippingScrollViewDecoratorViewManagerInterface<ClippingScrollViewDecoratorView> {
private val manager = ClippingScrollViewDecoratorViewManagerImpl()
private val mDelegate = ClippingScrollViewDecoratorViewManagerDelegate(this)

override fun getDelegate(): ViewManagerDelegate<ClippingScrollViewDecoratorView> = mDelegate

override fun getName(): String = ClippingScrollViewDecoratorViewManagerImpl.NAME

override fun createViewInstance(context: ThemedReactContext): ClippingScrollViewDecoratorView =
manager.createViewInstance(context)

override fun setContentInsetBottom(
view: ClippingScrollViewDecoratorView?,
value: Double,
) {
view?.setContentInsetBottom(value)
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
package com.reactnativekeyboardcontroller.managers

import com.facebook.react.uimanager.ThemedReactContext
import com.reactnativekeyboardcontroller.views.ClippingScrollViewDecoratorView

class ClippingScrollViewDecoratorViewManagerImpl {
fun createViewInstance(reactContext: ThemedReactContext): ClippingScrollViewDecoratorView =
ClippingScrollViewDecoratorView(reactContext)

companion object {
const val NAME = "ClippingScrollViewDecoratorView"
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
package com.reactnativekeyboardcontroller.views

import android.annotation.SuppressLint
import android.widget.ScrollView
import com.facebook.react.uimanager.ThemedReactContext
import com.facebook.react.views.view.ReactViewGroup
import com.reactnativekeyboardcontroller.extensions.px

@SuppressLint("ViewConstructor")
class ClippingScrollViewDecoratorView(
val reactContext: ThemedReactContext,
) : ReactViewGroup(reactContext) {
private var insetBottom = 0.0

override fun onAttachedToWindow() {
super.onAttachedToWindow()

decorateScrollView()
}

fun setContentInsetBottom(value: Double) {
insetBottom = value
decorateScrollView()
}

private fun decorateScrollView() {
val scrollView = getChildAt(0) as? ScrollView

scrollView?.clipToPadding = false
scrollView?.setPadding(
scrollView.paddingLeft,
scrollView.paddingTop,
scrollView.paddingRight,
insetBottom.toFloat().px.toInt(),
)
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
package java.com.reactnativekeyboardcontroller

import com.facebook.react.uimanager.ThemedReactContext
import com.facebook.react.uimanager.ViewGroupManager
import com.facebook.react.uimanager.annotations.ReactProp
import com.reactnativekeyboardcontroller.managers.ClippingScrollViewDecoratorViewManagerImpl
import com.reactnativekeyboardcontroller.views.ClippingScrollViewDecoratorView

class ClippingScrollViewDecoratorViewManager : ViewGroupManager<ClippingScrollViewDecoratorView>() {
private val manager = ClippingScrollViewDecoratorViewManagerImpl()

override fun getName(): String = ClippingScrollViewDecoratorViewManagerImpl.NAME

override fun createViewInstance(reactContext: ThemedReactContext): ClippingScrollViewDecoratorView =
manager.createViewInstance(reactContext)

@ReactProp(name = "contentInsetBottom")
fun setContentInsetBottom(
view: ClippingScrollViewDecoratorView,
value: Double,
) {
view.setContentInsetBottom(value)
}
}
5 changes: 5 additions & 0 deletions src/bindings.native.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import { NativeEventEmitter, Platform } from "react-native";

import type {
ClippingScrollViewProps,
FocusedInputEventsModule,
KeyboardBackgroundViewProps,
KeyboardControllerNativeModule,
Expand Down Expand Up @@ -71,3 +72,7 @@ export const RCTKeyboardExtender: React.FC<KeyboardExtenderProps> =
Platform.OS === "ios"
? require("./specs/KeyboardExtenderNativeComponent").default
: ({ children }: KeyboardExtenderProps) => children;
export const ClippingScrollView: React.FC<KeyboardBackgroundViewProps> =
Platform.OS === "android"
? require("./specs/ClippingScrollViewDecoratorViewNativeComponent").default
: ({ children }: ClippingScrollViewProps) => children;
8 changes: 8 additions & 0 deletions src/bindings.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import { View } from "react-native";

import type {
ClippingScrollViewProps,
FocusedInputEventsModule,
KeyboardBackgroundViewProps,
KeyboardControllerNativeModule,
Expand Down Expand Up @@ -82,3 +83,10 @@ export const KeyboardBackgroundView =
*/
export const RCTKeyboardExtender =
View as unknown as React.FC<KeyboardExtenderProps>;
/**
* A decorator that will clip the content of the `ScrollView`. It helps to simulate `contentInset` behavior on Android.
* Supports only `bottom` property (`paddingBottom` is not supported property of `ScrollView.style`).
* Using this component we can modify bottom inset without having a fake view.
*/
export const ClippingScrollView =
View as unknown as React.FC<ClippingScrollViewProps>;
17 changes: 17 additions & 0 deletions src/specs/ClippingScrollViewDecoratorViewNativeComponent.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
import { codegenNativeComponent } from "react-native";

import type { HostComponent } from "react-native";
import type { ViewProps } from "react-native";
import type { Double } from "react-native/Libraries/Types/CodegenTypes";

export interface NativeProps extends ViewProps {
contentInsetBottom: Double;
}

export default codegenNativeComponent<NativeProps>(
"ClippingScrollViewDecoratorView",
{
interfaceOnly: true,
excludedPlatforms: ["iOS"],
},
) as HostComponent<NativeProps>;
6 changes: 6 additions & 0 deletions src/types/views.ts
Original file line number Diff line number Diff line change
Expand Up @@ -49,3 +49,9 @@ export type KeyboardExtenderProps = PropsWithChildren<{
/** Controls whether this `KeyboardExtender` instance should take an effect. Default is `true`. */
enabled?: boolean;
}>;
export type ClippingScrollViewProps = PropsWithChildren<
ViewProps & {
/** An additional space that gets applied to the bottom of the `ScrollView` (inside a scrollable content). Default is `0`. */
contentInsetBottom?: number;
}
>;
Loading