Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[Android] Fix: Change navigation bar to theme color #5150

Merged
merged 12 commits into from
Nov 16, 2023
5 changes: 3 additions & 2 deletions android/app/src/main/java/me/rainbow/MainApplication.java
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@
import me.rainbow.NativeModules.Internals.InternalPackage;
import me.rainbow.NativeModules.RNBip39.RNBip39Package;
import me.rainbow.NativeModules.RNBackHandler.RNBackHandlerPackage;
import me.rainbow.NativeModules.SystemNavigationBar.SystemNavigationBarPackage;
import me.rainbow.NativeModules.RNReview.RNReviewPackage;
import me.rainbow.NativeModules.RNStartTime.RNStartTimePackage;
import me.rainbow.NativeModules.RNTextAnimatorPackage.RNTextAnimatorPackage;
Expand Down Expand Up @@ -53,6 +54,7 @@ protected List<ReactPackage> getPackages() {
// Packages that cannot be autolinked yet can be added manually here, for example:
packages.add(new RNBip39Package());
packages.add(new RNReviewPackage());
packages.add(new SystemNavigationBarPackage());
packages.add(new RNBackHandlerPackage());
packages.add(new RNTextAnimatorPackage());
packages.add(new RNZoomableButtonPackage());
Expand All @@ -61,8 +63,7 @@ protected List<ReactPackage> getPackages() {
packages.add(new RNStartTimePackage(MainApplication.START_MARK));
packages.add(new RNHapticsPackage());


return packages;
return packages;
}

@Override
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,176 @@
// Created by react-native-create-bridge

package me.rainbow.NativeModules.SystemNavigationBar;

import android.animation.ArgbEvaluator;
import android.animation.ValueAnimator;
import android.annotation.TargetApi;
import android.graphics.Color;
import android.os.Build;
import android.app.Activity;
import android.view.View;
import android.view.Window;
import android.view.WindowManager;
import androidx.annotation.UiThread;
import com.facebook.react.bridge.Arguments;
import com.facebook.react.bridge.Promise;
import com.facebook.react.bridge.ReactApplicationContext;
import com.facebook.react.bridge.ReactContextBaseJavaModule;
import com.facebook.react.bridge.ReactMethod;
import com.facebook.react.bridge.UiThreadUtil;
import com.facebook.react.bridge.WritableMap;
import java.util.HashMap;
import java.util.Map;
import com.facebook.react.uimanager.IllegalViewOperationException;
import static com.facebook.react.bridge.UiThreadUtil.runOnUiThread;

public class SystemNavigationBarModule extends ReactContextBaseJavaModule {
public static final String REACT_CLASS = "NavigationBar";
private static final String ERROR_NO_ACTIVITY = "E_NO_ACTIVITY";
private static final String ERROR_NO_ACTIVITY_MESSAGE = "Tried to change the navigation bar while not attached to an Activity";
private static final String ERROR_API_LEVEL = "API_LEVEl";
private static final String ERROR_API_LEVEL_MESSAGE = "Only Android Oreo and above is supported";
private static ReactApplicationContext reactContext = null;
private static final int UI_FLAG_HIDE_NAV_BAR = View.SYSTEM_UI_FLAG_LAYOUT_STABLE
| View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION
| View.SYSTEM_UI_FLAG_HIDE_NAVIGATION // hide nav bar
| View.SYSTEM_UI_FLAG_IMMERSIVE_STICKY;

public SystemNavigationBarModule(ReactApplicationContext context) {
// Pass in the context to the constructor and save it so you can emit events
// https://facebook.github.io/react-native/docs/native-modules-android.html#the-toast-module
super(context);
reactContext = context;
}

public void setNavigationBarTheme(Activity activity, Boolean light) {
if (activity != null && Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
Window window = activity.getWindow();
int flags = window.getDecorView().getSystemUiVisibility();
if (light) {
flags |= View.SYSTEM_UI_FLAG_LIGHT_NAVIGATION_BAR;
} else {
flags &= ~View.SYSTEM_UI_FLAG_LIGHT_NAVIGATION_BAR;
}
window.getDecorView().setSystemUiVisibility(flags);
}
}


@Override
public String getName() {
// Tell React the name of the module
// https://facebook.github.io/react-native/docs/native-modules-android.html#the-toast-module
return REACT_CLASS;
}

@Override
public Map<String, Object> getConstants() {
// Export any constants to be used in your native module
// https://facebook.github.io/react-native/docs/native-modules-android.html#the-toast-module
final Map<String, Object> constants = new HashMap<>();
constants.put("EXAMPLE_CONSTANT", "example");

return constants;
}

@ReactMethod
public void changeNavigationBarColor(final String color, final Boolean light, final Boolean animated, final Promise promise) {
final WritableMap map = Arguments.createMap();
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
if (getCurrentActivity() != null) {
try {
final Window window = getCurrentActivity().getWindow();
runOnUiThread(new Runnable() {
@Override
public void run() {
if (color.equals("transparent") || color.equals("translucent")) {
window.clearFlags(WindowManager.LayoutParams.FLAG_LAYOUT_NO_LIMITS);
window.clearFlags(WindowManager.LayoutParams.FLAG_TRANSLUCENT_NAVIGATION);
if (color.equals("transparent")) {
window.setFlags(WindowManager.LayoutParams.FLAG_LAYOUT_NO_LIMITS, WindowManager.LayoutParams.FLAG_LAYOUT_NO_LIMITS);
} else {
window.setFlags(WindowManager.LayoutParams.FLAG_TRANSLUCENT_NAVIGATION, WindowManager.LayoutParams.FLAG_TRANSLUCENT_NAVIGATION);
}
setNavigationBarTheme(getCurrentActivity(), light);
map.putBoolean("success", true);
promise.resolve(map);
return;
} else {
window.clearFlags(WindowManager.LayoutParams.FLAG_LAYOUT_NO_LIMITS);
window.clearFlags(WindowManager.LayoutParams.FLAG_TRANSLUCENT_NAVIGATION);
}
if (animated) {
Integer colorFrom = window.getNavigationBarColor();
Integer colorTo = Color.parseColor(String.valueOf(color));
//window.setNavigationBarColor(colorTo);
ValueAnimator colorAnimation = ValueAnimator.ofObject(new ArgbEvaluator(), colorFrom, colorTo);
colorAnimation.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
@Override
public void onAnimationUpdate(ValueAnimator animator) {
window.setNavigationBarColor((Integer) animator.getAnimatedValue());
}
});
colorAnimation.start();
} else {
window.setNavigationBarColor(Color.parseColor(String.valueOf(color)));
}
setNavigationBarTheme(getCurrentActivity(), light);
WritableMap map = Arguments.createMap();
map.putBoolean("success", true);
promise.resolve(map);
}
});
} catch (IllegalViewOperationException e) {
map.putBoolean("success", false);
promise.reject("error", e);
}

} else {
promise.reject(ERROR_NO_ACTIVITY, new Throwable(ERROR_NO_ACTIVITY_MESSAGE));

}
} else {
promise.reject(ERROR_API_LEVEL, new Throwable(ERROR_API_LEVEL_MESSAGE));
}
}

