Fix: Reader footer position & system bar (#1076)

* replace native Navigation bar by expo-navigation-bar

* fix fullscreen logic

* fix: navigation button color

* fix: navigation color when theme changed

* fix: status bar color on full screen mode

* 40 is too large

* no 40 is acceptable :)

* refactor setBarColor

* refactor: rename setStatusBarColor

* no padding for landscape mode
This commit is contained in:
nyagami
2024-05-28 15:13:46 +07:00
committed by GitHub
parent 6864d07dd9
commit 1ac6d116a2
10 changed files with 88 additions and 256 deletions
@@ -13,7 +13,6 @@ import com.facebook.react.defaults.DefaultReactNativeHost;
import com.facebook.soloader.SoLoader;
import com.rajarsheechatterjee.EpubUtil.EpubUtilPackage;
import com.rajarsheechatterjee.NavigationBarColor.NavigationBarColorPackage;
import com.rajarsheechatterjee.TextFile.TextFilePackage;
import com.rajarsheechatterjee.VolumeButtonListener.VolumeButtonListenerPackage;
import com.rajarsheechatterjee.ZipArchive.ZipArchivePackage;
@@ -35,7 +34,6 @@ public class MainApplication extends Application implements ReactApplication {
@Override
protected List<ReactPackage> getPackages() {
List<ReactPackage> packages = new PackageList(this).getPackages();
packages.add(new NavigationBarColorPackage());
packages.add(new VolumeButtonListenerPackage());
packages.add(new ZipArchivePackage());
packages.add(new TextFilePackage());
@@ -1,158 +0,0 @@
package com.rajarsheechatterjee.NavigationBarColor;
import android.animation.ArgbEvaluator;
import android.animation.ValueAnimator;
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 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.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 NavigationBarColorModule extends ReactContextBaseJavaModule {
public static final String REACT_CLASS = "NavigationBarColor";
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 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 NavigationBarColorModule(ReactApplicationContext context) {
super(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() {
return REACT_CLASS;
}
@Override
public Map<String, Object> getConstants() {
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 (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(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(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));
}
}
@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) {
Window w = getCurrentActivity().getWindow();
w.setStatusBarColor(Color.TRANSPARENT);
w.setNavigationBarColor(Color.TRANSPARENT);
View decorView = w.getDecorView();
decorView.setSystemUiVisibility(View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION | View.SYSTEM_UI_FLAG_LAYOUT_STABLE);
}
}
});
} catch (IllegalViewOperationException e) {
WritableMap map = Arguments.createMap();
map.putBoolean("success", false);
promise.reject("error", e);
}
}
}
@@ -1,25 +0,0 @@
package com.rajarsheechatterjee.NavigationBarColor;
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 NavigationBarColorPackage implements ReactPackage {
@Override
public List<ViewManager> createViewManagers(ReactApplicationContext reactContext) {
return Collections.emptyList();
}
@Override
public List<NativeModule> createNativeModules(ReactApplicationContext reactContext) {
List<NativeModule> modules = new ArrayList<>();
modules.add(new NavigationBarColorModule(reactContext));
return modules;
}
}
+25
View File
@@ -27,6 +27,7 @@
"expo-linear-gradient": "~12.3.0",
"expo-linking": "~5.0.2",
"expo-localization": "~14.3.0",
"expo-navigation-bar": "^2.8.1",
"expo-notifications": "~0.20.1",
"expo-speech": "~11.3.0",
"expo-sqlite": "~11.3.3",
@@ -10001,6 +10002,18 @@
"invariant": "^2.2.4"
}
},
"node_modules/expo-navigation-bar": {
"version": "2.8.1",
"resolved": "https://registry.npmjs.org/expo-navigation-bar/-/expo-navigation-bar-2.8.1.tgz",
"integrity": "sha512-aT5G+7SUsXDVPsRwp8fF940ycka1ABb4g3QKvTZN3YP6kMWvsiYEmRqMIJVy0zUr/i6bxBG1ZergkXimWrFt3w==",
"dependencies": {
"@react-native/normalize-color": "^2.0.0",
"debug": "^4.3.2"
},
"peerDependencies": {
"expo": "*"
}
},
"node_modules/expo-notifications": {
"version": "0.20.1",
"resolved": "https://registry.npmjs.org/expo-notifications/-/expo-notifications-0.20.1.tgz",
@@ -15722,6 +15735,18 @@
"@babel/runtime": "^7.20.6"
}
},
"node_modules/react-native-dotenv/node_modules/dotenv": {
"version": "16.4.5",
"resolved": "https://registry.npmjs.org/dotenv/-/dotenv-16.4.5.tgz",
"integrity": "sha512-ZmdL2rui+eB2YwhsWzjInR8LldtZHGDoQ1ugH85ppHKwpUHL7j7rN0Ti9NCnGiQbhaZ11FpR+7ao1dNsmduNUg==",
"dev": true,
"engines": {
"node": ">=12"
},
"funding": {
"url": "https://dotenvx.com"
}
},
"node_modules/react-native-drawer-layout": {
"version": "3.2.2",
"resolved": "https://registry.npmjs.org/react-native-drawer-layout/-/react-native-drawer-layout-3.2.2.tgz",
+1
View File
@@ -33,6 +33,7 @@
"expo-linear-gradient": "~12.3.0",
"expo-linking": "~5.0.2",
"expo-localization": "~14.3.0",
"expo-navigation-bar": "^2.8.1",
"expo-notifications": "~0.20.1",
"expo-speech": "~11.3.0",
"expo-sqlite": "~11.3.3",
+33 -21
View File
@@ -1,18 +1,17 @@
import { useCallback, useEffect } from 'react';
import { StatusBar } from 'react-native';
import { useNavigation } from '@react-navigation/native';
import color from 'color';
import {
changeNavigationBarColor,
hideNavigationBar,
showNavigationBar,
} from '../../native/NavigationBarColor';
import {
useChapterGeneralSettings,
useChapterReaderSettings,
useTheme,
} from '../persisted';
import Color from 'color';
import * as NavigationBar from 'expo-navigation-bar';
import {
changeNavigationBarColor,
setStatusBarColor,
} from '@theme/utils/setBarColor';
const useFullscreenMode = () => {
const { addListener } = useNavigation();
@@ -23,21 +22,37 @@ const useFullscreenMode = () => {
const setImmersiveMode = useCallback(() => {
if (fullScreenMode) {
StatusBar.setHidden(true);
hideNavigationBar();
NavigationBar.setVisibilityAsync('hidden');
} else {
StatusBar.setBarStyle(
color(backgroundColor).isDark() ? 'light-content' : 'dark-content',
setStatusBarColor(Color(backgroundColor));
changeNavigationBarColor(
backgroundColor,
Color(backgroundColor).isDark(),
);
StatusBar.setBackgroundColor(backgroundColor);
changeNavigationBarColor(backgroundColor);
}
}, [backgroundColor, fullScreenMode]);
const showStatusAndNavBar = useCallback(() => {
StatusBar.setHidden(false);
showNavigationBar();
changeNavigationBarColor(Color(theme.surface).hex(), !theme.isDark);
}, []);
NavigationBar.setVisibilityAsync('visible');
if (fullScreenMode) {
/**
* This is overlay of reader footer and should be transparent.
* But in hexa, ##xxxxxx00 could be another color
*/
changeNavigationBarColor(
Color(theme.surface).alpha(0.05).hexa(),
theme.isDark,
);
setStatusBarColor(theme);
} else {
changeNavigationBarColor(
backgroundColor,
Color(backgroundColor).isDark(),
);
}
}, [backgroundColor, fullScreenMode]);
useEffect(() => {
setImmersiveMode();
@@ -45,13 +60,10 @@ const useFullscreenMode = () => {
useEffect(() => {
const unsubscribe = addListener('beforeRemove', () => {
showStatusAndNavBar();
StatusBar.setBarStyle(theme.isDark ? 'light-content' : 'dark-content');
changeNavigationBarColor(
Color(theme.surface2).hex(),
!theme.isDark,
true,
);
StatusBar.setHidden(false);
NavigationBar.setVisibilityAsync('visible');
setStatusBarColor(theme);
changeNavigationBarColor(Color(theme.surface2).hex(), !theme.isDark);
});
return unsubscribe;
-32
View File
@@ -1,32 +0,0 @@
import { NativeModules, Platform } from 'react-native';
const { NavigationBarColor } = NativeModules;
const changeNavigationBarColor = (
color = '',
light = false,
animated = true,
) => {
if (Platform.OS === 'android') {
const LightNav = light ? true : false;
NavigationBarColor.changeNavigationBarColor(color, LightNav, animated);
}
};
const hideNavigationBar = () => {
if (Platform.OS === 'android') {
NavigationBarColor.hideNavigationBar();
} else {
return false;
}
};
const showNavigationBar = () => {
if (Platform.OS === 'android') {
NavigationBarColor.showNavigationBar();
} else {
return false;
}
};
export { changeNavigationBarColor, hideNavigationBar, showNavigationBar };
+6 -8
View File
@@ -3,7 +3,10 @@ import React, { useEffect } from 'react';
import { NavigationContainer } from '@react-navigation/native';
import { createStackNavigator } from '@react-navigation/stack';
import { setBarColor } from '@theme/utils/setBarColor';
import {
changeNavigationBarColor,
setStatusBarColor,
} from '@theme/utils/setBarColor';
import { useAppSettings, usePlugins, useTheme } from '@hooks/persisted';
import { useGithubUpdateChecker } from '@hooks/common/githubUpdateChecker';
@@ -31,7 +34,6 @@ import BrowseSettings from '../screens/browse/BrowseSettings';
import { updateLibrary } from '@services/updates';
import WebviewScreen from '@screens/WebviewScreen/WebviewScreen';
import { RootStackParamList } from './types';
import { changeNavigationBarColor } from '@native/NavigationBarColor';
import Color from 'color';
const Stack = createStackNavigator<RootStackParamList>();
@@ -43,12 +45,8 @@ const MainNavigator = () => {
useEffect(() => {
const timer = setTimeout(async () => {
setBarColor(theme);
changeNavigationBarColor(
Color(theme.surface2).hex(),
!theme.isDark,
true,
);
setStatusBarColor(theme);
changeNavigationBarColor(Color(theme.surface2).hex(), theme.isDark);
}, 500);
return () => {
@@ -7,6 +7,7 @@ import { ThemeColors } from '@theme/types';
import { ChapterInfo, NovelInfo } from '@database/types';
import { BottomSheetModalMethods } from '@gorhom/bottom-sheet/lib/typescript/types';
import { ChapterScreenProps } from '@navigators/types';
import { useDeviceOrientation } from '@hooks/index';
interface ChapterFooterProps {
theme: ThemeColors;
@@ -38,14 +39,17 @@ const ChapterFooter = ({
borderless: true,
radius: 50,
};
const orientation = useDeviceOrientation();
return (
<Animated.View
entering={FadeIn.duration(150)}
exiting={FadeOut.duration(150)}
style={[
styles.footer,
{ backgroundColor: color(theme.surface).alpha(0.9).string() },
{
backgroundColor: color(theme.surface).alpha(0.9).string(),
paddingBottom: orientation === 'potrait' ? 40 : 0,
},
]}
>
<View style={[styles.buttonsContainer]}>
@@ -142,7 +146,6 @@ const styles = StyleSheet.create({
zIndex: 1,
bottom: 0,
width: '100%',
paddingBottom: 16,
},
buttonsContainer: {
flexDirection: 'row',
+17 -7
View File
@@ -1,11 +1,21 @@
import { StatusBar } from 'react-native';
import { ThemeColors } from '@theme/types';
import * as NavigationBar from 'expo-navigation-bar';
import Color from 'color';
/**
* Sets status and navigation bar color.
*
* @param theme
*/
export const setStatusBarColor = (color: ThemeColors | Color) => {
if (color instanceof Color) {
// fullscreen reader mode
StatusBar.setBarStyle(color.isDark() ? 'light-content' : 'dark-content');
StatusBar.setBackgroundColor(color.hexa());
} else {
StatusBar.setTranslucent(true);
StatusBar.setBackgroundColor('transparent');
StatusBar.setBarStyle(color.isDark ? 'light-content' : 'dark-content');
}
};
export const setBarColor = (theme: ThemeColors) =>
StatusBar.setBarStyle(theme.isDark ? 'light-content' : 'dark-content');
export const changeNavigationBarColor = (color: string, isDark = false) => {
NavigationBar.setBackgroundColorAsync(color);
NavigationBar.setButtonStyleAsync(isDark ? 'light' : 'dark');
};