diff --git a/android/src/main/java/com/reactnativekeyboardcontroller/KeyboardControllerPackage.kt b/android/src/main/java/com/reactnativekeyboardcontroller/KeyboardControllerPackage.kt index f3965e114..68d8c8a7c 100644 --- a/android/src/main/java/com/reactnativekeyboardcontroller/KeyboardControllerPackage.kt +++ b/android/src/main/java/com/reactnativekeyboardcontroller/KeyboardControllerPackage.kt @@ -7,7 +7,7 @@ import com.facebook.react.uimanager.ViewManager class KeyboardControllerPackage : ReactPackage { override fun createNativeModules(reactContext: ReactApplicationContext): List { - return listOf(KeyboardControllerModule(reactContext)) + return listOf(KeyboardControllerModule(reactContext), StatusBarManagerCompatModule(reactContext)) } override fun createViewManagers(reactContext: ReactApplicationContext): List> { diff --git a/android/src/main/java/com/reactnativekeyboardcontroller/KeyboardControllerViewManager.kt b/android/src/main/java/com/reactnativekeyboardcontroller/KeyboardControllerViewManager.kt index 670fd215b..ac85b9815 100644 --- a/android/src/main/java/com/reactnativekeyboardcontroller/KeyboardControllerViewManager.kt +++ b/android/src/main/java/com/reactnativekeyboardcontroller/KeyboardControllerViewManager.kt @@ -20,6 +20,8 @@ class KeyboardControllerViewManager(reactContext: ReactApplicationContext) : Rea override fun createViewInstance(reactContext: ThemedReactContext): ReactViewGroup { val view = EdgeToEdgeReactViewGroup(reactContext) + val window = mReactContext.currentActivity!!.window + val decorView = window.decorView ViewCompat.setOnApplyWindowInsetsListener(view) { v, insets -> val content = @@ -35,7 +37,7 @@ class KeyboardControllerViewManager(reactContext: ReactApplicationContext) : Rea } ViewCompat.setWindowInsetsAnimationCallback( - reactContext.currentActivity!!.window!!.decorView, + decorView, TranslateDeferringInsetsAnimationCallback( view = view, persistentInsetTypes = WindowInsetsCompat.Type.systemBars(), diff --git a/android/src/main/java/com/reactnativekeyboardcontroller/StatusBarManagerCompatModule.kt b/android/src/main/java/com/reactnativekeyboardcontroller/StatusBarManagerCompatModule.kt new file mode 100644 index 000000000..3ce86b94d --- /dev/null +++ b/android/src/main/java/com/reactnativekeyboardcontroller/StatusBarManagerCompatModule.kt @@ -0,0 +1,81 @@ +package com.reactnativekeyboardcontroller + +import android.animation.ArgbEvaluator +import android.animation.ValueAnimator +import android.os.Build +import androidx.annotation.RequiresApi +import androidx.core.view.WindowInsetsCompat +import androidx.core.view.WindowInsetsControllerCompat +import com.facebook.react.bridge.ReactApplicationContext +import com.facebook.react.bridge.ReactContextBaseJavaModule +import com.facebook.react.bridge.ReactMethod +import com.facebook.react.bridge.UiThreadUtil + +class StatusBarManagerCompatModule(private val mReactContext: ReactApplicationContext) : ReactContextBaseJavaModule(mReactContext) { + private var controller: WindowInsetsControllerCompat? = null + + override fun getName(): String = "StatusBarManagerCompat" + + @ReactMethod + private fun setHidden(hidden: Boolean) { + UiThreadUtil.runOnUiThread { + if (hidden) { + getController()?.hide(WindowInsetsCompat.Type.statusBars()) + } else { + getController()?.show(WindowInsetsCompat.Type.statusBars()) + } + } + } + + @RequiresApi(Build.VERSION_CODES.LOLLIPOP) + @ReactMethod + private fun setColor(color: Int, animated: Boolean) { + UiThreadUtil.runOnUiThread { + val window = mReactContext.currentActivity!!.window + + if (animated) { + val curColor: Int = window.statusBarColor + val colorAnimation = ValueAnimator.ofObject(ArgbEvaluator(), curColor, color) + colorAnimation.addUpdateListener { animator -> + window.statusBarColor = animator.animatedValue as Int + } + colorAnimation.setDuration(300).startDelay = 0 + colorAnimation.start() + } else { + window.statusBarColor = color + } + } + } + + @ReactMethod + private fun setTranslucent(translucent: Boolean) { + // the status bar is translucent by default (once you wrapped App in Provider, + // and EdgeToEdgeReactViewGroup has been mounted and called + // `setDecorFitsSystemWindows(window, false)`. By default this library applies default padding + // which equal to StatusBar height, so it will have a default RN app behavior. Though once you + // need to set StatusBar as translucent, you will need to use `statusBarTranslucent` prop on + // `KeyboardProvider` (it will preventing of applying additional padding, and status bar will be + // translucent. Though it's important to note, that this value is not reactive (i. e. if you change + // `statusBarTranslucent` in runtime it will not have any effect. Just theoretically I could make + // it reactive, but I know, that most of apps or don't use StatusBar translucency at all or they are + // specifying it for entire app, so I don't see a lot of sense to make it reactive as of now. If your + // app requires to dynamically manage it - just shoot an issue and I will try to add a support fot that. + } + + @ReactMethod + private fun setStyle(style: String) { + UiThreadUtil.runOnUiThread { + getController()?.isAppearanceLightStatusBars = style == "dark-content" + } + } + + private fun getController(): WindowInsetsControllerCompat? { + if (this.controller == null) { + val window = mReactContext.currentActivity!!.window + + this.controller = WindowInsetsControllerCompat(window, window.decorView) + } + + return this.controller + } +} diff --git a/example/android/app/src/main/res/values/styles.xml b/example/android/app/src/main/res/values/styles.xml index 7ba83a2ad..5cc1a3438 100644 --- a/example/android/app/src/main/res/values/styles.xml +++ b/example/android/app/src/main/res/values/styles.xml @@ -4,6 +4,11 @@ diff --git a/example/src/App.tsx b/example/src/App.tsx index 1dfc20140..c8cf05ff8 100644 --- a/example/src/App.tsx +++ b/example/src/App.tsx @@ -13,7 +13,7 @@ import RootStack from './navigation/RootStack'; export default function App() { return ( - + diff --git a/example/src/components/KeyboardAnimation/index.tsx b/example/src/components/KeyboardAnimation/index.tsx new file mode 100644 index 000000000..3c485aaa4 --- /dev/null +++ b/example/src/components/KeyboardAnimation/index.tsx @@ -0,0 +1,61 @@ +import React from 'react'; +import { Animated, TextInput, View } from 'react-native'; +import { + useKeyboardAnimation, + useKeyboardAnimationReplica, +} from 'react-native-keyboard-controller'; +import styles from './styles'; + +export default function KeyboardAnimation() { + const { height, progress } = useKeyboardAnimation(); + const { height: heightReplica } = useKeyboardAnimationReplica(); + + return ( + + + + + + + + + ); +} diff --git a/example/src/components/KeyboardAnimation/styles.ts b/example/src/components/KeyboardAnimation/styles.ts new file mode 100644 index 000000000..625438f4c --- /dev/null +++ b/example/src/components/KeyboardAnimation/styles.ts @@ -0,0 +1,17 @@ +import { StyleSheet } from 'react-native'; + +export default StyleSheet.create({ + container: { + flex: 1, + alignItems: 'center', + justifyContent: 'center', + }, + box: { + width: 60, + height: 60, + marginVertical: 20, + }, + row: { + flexDirection: 'row', + }, +}); diff --git a/example/src/constants/screenNames.ts b/example/src/constants/screenNames.ts index 4b8623738..59534499b 100644 --- a/example/src/constants/screenNames.ts +++ b/example/src/constants/screenNames.ts @@ -3,6 +3,7 @@ export enum ScreenNames { REANIMATED_CHAT = 'REANIMATED_CHAT', EVENTS = 'EVENTS', AWARE_SCROLL_VIEW = 'AWARE_SCROLL_VIEW', + STATUS_BAR = 'STATUS_BAR', EXAMPLES_STACK = 'EXAMPLES_STACK', EXAMPLES = 'EXAMPLES', } diff --git a/example/src/navigation/ExamplesStack/index.tsx b/example/src/navigation/ExamplesStack/index.tsx index 1b0846fde..fe2cd85ac 100644 --- a/example/src/navigation/ExamplesStack/index.tsx +++ b/example/src/navigation/ExamplesStack/index.tsx @@ -7,12 +7,14 @@ import KeyboardAnimation from '../../screens/Examples/KeyboardAnimation'; import ReanimatedChat from '../../screens/Examples/ReanimatedChat'; import Events from '../../screens/Examples/Events'; import AwareScrollView from '../../screens/Examples/AwareScrollView'; +import StatusBar from '../../screens/Examples/StatusBar'; type ExamplesStackParamList = { [ScreenNames.ANIMATED_EXAMPLE]: undefined; [ScreenNames.REANIMATED_CHAT]: undefined; [ScreenNames.EVENTS]: undefined; [ScreenNames.AWARE_SCROLL_VIEW]: undefined; + [ScreenNames.STATUS_BAR]: undefined; }; const Stack = createStackNavigator(); @@ -30,6 +32,10 @@ const options = { [ScreenNames.AWARE_SCROLL_VIEW]: { title: 'Aware scroll view', }, + [ScreenNames.STATUS_BAR]: { + headerShown: false, + title: 'Status bar manipulation', + }, }; const ExamplesStack = () => ( @@ -54,6 +60,11 @@ const ExamplesStack = () => ( component={AwareScrollView} options={options[ScreenNames.AWARE_SCROLL_VIEW]} /> + ); diff --git a/example/src/screens/Examples/AwareScrollView/index.tsx b/example/src/screens/Examples/AwareScrollView/index.tsx index 47e06b7ff..87537e5d6 100644 --- a/example/src/screens/Examples/AwareScrollView/index.tsx +++ b/example/src/screens/Examples/AwareScrollView/index.tsx @@ -7,10 +7,7 @@ import Reanimated, { useAnimatedStyle, useSharedValue, } from 'react-native-reanimated'; - -function randomColor() { - return '#' + Math.random().toString(16).slice(-6); -} +import { randomColor } from '../../../utils'; const screenHeight = Dimensions.get('window').height; diff --git a/example/src/screens/Examples/KeyboardAnimation/index.tsx b/example/src/screens/Examples/KeyboardAnimation/index.tsx index 3c485aaa4..7da048df5 100644 --- a/example/src/screens/Examples/KeyboardAnimation/index.tsx +++ b/example/src/screens/Examples/KeyboardAnimation/index.tsx @@ -1,61 +1,7 @@ import React from 'react'; -import { Animated, TextInput, View } from 'react-native'; -import { - useKeyboardAnimation, - useKeyboardAnimationReplica, -} from 'react-native-keyboard-controller'; -import styles from './styles'; -export default function KeyboardAnimation() { - const { height, progress } = useKeyboardAnimation(); - const { height: heightReplica } = useKeyboardAnimationReplica(); +import KeyboardAnimationTemplate from '../../../components/KeyboardAnimation'; - return ( - - - - - - - - - ); +export default function KeyboardAnimation() { + return ; } diff --git a/example/src/screens/Examples/Main/constants.ts b/example/src/screens/Examples/Main/constants.ts index b97c49e3a..f2ff92032 100644 --- a/example/src/screens/Examples/Main/constants.ts +++ b/example/src/screens/Examples/Main/constants.ts @@ -14,4 +14,9 @@ export const examples: Example[] = [ info: ScreenNames.AWARE_SCROLL_VIEW, icons: '🤓', }, + { + title: 'Status Bar', + info: ScreenNames.STATUS_BAR, + icons: '🔋', + }, ]; diff --git a/example/src/screens/Examples/StatusBar/index.tsx b/example/src/screens/Examples/StatusBar/index.tsx new file mode 100644 index 000000000..96abe07b9 --- /dev/null +++ b/example/src/screens/Examples/StatusBar/index.tsx @@ -0,0 +1,45 @@ +import React, { useState } from 'react'; +import { View, StatusBar, StatusBarStyle, Button } from 'react-native'; + +import KeyboardAnimationTemplate from '../../../components/KeyboardAnimation'; +import { randomColor } from '../../../utils'; + +export default function StatusBarManipulation() { + const [color, setColor] = useState('#00FF0000'); + const [barStyle, setBarStyle] = useState('light-content'); + const [hidden, setHidden] = useState(false); + const [animated, setAnimated] = useState(true); + + return ( + +