Skip to content

Commit

Permalink
Merge pull request #168 from jpudysz/feature/colors
Browse files Browse the repository at this point in the history
feat: add api to change status bar and navigation bar colors
  • Loading branch information
jpudysz authored Apr 5, 2024
2 parents 332d5e6 + a1cca7f commit b60accd
Show file tree
Hide file tree
Showing 19 changed files with 304 additions and 33 deletions.
3 changes: 2 additions & 1 deletion .eslintrc
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
"@typescript-eslint/no-empty-interface": 0,
"no-duplicate-imports": 0,
"functional/immutable-data": 0,
"no-underscore-dangle": 0
"no-underscore-dangle": 0,
"max-lines": 0
}
}
20 changes: 20 additions & 0 deletions android/src/main/cxx/cpp-adapter.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -99,6 +99,26 @@ Java_com_unistyles_UnistylesModule_nativeInstall(
env->DeleteLocalRef(cls);
});

unistylesRuntime->onSetNavigationBarColor([=](const std::string &color) {
jstring colorStr = env->NewStringUTF(color.c_str());
jclass cls = env->GetObjectClass(unistylesModule);
jmethodID methodId = env->GetMethodID(cls, "onSetNavigationBarColor", "(Ljava/lang/String;)V");

env->CallVoidMethod(unistylesModule, methodId, colorStr);
env->DeleteLocalRef(colorStr);
env->DeleteLocalRef(cls);
});

unistylesRuntime->onSetStatusBarColor([=](const std::string &color) {
jstring colorStr = env->NewStringUTF(color.c_str());
jclass cls = env->GetObjectClass(unistylesModule);
jmethodID methodId = env->GetMethodID(cls, "onSetStatusBarColor", "(Ljava/lang/String;)V");

env->CallVoidMethod(unistylesModule, methodId, colorStr);
env->DeleteLocalRef(colorStr);
env->DeleteLocalRef(cls);
});

jsi::Object hostObject = jsi::Object::createFromHostObject(*runtime, unistylesRuntime);

runtime->global().setProperty(*runtime, "__UNISTYLES__", std::move(hostObject));
Expand Down
3 changes: 3 additions & 0 deletions android/src/main/java/com/unistyles/Platform.kt
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,9 @@ import com.facebook.react.bridge.ReactApplicationContext
class Platform(reactApplicationContext: ReactApplicationContext) {
private val config: UnistylesConfig = UnistylesConfig(reactApplicationContext)

var defaultNavigationBarColor: Int? = null
var defaultStatusBarColor: Int? = null

fun hasNewLayoutConfig(): Boolean {
return this.config.hasNewLayoutConfig()
}
Expand Down
29 changes: 29 additions & 0 deletions android/src/main/java/com/unistyles/UnistylesModule.kt
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import android.content.BroadcastReceiver
import android.content.Context
import android.content.Intent
import android.content.IntentFilter
import android.graphics.Color
import android.os.Handler
import android.os.Looper
import android.util.Log
Expand Down Expand Up @@ -235,6 +236,34 @@ class UnistylesModule(reactContext: ReactApplicationContext) : ReactContextBaseJ
.emit("__unistylesOnChange", body)
}

private fun onSetNavigationBarColor(color: String) {
val activity = currentActivity ?: return

if (platform.defaultNavigationBarColor == null) {
platform.defaultNavigationBarColor = activity.window.navigationBarColor
}

try {
activity.window.navigationBarColor = if (color == "") platform.defaultNavigationBarColor!! else Color.parseColor(color)
} catch (_: Exception) {
Log.d("Unistyles", "Failed to set navigation bar color: $color")
}
}

private fun onSetStatusBarColor(color: String) {
val activity = currentActivity ?: return

if (platform.defaultStatusBarColor == null) {
platform.defaultStatusBarColor = activity.window.statusBarColor
}

try {
activity.window.statusBarColor = if (color == "") platform.defaultStatusBarColor!! else Color.parseColor(color)
} catch (_: Exception) {
Log.d("Unistyles", "Failed to set status bar color: $color")
}
}

@ReactMethod
fun addListener(eventName: String?) = Unit