@ReactMethod
public void hideNavigationBar(Promise promise) {
try {
runOnUiThread(new Runnable() {
@Override
public void run() {
if (getCurrentActivity() != null) {
View decorView = getCurrentActivity().getWindow().getDecorView();
decorView.setSystemUiVisibility(UI_FLAG_HIDE_NAV_BAR);
}
}
});
} catch (IllegalViewOperationException e) {
WritableMap map = Arguments.createMap();
map.putBoolean("success", false);
promise.reject("error", e);
}
}

@ReactMethod
public void showNavigationBar(Promise promise) {
try {
runOnUiThread(new Runnable() {
@Override
public void run() {
if (getCurrentActivity() != null) {
View decorView = getCurrentActivity().getWindow().getDecorView();
int uiOptions = View.SYSTEM_UI_FLAG_VISIBLE;
decorView.setSystemUiVisibility(uiOptions);
}
}
});
} catch (IllegalViewOperationException e) {
WritableMap map = Arguments.createMap();
map.putBoolean("success", false);
promise.reject("error", e);
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
package me.rainbow.NativeModules.SystemNavigationBar;

import androidx.annotation.NonNull;

import com.facebook.react.ReactPackage;
import com.facebook.react.bridge.NativeModule;
import com.facebook.react.bridge.ReactApplicationContext;
import com.facebook.react.uimanager.ViewManager;

import java.util.ArrayList;
import java.util.Collections;
import java.util.List;

public class SystemNavigationBarPackage implements ReactPackage {
@NonNull
@Override
public List<NativeModule> createNativeModules(@NonNull ReactApplicationContext reactContext) {
List<NativeModule> modules = new ArrayList<>();
modules.add(new SystemNavigationBarModule(reactContext));
return modules;
}

@NonNull
@Override
public List<ViewManager> createViewManagers(@NonNull ReactApplicationContext reactContext) {
return Collections.emptyList();
}
}
7 changes: 6 additions & 1 deletion src/components/PromoSheet.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,10 @@
import React, { useCallback, useEffect, useReducer } from 'react';
import { ImageSourcePropType, StatusBar, ImageBackground } from 'react-native';
import {
ImageSourcePropType,
Dimensions,
StatusBar,
ImageBackground,
} from 'react-native';
import LinearGradient from 'react-native-linear-gradient';
import MaskedView from '@react-native-masked-view/masked-view';
import { SheetActionButton, SheetHandle, SlackSheet } from '@/components/sheet';
Expand Down
26 changes: 25 additions & 1 deletion src/handlers/localstorage/theme.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,21 @@
import { IS_ANDROID } from '@/env';
import { getGlobal, saveGlobal } from './common';
import { NativeModules } from 'react-native';
import { colors } from '@/styles';
import { isUsingButtonNavigation } from '@/helpers/statusBarHelper';

const { NavigationBar } = NativeModules;

const THEME = 'theme';

export const getColorForThemeAndNavigationStyle = (theme: string) => {
if (!isUsingButtonNavigation()) {
return 'transparent';
}

return theme === 'dark' ? '#191A1C' : colors.white;
};

/**
* @desc get theme
* @return {String}
Expand All @@ -11,4 +25,14 @@ export const getTheme = () => getGlobal(THEME, 'light');
/**
* @desc save theme
*/
export const saveTheme = (theme: any) => saveGlobal(THEME, theme);
export const saveTheme = (theme: string) => {
if (IS_ANDROID) {
NavigationBar.changeNavigationBarColor(
getColorForThemeAndNavigationStyle(theme),
theme === 'light',
true
);
}

return saveGlobal(THEME, theme);
};
14 changes: 13 additions & 1 deletion src/helpers/statusBarHelper.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,9 @@
import { ColorValue, StatusBar, StatusBarAnimation } from 'react-native';
import {
ColorValue,
StatusBar,
StatusBarAnimation,
Dimensions,
} from 'react-native';

export const setTranslucent = (translucent: boolean): void => {
StatusBar.setTranslucent(translucent);
Expand Down Expand Up @@ -35,3 +40,10 @@ export const setDarkContent = (isAnimated = true) => {
barStyle: 'dark-content',
});
};

export const isUsingButtonNavigation = () => {
const deviceHeight = Dimensions.get('screen').height;
const windowHeight = Dimensions.get('window').height;
const bottomNavBarHeight = deviceHeight - windowHeight;
return bottomNavBarHeight > 80;
};
17 changes: 15 additions & 2 deletions src/navigation/SwipeNavigator.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { BlurView } from '@react-native-community/blur';
import { createMaterialTopTabNavigator } from '@react-navigation/material-top-tabs';
import React, { useLayoutEffect, useState } from 'react';
import React, { useLayoutEffect, useMemo, useState } from 'react';
import Animated, {
useAnimatedStyle,
useSharedValue,
Expand Down Expand Up @@ -46,6 +46,7 @@ import config from '@/model/config';
import SectionListScrollToTopProvider, {
useSectionListScrollToTopContext,
} from './SectionListScrollToTopContext';
import { isUsingButtonNavigation } from '@/helpers/statusBarHelper';

const HORIZONTAL_TAB_BAR_INSET = 6;

Expand All @@ -62,7 +63,18 @@ const animationConfig = {
};

const Swipe = createMaterialTopTabNavigator();
const HEADER_HEIGHT = IS_IOS ? 82 : 62;

const getHeaderHeight = () => {
if (IS_IOS) {
return 82;
}

if (!isUsingButtonNavigation()) {
return 72;
}

return 48;
};

const TabBar = ({
state,
Expand All @@ -76,6 +88,7 @@ const TabBar = ({
const { width: deviceWidth } = useDimensions();
const { colors, isDarkMode } = useTheme();

const HEADER_HEIGHT = useMemo(() => getHeaderHeight(), []);
const showPointsTab = IS_DEV || IS_TEST || config.points_enabled;
const NUMBER_OF_TABS = showPointsTab ? 4 : 3;

Expand Down
2 changes: 1 addition & 1 deletion src/screens/AddCash/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -51,7 +51,7 @@ export function AddCashSheet() {
const skeletonColor = useBackgroundColor('surfaceSecondaryElevated');
const sheetHeight = IS_IOS
? deviceHeight - insets.top
: deviceHeight - statusBarHeight;
: deviceHeight + statusBarHeight;

const { isLoading, data: providers, error } = useQuery(
['f2c', 'providers'],
Expand Down
1 change: 0 additions & 1 deletion src/theme/ThemeContext.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -93,7 +93,6 @@ export const MainThemeProvider = (
// Listening to changes of device appearance while in run-time
useEffect(() => {
if (colorScheme) {
// setIsDarkMode(colorScheme === Themes.DARK);
saveTheme(colorScheme);
}
}, [colorScheme]);
Expand Down