Skip to content

Commit

Permalink
feat: react on StatusBar.translucent changes (#528)
Browse files Browse the repository at this point in the history
## 📜 Description

Give an ability to control `translucent` from `StatusBar` component.

## 💡 Motivation and Context

The original decision to ignore `translucent` property was introduced in
#30

The problem is that we already have `translucent` `StatusBar` and we can
only mimicrate `non-translucent` by adding additional margin. The
solution with static `statusBarTranslucent` property was added
#15
and till now we didn't have an ability to manage `translucent` property
dynamically

The new functionality was requested in
#526

To implement new functionality I am discovering `edge-to-edge` view
(managed by `KeyboardProvider`) and conditionally add remove padding,
re-setup listener and re-request insets update.

Closes
#526

## 📢 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 -->

### JS

- add `vendor` to prettier ignore;
- add `StatusBar` to root screen;
- add new dynamic props to StatusBar example (`translucent`) + module
management;
- add testID to StatusBar example screen;

### E2E

- updated assets on Android;

### Android

- added new `ReactContext` extension file and move `.rootView` and
`.content` extensions there;
- implemented `setTranslucent` method in `StatusBarCompatModule`;
- assign `TAG` to `EdgeToEdgeView`;

## 🤔 How Has This Been Tested?

Tested manually on:
- Pixel 3a (API 33, emulator);
- Pixel 7 Pro (API 34, real device);
- (Xiaomi Redmi Note 5 Pro, API 28);

## 📸 Screenshots (if appropriate):

|Before|After|
|-------|-----|
|<video
src="https://github.com/user-attachments/assets/e2b5e28f-6038-4d09-8c9e-2ef7d5015395">|<video
src="https://github.com/user-attachments/assets/e089f3a4-f2bd-4e4a-9d32-0804c88ed2f4">|

## 📝 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 1bce3c8 commit 265b93c
Show file tree
Hide file tree
Showing 66 changed files with 85 additions and 26 deletions.
1 change: 1 addition & 0 deletions .prettierignore
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ docs/.docusaurus/
docs/build/
lib/
node_modules/
vendor/

*.lottie.json

Expand Down
8 changes: 7 additions & 1 deletion FabricExample/src/App.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import "react-native-gesture-handler";

import { NavigationContainer } from "@react-navigation/native";
import * as React from "react";
import { ActivityIndicator, StyleSheet } from "react-native";
import { ActivityIndicator, StatusBar, StyleSheet } from "react-native";
import { GestureHandlerRootView } from "react-native-gesture-handler";
import { KeyboardProvider } from "react-native-keyboard-controller";
import {
Expand Down Expand Up @@ -42,6 +42,12 @@ export default function App() {
<GestureHandlerRootView style={styles.root}>
<KeyboardProvider statusBarTranslucent>
<NavigationContainer linking={linking} fallback={spinner}>
<StatusBar
backgroundColor={"#FFFFFF00"}
translucent
animated
barStyle={"dark-content"}
/>
<RootStack />
</NavigationContainer>
</KeyboardProvider>
Expand Down
19 changes: 18 additions & 1 deletion FabricExample/src/screens/Examples/StatusBar/index.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import React, { useState } from "react";
import { Button, StatusBar, View } from "react-native";
import { useKeyboardController } from "react-native-keyboard-controller";

import KeyboardAnimationTemplate from "../../../components/KeyboardAnimation";
import { randomColor } from "../../../utils";
Expand All @@ -11,6 +12,8 @@ export default function StatusBarManipulation() {
const [barStyle, setBarStyle] = useState<StatusBarStyle>("light-content");
const [hidden, setHidden] = useState(false);
const [animated, setAnimated] = useState(true);
const [translucent, setTranslucent] = useState(true);
const { setEnabled, enabled } = useKeyboardController();

return (
<View style={{ flex: 1, backgroundColor: "pink" }}>
Expand All @@ -19,20 +22,23 @@ export default function StatusBarManipulation() {
barStyle={barStyle}
hidden={hidden}
animated={animated}
translucent
translucent={translucent}
/>
<KeyboardAnimationTemplate />
<Button
title={`Set ${hidden ? "shown" : "hidden"}`}
onPress={() => setHidden(!hidden)}
testID="button.hidden"
/>
<Button
title="Update color"
onPress={() => setColor(`${randomColor()}`)}
testID="button.color"
/>
<Button
title={`Set ${!animated ? "" : "not"} animated`}
onPress={() => setAnimated(!animated)}
testID="button.animated"
/>
<Button
title={`Change ${barStyle}`}
Expand All @@ -41,6 +47,17 @@ export default function StatusBarManipulation() {
barStyle === "light-content" ? "dark-content" : "light-content",
)
}
testID="button.bar_style"
/>
<Button
title={`Set ${!translucent ? "" : "not"} translucent`}
onPress={() => setTranslucent(!translucent)}
testID="button.translucent"
/>
<Button
title={`${enabled ? "Disable" : "Enable"} module`}
onPress={() => setEnabled(!enabled)}
testID="button.enabled"
/>
</View>
);
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
package com.reactnativekeyboardcontroller.extensions

import android.view.View
import android.view.ViewGroup
import com.facebook.react.bridge.ReactContext

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

val ReactContext.content: ViewGroup?
get() = this.currentActivity?.window?.decorView?.rootView?.findViewById(
androidx.appcompat.R.id.action_bar_root,
)
Original file line number Diff line number Diff line change
@@ -1,8 +1,6 @@
package com.reactnativekeyboardcontroller.extensions

import android.util.Log
import android.view.View
import android.view.ViewGroup
import com.facebook.react.bridge.ReactContext
import com.facebook.react.bridge.WritableMap
import com.facebook.react.modules.core.DeviceEventManagerModule
Expand All @@ -12,14 +10,6 @@ import com.facebook.react.uimanager.events.Event
import com.facebook.react.uimanager.events.EventDispatcher
import com.reactnativekeyboardcontroller.listeners.WindowDimensionListener

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

val ThemedReactContext.content: ViewGroup?
get() = this.currentActivity?.window?.decorView?.rootView?.findViewById(
androidx.appcompat.R.id.action_bar_root,
)

fun ThemedReactContext.setupWindowDimensionsListener() {
WindowDimensionListener(this)
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,8 @@ import androidx.core.view.WindowInsetsCompat
import androidx.core.view.WindowInsetsControllerCompat
import com.facebook.react.bridge.ReactApplicationContext
import com.facebook.react.bridge.UiThreadUtil
import com.reactnativekeyboardcontroller.extensions.rootView
import com.reactnativekeyboardcontroller.views.EdgeToEdgeReactViewGroup

private val TAG = StatusBarManagerCompatModuleImpl::class.qualifiedName

Expand Down Expand Up @@ -50,19 +52,11 @@ class StatusBarManagerCompatModuleImpl(private val mReactContext: ReactApplicati
}
}

@Suppress("detekt:UnusedParameter")
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.
UiThreadUtil.runOnUiThread {
val view = mReactContext.rootView?.findViewWithTag<EdgeToEdgeReactViewGroup>(EdgeToEdgeReactViewGroup.VIEW_TAG)
view?.forceStatusBarTranslucent(translucent)
}
}

fun setStyle(style: String) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,7 @@ class EdgeToEdgeReactViewGroup(private val reactContext: ThemedReactContext) : R

init {
reactContext.setupWindowDimensionsListener()
tag = VIEW_TAG
}

// region View life cycles
Expand Down Expand Up @@ -200,4 +201,18 @@ class EdgeToEdgeReactViewGroup(private val reactContext: ThemedReactContext) : R
}
}
// endregion

// region external methods
fun forceStatusBarTranslucent(isStatusBarTranslucent: Boolean) {
if (active && this.isStatusBarTranslucent != isStatusBarTranslucent) {
this.isStatusBarTranslucent = isStatusBarTranslucent
this.setupWindowInsets()
this.requestApplyInsetsWhenAttached()
}
}
// endregion

companion object {
val VIEW_TAG = EdgeToEdgeReactViewGroup::class.simpleName
}
}
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file modified e2e/kit/assets/android/e2e_emulator_28/ModalKeyboardClosed.png
Binary file modified e2e/kit/assets/android/e2e_emulator_28/ModalKeyboardOpened.png
Binary file modified e2e/kit/assets/android/e2e_emulator_31/ModalKeyboardClosed.png
Binary file modified e2e/kit/assets/android/e2e_emulator_31/ModalKeyboardOpened.png
Binary file modified e2e/kit/assets/android/e2e_emulator_31/ToolbarKeyboardClosed.png
8 changes: 7 additions & 1 deletion example/src/App.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import "react-native-gesture-handler";

