Skip to content

Commit

Permalink
feat: app background (#26)
Browse files Browse the repository at this point in the history
* feat: app background

* docs: add some docs

* docs: add some docs

* docs: more docs

* docs: more docs
  • Loading branch information
vonovak authored Dec 22, 2023
1 parent 1049fd3 commit 9058686
Show file tree
Hide file tree
Showing 18 changed files with 1,202 additions and 863 deletions.
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -61,3 +61,4 @@ lib/


docgen/
theme-ex/
6 changes: 3 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -18,9 +18,9 @@ The package is tested with RN >= 0.66.1 (last tested with RN 73, Expo 49). See [

Notice the color of scroll bar and keyboard in the screenshots below.

| light mode | dark mode |
| ------------------------- | ----------------------- |
| ![light](./img/light.png) | ![dark](./img/dark.png) |
| light mode | dark mode | application background in modal |
| ------------------------- | ----------------------- |------------------------------------|
| ![light](./img/light.png) | ![dark](./img/dark.png) | ![app background](./img/modal.png) |

## Motivation

Expand Down
96 changes: 44 additions & 52 deletions android/src/main/java/eu/reactnativetraining/ThemeControlModule.kt
Original file line number Diff line number Diff line change
Expand Up @@ -11,11 +11,9 @@ import com.facebook.react.bridge.ReactMethod
import com.facebook.react.bridge.ReadableMap
import com.facebook.react.bridge.UiThreadUtil
import com.facebook.react.common.ReactConstants
import com.facebook.react.module.annotations.ReactModule

@ReactModule(name = ThemeControlModule.NAME)
class ThemeControlModule(reactContext: ReactApplicationContext?) :
NativeThemeControlSpec(reactContext) {
NativeThemeControlSpec(reactContext) {
override fun getName(): String {
return NAME
}
Expand All @@ -27,19 +25,14 @@ class ThemeControlModule(reactContext: ReactApplicationContext?) :

@ReactMethod
override fun setNavbarAppearance(params: ReadableMap, promise: Promise) {
val bgColor =
if (params.isNull("backgroundColor")) null else params.getInt("backgroundColor")
val dividerColor =
if (params.isNull("dividerColor")) null else params.getInt("dividerColor")
val bgColor = if (params.isNull("backgroundColor")) null else params.getInt("backgroundColor")
val dividerColor = if (params.isNull("dividerColor")) null else params.getInt("dividerColor")
val barStyle = if (params.isNull("barStyle")) null else params.getString("barStyle")

UiThreadUtil.runOnUiThread {
val currentActivity = currentActivity
if (currentActivity == null) {
FLog.e(
ReactConstants.TAG,
"$NAME cannot change navbar bgColor, activity is null."
)
FLog.e(ReactConstants.TAG, "$NAME cannot change navbar bgColor, activity is null.")
return@runOnUiThread
}
val window = currentActivity.window
Expand All @@ -53,45 +46,57 @@ class ThemeControlModule(reactContext: ReactApplicationContext?) :
val decorView = window.decorView
val lightNavigationBars = "dark-content" == barStyle
WindowInsetsControllerCompat(window, decorView).isAppearanceLightNavigationBars =
lightNavigationBars
lightNavigationBars
}
}
promise.resolve(null)
}

@ReactMethod
override fun setAppBackground(opts: ReadableMap, promise: Promise) {
val appBackground = if (opts.hasKey("appBackground")) opts.getInt("appBackground") else null

val activity = currentActivity

UiThreadUtil.runOnUiThread {
if (appBackground != null) {
activity?.window?.decorView?.setBackgroundColor(appBackground)
}
}
promise.resolve(activity != null)
}

@ReactMethod
override fun setTheme(themeStyle: String, opts: ReadableMap, promise: Promise) {
val persistTheme = !opts.hasKey("persistTheme") || opts.getBoolean("persistTheme")
val restartActivity = opts.hasKey("restartActivity") && opts.getBoolean("restartActivity")
@AppCompatDelegate.NightMode val mode = stringToMode(themeStyle)

if (persistTheme || restartActivity) {
persistTheme(themeStyle)
persistTheme(mode)
}
@AppCompatDelegate.NightMode val mode = stringToMode(themeStyle)
cachedMode = mode

UiThreadUtil.runOnUiThread {
if (restartActivity) {
val currentActivity = currentActivity
if (currentActivity != null) {
// the persisted / cached theme will be picked up
currentActivity.recreate()
} else {
FLog.e(ReactConstants.TAG, "$NAME cannot recreate, activity is null.")
}
UiThreadUtil.runOnUiThread { setMode(mode, restartActivity) }
promise.resolve(null)
}

private fun setMode(mode: Int, restartActivity: Boolean) {
if (restartActivity) {
val currentActivity = currentActivity
if (currentActivity != null) {
// the persisted / cached theme will be picked up
currentActivity.recreate()
} else {
AppCompatDelegate.setDefaultNightMode(mode)
FLog.e(ReactConstants.TAG, "$NAME cannot recreate, activity is null.")
}
} else {
AppCompatDelegate.setDefaultNightMode(mode)
}
promise.resolve(null)
}

private fun persistTheme(themeStyle: String) {
val prefs = reactApplicationContext.getSharedPreferences(
NAME,
Context.MODE_PRIVATE
)
private fun persistTheme(mode: Int) {
val prefs = reactApplicationContext.getSharedPreferences(NAME, Context.MODE_PRIVATE)
val editor = prefs.edit()
@AppCompatDelegate.NightMode val mode = stringToMode(themeStyle)
if (mode == AppCompatDelegate.MODE_NIGHT_FOLLOW_SYSTEM) {
editor.remove(THEME_ENTRY_KEY)
} else {
Expand All @@ -103,32 +108,19 @@ class ThemeControlModule(reactContext: ReactApplicationContext?) :
companion object {
const val NAME = "RNThemeControl"
const val THEME_ENTRY_KEY = "ThemeControlModuleEntry"
private var cachedMode: Int? = null

fun getThemeMode(ctx: Context): Int {
return cachedMode ?: let {
val prefs = ctx.getSharedPreferences(NAME, Context.MODE_PRIVATE)
@AppCompatDelegate.NightMode
val mode = prefs.getInt(THEME_ENTRY_KEY, AppCompatDelegate.MODE_NIGHT_FOLLOW_SYSTEM)
cachedMode = mode
return mode
}
}

fun recoverApplicationTheme(ctx: Context): Int {
val mode = getThemeMode(ctx)
setMode(mode)
return mode
}
val prefs = ctx.getSharedPreferences(NAME, Context.MODE_PRIVATE)
val nightMode = prefs.getInt(THEME_ENTRY_KEY, AppCompatDelegate.MODE_NIGHT_FOLLOW_SYSTEM)
forceTheme(nightMode)

fun forceTheme(forcedMode: Int) {
setMode(forcedMode)
return nightMode
}

private fun setMode(mode: Int) {
fun forceTheme(forcedMode: Int) {
UiThreadUtil.assertOnUiThread()
// setDefaultNightMode will be a noop if no change is needed
AppCompatDelegate.setDefaultNightMode(mode)
// setDefaultNightMode is a noop if no change is needed
AppCompatDelegate.setDefaultNightMode(forcedMode)
}

@AppCompatDelegate.NightMode
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,9 @@ public NativeThemeControlSpec(ReactApplicationContext reactContext) {
@ReactMethod
@DoNotStrip
public abstract void setNavbarAppearance(ReadableMap params, Promise promise);
@ReactMethod
@DoNotStrip
public abstract void setAppBackground(ReadableMap params, Promise promise);

@ReactMethod(isBlockingSynchronousMethod = true)
@DoNotStrip
Expand Down
45 changes: 40 additions & 5 deletions docs/readme-internal.md
Original file line number Diff line number Diff line change
Expand Up @@ -13,15 +13,17 @@
- [NavigationBar](readme-internal.md#navigationbar)
- [ThemeAwareStatusBar](readme-internal.md#themeawarestatusbar)
- [SystemBars](readme-internal.md#systembars)
- [AppBackground](readme-internal.md#appbackground)

### Type aliases

- [ThemedStatusBarProps](readme-internal.md#themedstatusbarprops)
- [AppBackgroundProps](readme-internal.md#appbackgroundprops)
- [SystemBarsProps](readme-internal.md#systembarsprops)
- [NavigationBarProps](readme-internal.md#navigationbarprops)
- [NavbarAppearanceParams](readme-internal.md#navbarappearanceparams)
- [ThemePreference](readme-internal.md#themepreference)
- [SetThemeOptions](readme-internal.md#setthemeoptions)
- [AppBackgroundProps](readme-internal.md#appbackgroundprops)

## Functions

Expand Down Expand Up @@ -97,10 +99,29 @@ However, you can override this behavior by passing a custom `barStyle` prop.

| Name | Type |
|:--------| :------ |
| `props` | [`ThemeAwareStatusBarProps`](readme-internal.md#themedstatusbarprops) |
| `props` | [`ThemeAwareStatusBarProps`](readme-internal.md#themeawarestatusbarprops) |

___

### AppBackground

**AppBackground**(`props`): ``null``

Sets the background color of the ApplicationWindow (iOS) or the current Activity (Android).
This is useful with React Navigation to prevent [white flashes when navigating](https://github.com/react-navigation/react-navigation/issues/10951) on Android, or to control the background color users see when presenting a modal on iOS.

You need to specify the background color for light and dark mode separately.

#### Parameters

| Name | Type |
| :------ | :------ |
| `props` | [`AppBackgroundProps`](modules.md#appbackgroundprops) |

#### Returns

``null``

### SystemBars

**SystemBars**(`props`): `Element`
Expand All @@ -124,14 +145,28 @@ Props of [ThemeAwareStatusBar](readme-internal.md#themeawarestatusbar)

___

### SystemBarsProps
### AppBackgroundProps

Ƭ **AppBackgroundProps**: `Object`

Ƭ **SystemBarsProps**: [`ThemeAwareStatusBarProps`](readme-internal.md#themeawarestatusbarprops) & `Pick`<[`NavigationBarProps`](readme-internal.md#navigationbarprops), ``"dividerColor"``\>
Background color of the application window (iOS) or the current Activity (Android), for light and dark mode separately.

Props of [SystemBars](readme-internal.md#systembars)
#### Type declaration

| Name | Type |
| :------ | :------ |
| `dark` | `ColorValue` |
| `light` | `ColorValue` |
___

### SystemBarsProps

Ƭ **SystemBarsProps**: [`ThemeAwareStatusBarProps`](modules.md#themeawarestatusbarprops) & `Pick`\<[`NavigationBarProps`](modules.md#navigationbarprops), ``"dividerColor"``\>

Props of [SystemBars](modules.md#systembars)

---

### NavigationBarProps

Ƭ **NavigationBarProps**: `Pick`<`StatusBarProps`, ``"barStyle"``> & { `backgroundColor?`: `ColorValue` ; `dividerColor?`: `ColorValue` }
Expand Down
2 changes: 1 addition & 1 deletion example/ios/Podfile
Original file line number Diff line number Diff line change
Expand Up @@ -4,4 +4,4 @@ use_flipper! false

workspace 'theme-control-example.xcworkspace'

use_test_app! :fabric_enabled => false, :turbomodule_enabled => false
use_test_app! :hermes_enabled => true
Loading

0 comments on commit 9058686

Please sign in to comment.