Expand Down
28 changes: 28 additions & 0 deletions cxx/UnistylesRuntime.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -250,18 +250,46 @@ jsi::Value UnistylesRuntime::get(jsi::Runtime& runtime, const jsi::PropNameID& p

if (propName == "statusBar") {
auto statusBar = jsi::Object(runtime);
auto setStatusBarColorFunction = jsi::Function::createFromHostFunction(runtime,
jsi::PropNameID::forAscii(runtime, "setColor"),
1,
[this](jsi::Runtime &runtime, const jsi::Value &thisVal, const jsi::Value *arguments, size_t count) -> jsi::Value {
std::string color = arguments[0].asString(runtime).utf8(runtime);

if (this->onSetStatusBarColorCallback.has_value()) {
this->onSetStatusBarColorCallback.value()(color);
}

return jsi::Value::undefined();
}
);

statusBar.setProperty(runtime, "width", this->statusBar.width);
statusBar.setProperty(runtime, "height", this->statusBar.height);
statusBar.setProperty(runtime, "setColor", setStatusBarColorFunction);

return statusBar;
}

if (propName == "navigationBar") {
auto navigationBarValue = jsi::Object(runtime);
auto setNavigationBarColorFunction = jsi::Function::createFromHostFunction(runtime,
jsi::PropNameID::forAscii(runtime, "setColor"),
1,
[this](jsi::Runtime &runtime, const jsi::Value &thisVal, const jsi::Value *arguments, size_t count) -> jsi::Value {
std::string color = arguments[0].asString(runtime).utf8(runtime);

if (this->onSetStatusBarColorCallback.has_value()) {
this->onSetNavigationBarColorCallback.value()(color);
}

return jsi::Value::undefined();
}
);

navigationBarValue.setProperty(runtime, "width", this->navigationBar.width);
navigationBarValue.setProperty(runtime, "height", this->navigationBar.height);
navigationBarValue.setProperty(runtime, "setColor", setNavigationBarColorFunction);

return navigationBarValue;
}
Expand Down
25 changes: 18 additions & 7 deletions cxx/UnistylesRuntime.h
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
#include <jsi/jsi.h>
#include <vector>
#include <map>
#include <optional>

using namespace facebook;

Expand Down Expand Up @@ -35,14 +36,16 @@ class JSI_EXPORT UnistylesRuntime : public jsi::HostObject {
std::function<void(std::string breakpoint, std::string orientation, Dimensions& screen, Dimensions& statusBar, Insets& insets, Dimensions& navigationBar)> onLayoutChangeCallback;
std::function<void(std::string)> onContentSizeCategoryChangeCallback;
std::function<void()> onPluginChangeCallback;

std::optional<std::function<void(std::string)>> onSetStatusBarColorCallback;
std::optional<std::function<void(std::string)>> onSetNavigationBarColorCallback;

Dimensions screen;
Dimensions statusBar;
Dimensions navigationBar;
Insets insets;
std::string colorScheme;
std::string contentSizeCategory;

public:
UnistylesRuntime(
Dimensions screen,
Expand All @@ -60,28 +63,36 @@ class JSI_EXPORT UnistylesRuntime : public jsi::HostObject {

bool hasAdaptiveThemes;
bool supportsAutomaticColorScheme;

std::string themeName;
std::string breakpoint;
std::vector<std::string> pluginNames;
std::vector<std::string> themes;
std::vector<std::pair<std::string, double>> sortedBreakpointPairs;

void onThemeChange(std::function<void(std::string)> callback) {
this->onThemeChangeCallback = callback;
}

void onLayoutChange(std::function<void(std::string breakpoint, std::string orientation, Dimensions& screen, Dimensions& statusBar, Insets& insets, Dimensions& navigationBar)> callback) {
this->onLayoutChangeCallback = callback;
}

void onPluginChange(std::function<void()> callback) {
this->onPluginChangeCallback = callback;
}

void onContentSizeCategoryChange(std::function<void(std::string)> callback) {
this->onContentSizeCategoryChangeCallback = callback;
}

void onSetStatusBarColor(std::function<void(std::string color)> callback) {
this->onSetStatusBarColorCallback = callback;
}

void onSetNavigationBarColor(std::function<void(std::string color)> callback) {
this->onSetNavigationBarColorCallback = callback;
}

jsi::Value get(jsi::Runtime&, const jsi::PropNameID& name) override;
void set(jsi::Runtime& runtime, const jsi::PropNameID& propNameId, const jsi::Value& value) override;
Expand Down
18 changes: 8 additions & 10 deletions docs/astro.config.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -65,7 +65,8 @@ export default defineConfig({
},
{
label: 'Theming',
link: '/reference/theming/'
link: '/reference/theming/',
badge: 'New'
},
{
label: 'useInitialTheme',
Expand All @@ -89,8 +90,7 @@ export default defineConfig({
},
{
label: 'Dimensions',
link: '/reference/dimensions/',
badge: 'New'
link: '/reference/dimensions/'
},
{
label: 'Unistyles Registry',
Expand All @@ -103,8 +103,7 @@ export default defineConfig({
},
{
label: 'Content size category',
link: '/reference/content-size-category/',
badge: 'New'
link: '/reference/content-size-category/'
},
{
label: 'Plugins',
Expand All @@ -124,8 +123,7 @@ export default defineConfig({
},
{
label: 'Testing',
link: '/reference/testing/',
badge: 'New'
link: '/reference/testing/'
},
{
label: 'Errors',
Expand Down Expand Up @@ -155,7 +153,8 @@ export default defineConfig({
items: [
{
label: 'All examples',
link: '/examples/all'
link: '/examples/all',
badge: 'New'
}
]
},
Expand All @@ -168,8 +167,7 @@ export default defineConfig({
},
{
label: 'Sponsors',
link: 'other/sponsors/',
badge: 'New'
link: 'other/sponsors/'
},
{
label: 'For Sponsors',
Expand Down
3 changes: 3 additions & 0 deletions docs/src/content/docs/examples/all.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ Collection of examples is hosted on GitHub, making it easy for you to browse, le
- Windows [link](/~https://github.com/jpudysz/react-native-unistyles/tree/main/examples/win)
- Expo Router [link](/~https://github.com/jpudysz/react-native-unistyles-expo-router)
- SSR + NextJS [link](/~https://github.com/jpudysz/react-native-unistyles-ssr-exmaple)
- Bare React Native + Bridgeless [link](/~https://github.com/jpudysz/react-native-unistyles/tree/main/examples/bare)

#### Theming

Expand All @@ -50,6 +51,7 @@ Collection of examples is hosted on GitHub, making it easy for you to browse, le

- Selected variant [link](/~https://github.com/jpudysz/react-native-unistyles/tree/main/examples/expo/src/examples/VariantsScreen.tsx)
- Default variant [link](/~https://github.com/jpudysz/react-native-unistyles/tree/main/examples/expo/src/examples/DefaultVariantScreen.tsx)
- Boolean variants [link](/~https://github.com/jpudysz/react-native-unistyles/tree/main/examples/expo/src/examples/BooleanVariantsScreen.tsx)

#### Plugins
- Auto guideline plugin [link](/~https://github.com/jpudysz/react-native-unistyles/tree/main/examples/expo/src/examples/AutoGuidelinePluginScreen.tsx)
Expand All @@ -67,5 +69,6 @@ Collection of examples is hosted on GitHub, making it easy for you to browse, le
- useStyles with no arguments [link](/~https://github.com/jpudysz/react-native-unistyles/tree/main/examples/expo/src/examples/NoStyleSheetScreen.tsx)
- Advanced nested stylesheeets with perfect TypeScript support [link](/~https://github.com/jpudysz/react-native-unistyles/tree/main/examples/expo/src/examples/TypeScriptValidatorTest.tsx)
- Content size category [link](/~https://github.com/jpudysz/react-native-unistyles/tree/main/examples/expo/src/examples/ContentSizeCategoryScreen.tsx)
- Change status/navigation bar color [link](/~https://github.com/jpudysz/react-native-unistyles/tree/main/examples/expo/src/examples/AndroidStatusBarNavigationBarScreen.tsx)

</Seo>
73 changes: 73 additions & 0 deletions docs/src/content/docs/reference/theming.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -294,4 +294,77 @@ export const UpdateTheme = ({ selectedColors }) => (
)
```

### Update statusBar color during runtime

<Badge label="Android" />
<Badge label="2.6.0" />

Sometimes your app needs to dynamically change the status bar color, for example, when entering image preview or on some onboarding screens. You can easily update the color with `UnistylesRuntime` and for example the `useEffect` hook:

```tsx /statusBar.setColor/
import React, { useEffect } from 'react'
import { UnistylesRuntime, useStyles } from 'react-native-unistyles'

export const OnboardingScreen = () => {
const { theme } = useStyles()

useEffect(() => {
UnistylesRuntime.statusBar.setColor(theme.colors.primary)

return () => {
// set default color
UnistylesRuntime.statusBar.setColor(undefined)
}
}, [])

// your onboarding JSX
return ()
}
```
:::tip[Colors]
Unistyles supports only a few colors and hex values. Check the compability [here](#status-and-navigation-bar-colors)
:::

### Update navigationBar color during runtime

<Badge label="Android" />
<Badge label="2.6.0" />

Just as with the `statusBar`, you can do the same with the `navigationBar` and set a specified color.

```tsx /navigationBar.setColor/
import React, { useEffect } from 'react'
import { UnistylesRuntime, useStyles } from 'react-native-unistyles'

export const ImagePreview = ({ imageUri }) => {
const { theme } = useStyles()

useEffect(() => {
UnistylesRuntime.navigationBar.setColor(theme.colors.black)

return () => {
// set default color
UnistylesRuntime.navigationBar.setColor(undefined)
}
}, [])

// your image preview JSX
return ()
}
```
:::tip[Colors]
Unistyles supports only a few colors and hex values. Check the compability [here](#status-and-navigation-bar-colors)
:::

### Status and Navigation bar colors

Unistyles supports all 6- and 8-character hex colors with a `#` prefix, as well as some built-in colors:

red, blue, green, black, white, gray, cyan, magenta, yellow, light gray, dark gray, grey, light grey, dark grey, aqua, fuchsia, lime, maroon, navy, olive, purple, silver, and teal.

:::danger
It's not possible to set a color for a **translucent** navigation bar.

If you set an unsupported color or attempt to do so on a translucent navigation bar, Unistyles will silently fail.
:::
</Seo>
2 changes: 2 additions & 0 deletions docs/src/content/docs/reference/unistyles-runtime.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,8 @@ and use it anywhere in your code, even outside a component.
| - | addPlugin | (plugin: UnistylesPlugin) => void | Enable a [plugin](/reference/plugins/) |
| - | removePlugin | (plugin: UnistylesPlugin) => void | Disable a [plugin](/reference/plugins/) |
| <Badge label="2.2.0" /> | updateTheme | (themeName: string, updater: (currentTheme: Theme) => Theme) => void | Update the [theme](/reference/theming/#update-theme-during-runtime) at runtime |
| <Badge label="2.6.0" />| statusBar.setColor | (color?: string) => void | Update [statusBar color](/reference/theming/#update-statusbar-color-during-runtime) at runtime |
| <Badge label="2.6.0" />| navigationBar.setColor | (color?: string) => void | Update [navigationBar color](/reference/theming/#update-navigationbar-color-during-runtime) at runtime |

### Why doesn't `UnistylesRuntime` re-render my component?

Expand Down
1 change: 1 addition & 0 deletions examples/expo/src/App.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,7 @@ export const App: React.FunctionComponent = () => (
<Stack.Screen name={DemoNames.ContentSizeCategoryScreen} component={Screens.ContentSizeCategoryScreen} />
<Stack.Screen name={DemoNames.BooleanVariants} component={Screens.BooleanVariantsScreen} />
<Stack.Screen name={DemoNames.UpdateTheme} component={Screens.UpdateThemeScreen} />
<Stack.Screen name={DemoNames.AndroidStatusBarNavigationBar} component={Screens.AndroidStatusBarNavigationBarScreen} />
</Stack.Navigator>
</NavigationContainer>
</SafeAreaProvider>
Expand Down
Loading

0 comments on commit b60accd

Please sign in to comment.