import { NavigationContainer } from "@react-navigation/native";
import * as React from "react";
import { ActivityIndicator, StyleSheet } from "react-native";
import { ActivityIndicator, StatusBar, StyleSheet } from "react-native";
import { GestureHandlerRootView } from "react-native-gesture-handler";
import { KeyboardProvider } from "react-native-keyboard-controller";
import {
Expand Down Expand Up @@ -42,6 +42,12 @@ export default function App() {
<GestureHandlerRootView style={styles.root}>
<KeyboardProvider statusBarTranslucent>
<NavigationContainer linking={linking} fallback={spinner}>
<StatusBar
backgroundColor={"#FFFFFF00"}
translucent
animated
barStyle={"dark-content"}
/>
<RootStack />
</NavigationContainer>
</KeyboardProvider>
Expand Down
19 changes: 18 additions & 1 deletion example/src/screens/Examples/StatusBar/index.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import React, { useState } from "react";
import { Button, StatusBar, View } from "react-native";
import { useKeyboardController } from "react-native-keyboard-controller";

import KeyboardAnimationTemplate from "../../../components/KeyboardAnimation";
import { randomColor } from "../../../utils";
Expand All @@ -11,6 +12,8 @@ export default function StatusBarManipulation() {
const [barStyle, setBarStyle] = useState<StatusBarStyle>("light-content");
const [hidden, setHidden] = useState(false);
const [animated, setAnimated] = useState(true);
const [translucent, setTranslucent] = useState(true);
const { setEnabled, enabled } = useKeyboardController();

return (
<View style={{ flex: 1, backgroundColor: "pink" }}>
Expand All @@ -19,20 +22,23 @@ export default function StatusBarManipulation() {
barStyle={barStyle}
hidden={hidden}
animated={animated}
translucent
translucent={translucent}
/>
<KeyboardAnimationTemplate />
<Button
title={`Set ${hidden ? "shown" : "hidden"}`}
onPress={() => setHidden(!hidden)}
testID="button.hidden"
/>
<Button
title="Update color"
onPress={() => setColor(`${randomColor()}`)}
testID="button.color"
/>
<Button
title={`Set ${!animated ? "" : "not"} animated`}
onPress={() => setAnimated(!animated)}
testID="button.animated"
/>
<Button
title={`Change ${barStyle}`}
Expand All @@ -41,6 +47,17 @@ export default function StatusBarManipulation() {
barStyle === "light-content" ? "dark-content" : "light-content",
)
}
testID="button.bar_style"
/>
<Button
title={`Set ${!translucent ? "" : "not"} translucent`}
onPress={() => setTranslucent(!translucent)}
testID="button.translucent"
/>
<Button
title={`${enabled ? "Disable" : "Enable"} module`}
onPress={() => setEnabled(!enabled)}
testID="button.enabled"
/>
</View>
);
Expand Down

0 comments on commit 265b93c

Please sign in to comment.