1import { ExpoConfig } from '@expo/config-types';
2import assert from 'assert';
3
4import { ConfigPlugin } from '../Plugin.types';
5import { withAndroidColors, withAndroidStyles } from '../plugins/android-plugins';
6import { assignColorValue } from './Colors';
7import { ResourceXML } from './Resources';
8import { assignStylesValue, getAppThemeLightNoActionBarGroup } from './Styles';
9
10// https://developer.android.com/reference/android/R.attr#colorPrimaryDark
11const COLOR_PRIMARY_DARK_KEY = 'colorPrimaryDark';
12// https://developer.android.com/reference/android/R.attr#windowTranslucentStatus
13const WINDOW_TRANSLUCENT_STATUS = 'android:windowTranslucentStatus';
14// https://developer.android.com/reference/android/R.attr#windowLightStatusBar
15const WINDOW_LIGHT_STATUS_BAR = 'android:windowLightStatusBar';
16
17export const withStatusBar: ConfigPlugin = (config) => {
18  config = withStatusBarColors(config);
19  config = withStatusBarStyles(config);
20  return config;
21};
22
23const withStatusBarColors: ConfigPlugin = (config) => {
24  return withAndroidColors(config, (config) => {
25    config.modResults = setStatusBarColors(config, config.modResults);
26    return config;
27  });
28};
29
30const withStatusBarStyles: ConfigPlugin = (config) => {
31  return withAndroidStyles(config, (config) => {
32    config.modResults = setStatusBarStyles(config, config.modResults);
33    return config;
34  });
35};
36
37export function setStatusBarColors(
38  config: Pick<ExpoConfig, 'androidStatusBar'>,
39  colors: ResourceXML
40): ResourceXML {
41  return assignColorValue(colors, {
42    name: COLOR_PRIMARY_DARK_KEY,
43    value: getStatusBarColor(config),
44  });
45}
46
47export function setStatusBarStyles(
48  config: Pick<ExpoConfig, 'androidStatusBar'>,
49  styles: ResourceXML
50): ResourceXML {
51  const hexString = getStatusBarColor(config);
52  const floatElement = getStatusBarTranslucent(config);
53
54  styles = assignStylesValue(styles, {
55    parent: getAppThemeLightNoActionBarGroup(),
56    name: WINDOW_LIGHT_STATUS_BAR,
57    targetApi: '23',
58    value: 'true',
59    // Default is light-content, don't need to do anything to set it
60    add: getStatusBarStyle(config) === 'dark-content',
61  });
62
63  styles = assignStylesValue(styles, {
64    parent: getAppThemeLightNoActionBarGroup(),
65    name: WINDOW_TRANSLUCENT_STATUS,
66    value: 'true',
67    // translucent status bar set in theme
68    add: floatElement,
69  });
70
71  styles = assignStylesValue(styles, {
72    parent: getAppThemeLightNoActionBarGroup(),
73    name: COLOR_PRIMARY_DARK_KEY,
74    value: `@color/${COLOR_PRIMARY_DARK_KEY}`,
75    // Remove the color if translucent is used
76    add: !!hexString,
77  });
78
79  return styles;
80}
81
82export function getStatusBarColor(config: Pick<ExpoConfig, 'androidStatusBar'>) {
83  const backgroundColor = config.androidStatusBar?.backgroundColor;
84  if (backgroundColor) {
85    // Drop support for translucent
86    assert(
87      backgroundColor !== 'translucent',
88      `androidStatusBar.backgroundColor must be a valid hex string, instead got: "${backgroundColor}"`
89    );
90  }
91  return backgroundColor;
92}
93
94/**
95 * Specifies whether the status bar should be "translucent". When true, the status bar is drawn with `position: absolute` and a gray underlay, when false `position: relative` (pushes content down).
96 *
97 * @default false
98 * @param config
99 * @returns
100 */
101export function getStatusBarTranslucent(config: Pick<ExpoConfig, 'androidStatusBar'>): boolean {
102  return config.androidStatusBar?.translucent ?? false;
103}
104
105export function getStatusBarStyle(config: Pick<ExpoConfig, 'androidStatusBar'>) {
106  return config.androidStatusBar?.barStyle || 'light-content';
107}
108