Skip to content

Commit

Permalink
Implement initialWindowSafeAreaInsets on android
Browse files Browse the repository at this point in the history
  • Loading branch information
janicduplessis committed Feb 5, 2020
1 parent 831fd65 commit 0baefa5
Show file tree
Hide file tree
Showing 10 changed files with 146 additions and 65 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -24,13 +24,8 @@ public String getEventName() {

@Override
public void dispatch(RCTEventEmitter rctEventEmitter) {
WritableMap insets = Arguments.createMap();
insets.putDouble("top", PixelUtil.toDIPFromPixel(mInsets.top));
insets.putDouble("right", PixelUtil.toDIPFromPixel(mInsets.right));
insets.putDouble("bottom", PixelUtil.toDIPFromPixel(mInsets.bottom));
insets.putDouble("left", PixelUtil.toDIPFromPixel(mInsets.left));
WritableMap event = Arguments.createMap();
event.putMap("insets", insets);
event.putMap("insets", SafeAreaUtils.edgeInsetsToJsMap(mInsets));
rctEventEmitter.receiveEvent(getViewTag(), getEventName(), event);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ public List<NativeModule> createNativeModules(@Nonnull ReactApplicationContext r
@Nonnull
@Override
public List<ViewManager> createViewManagers(@Nonnull ReactApplicationContext reactContext) {
return Collections.<ViewManager>singletonList(new SafeAreaViewManager());
return Collections.<ViewManager>singletonList(new SafeAreaViewManager(reactContext));
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,85 @@
package com.th3rdwave.safeareacontext;

import android.graphics.Rect;
import android.os.Build;
import android.view.Surface;
import android.view.View;
import android.view.WindowInsets;
import android.view.WindowManager;

import com.facebook.react.bridge.Arguments;
import com.facebook.react.bridge.ReadableMap;
import com.facebook.react.bridge.WritableMap;
import com.facebook.react.common.MapBuilder;
import com.facebook.react.uimanager.PixelUtil;

import java.util.Map;

/* package */ class SafeAreaUtils {
static ReadableMap edgeInsetsToJsMap(EdgeInsets insets) {
WritableMap insetsMap = Arguments.createMap();
insetsMap.putDouble("top", PixelUtil.toDIPFromPixel(insets.top));
insetsMap.putDouble("right", PixelUtil.toDIPFromPixel(insets.right));
insetsMap.putDouble("bottom", PixelUtil.toDIPFromPixel(insets.bottom));
insetsMap.putDouble("left", PixelUtil.toDIPFromPixel(insets.left));
return insetsMap;
}

static Map<String, Float> edgeInsetsToJavaMap(EdgeInsets insets) {
return MapBuilder.of(
"top",
PixelUtil.toDIPFromPixel(insets.top),
"right",
PixelUtil.toDIPFromPixel(insets.right),
"bottom",
PixelUtil.toDIPFromPixel(insets.bottom),
"left",
PixelUtil.toDIPFromPixel(insets.left));
}

static EdgeInsets getSafeAreaInsets(WindowManager windowManager, View rootView) {
// Window insets are parts of the window that are covered by system views (status bar,
// navigation bar, notches). There are no apis the get these values for android < M so we
// do a best effort polyfill.
EdgeInsets windowInsets;
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
WindowInsets insets = rootView.getRootWindowInsets();
windowInsets = new EdgeInsets(
insets.getSystemWindowInsetTop(),
insets.getSystemWindowInsetRight(),
insets.getSystemWindowInsetBottom(),
insets.getSystemWindowInsetLeft());
} else {
int rotation = windowManager.getDefaultDisplay().getRotation();
int statusBarHeight = 0;
int resourceId = rootView.getResources().getIdentifier("status_bar_height", "dimen", "android");
if (resourceId > 0) {
statusBarHeight = rootView.getResources().getDimensionPixelSize(resourceId);
}
int navbarHeight = 0;
resourceId = rootView.getResources().getIdentifier("navigation_bar_height", "dimen", "android");
if (resourceId > 0) {
navbarHeight = rootView.getResources().getDimensionPixelSize(resourceId);
}

windowInsets = new EdgeInsets(
statusBarHeight,
rotation == Surface.ROTATION_90 ? navbarHeight : 0,
rotation == Surface.ROTATION_0 || rotation == Surface.ROTATION_180 ? navbarHeight : 0,
rotation == Surface.ROTATION_270 ? navbarHeight : 0);
}

// Calculate the part of the root view that overlaps with window insets.
View contentView = rootView.findViewById(android.R.id.content);
float windowWidth = rootView.getWidth();
float windowHeight = rootView.getHeight();
Rect visibleRect = new Rect();
contentView.getGlobalVisibleRect(visibleRect);

windowInsets.top = Math.max(windowInsets.top - visibleRect.top, 0);
windowInsets.left = Math.max(windowInsets.left - visibleRect.left, 0);
windowInsets.bottom = Math.max(visibleRect.top + contentView.getHeight() + windowInsets.bottom - windowHeight, 0);
windowInsets.right = Math.max(visibleRect.left + contentView.getWidth() + windowInsets.right - windowWidth, 0);
return windowInsets;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -20,64 +20,17 @@ public interface OnInsetsChangeListener {
}

private @Nullable OnInsetsChangeListener mInsetsChangeListener;
private WindowManager mWindowManager;
private final WindowManager mWindowManager;
private @Nullable EdgeInsets mLastInsets;

public SafeAreaView(Context context) {
public SafeAreaView(Context context, WindowManager windowManager) {
super(context);

mWindowManager = (WindowManager) context.getSystemService(Context.WINDOW_SERVICE);
}

private EdgeInsets getSafeAreaInsets() {
// Window insets are parts of the window that are covered by system views (status bar,
// navigation bar, notches). There are no apis the get these values for android < M so we
// do a best effort polyfill.
EdgeInsets windowInsets;
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
WindowInsets insets = getRootWindowInsets();
windowInsets = new EdgeInsets(
insets.getSystemWindowInsetTop(),
insets.getSystemWindowInsetRight(),
insets.getSystemWindowInsetBottom(),
insets.getSystemWindowInsetLeft());
} else {
int rotation = mWindowManager.getDefaultDisplay().getRotation();
int statusBarHeight = 0;
int resourceId = getResources().getIdentifier("status_bar_height", "dimen", "android");
if (resourceId > 0) {
statusBarHeight = getResources().getDimensionPixelSize(resourceId);
}
int navbarHeight = 0;
resourceId = getResources().getIdentifier("navigation_bar_height", "dimen", "android");
if (resourceId > 0) {
navbarHeight = getResources().getDimensionPixelSize(resourceId);
}

windowInsets = new EdgeInsets(
statusBarHeight,
rotation == Surface.ROTATION_90 ? navbarHeight : 0,
rotation == Surface.ROTATION_0 || rotation == Surface.ROTATION_180 ? navbarHeight : 0,
rotation == Surface.ROTATION_270 ? navbarHeight : 0);
}

// Calculate the part of the root view that overlaps with window insets.
View rootView = getRootView();
View contentView = rootView.findViewById(android.R.id.content);
float windowWidth = rootView.getWidth();
float windowHeight = rootView.getHeight();
Rect visibleRect = new Rect();
contentView.getGlobalVisibleRect(visibleRect);

windowInsets.top = Math.max(windowInsets.top - visibleRect.top, 0);
windowInsets.left = Math.max(windowInsets.left - visibleRect.left, 0);
windowInsets.bottom = Math.max(visibleRect.top + contentView.getHeight() + windowInsets.bottom - windowHeight, 0);
windowInsets.right = Math.max(visibleRect.left + contentView.getWidth() + windowInsets.right - windowWidth, 0);
return windowInsets;
}

private void maybeUpdateInsets() {
EdgeInsets edgeInsets = getSafeAreaInsets();
EdgeInsets edgeInsets = SafeAreaUtils.getSafeAreaInsets(mWindowManager, getRootView());
if (mLastInsets == null || !mLastInsets.equalsToEdgeInsets(edgeInsets)) {
Assertions.assertNotNull(mInsetsChangeListener).onInsetsChange(this, edgeInsets);
mLastInsets = edgeInsets;
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,10 @@
package com.th3rdwave.safeareacontext;

import android.app.Activity;
import android.content.Context;
import android.view.WindowManager;

import com.facebook.react.bridge.ReactApplicationContext;
import com.facebook.react.common.MapBuilder;
import com.facebook.react.uimanager.ThemedReactContext;
import com.facebook.react.uimanager.UIManagerModule;
Expand All @@ -9,8 +14,19 @@
import java.util.Map;

import androidx.annotation.NonNull;
import androidx.annotation.Nullable;

public class SafeAreaViewManager extends ViewGroupManager<SafeAreaView> {
private final ReactApplicationContext mContext;
private final WindowManager mWindowManager;

public SafeAreaViewManager(ReactApplicationContext context) {
super();

mContext = context;
mWindowManager = (WindowManager) context.getSystemService(Context.WINDOW_SERVICE);
}

@Override
@NonNull
public String getName() {
Expand All @@ -20,7 +36,7 @@ public String getName() {
@Override
@NonNull
public SafeAreaView createViewInstance(@NonNull ThemedReactContext context) {
return new SafeAreaView(context);
return new SafeAreaView(context, mWindowManager);
}

@Override
Expand All @@ -41,4 +57,18 @@ public Map<String, Object> getExportedCustomDirectEventTypeConstants() {
.put(InsetsChangeEvent.EVENT_NAME, MapBuilder.of("registrationName", "onInsetsChange"))
.build();
}

@Nullable
@Override
public Map<String, Object> getExportedViewConstants() {
Activity activity = mContext.getCurrentActivity();
if (activity == null) {
return null;
}

EdgeInsets insets = SafeAreaUtils.getSafeAreaInsets(mWindowManager, activity.getWindow().getDecorView());
return MapBuilder.<String, Object>of(
"initialWindowSafeAreaInsets",
SafeAreaUtils.edgeInsetsToJavaMap(insets));
}
}
10 changes: 9 additions & 1 deletion example/App.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,11 @@ import * as React from 'react';
import { View, Text, StatusBar, Button } from 'react-native';

// import { SafeAreaProvider, useSafeArea } from 'react-native-safe-area-context'; in your app.
import { SafeAreaProvider, useSafeArea } from '../src';
import {
SafeAreaProvider,
useSafeArea,
initialWindowSafeAreaInsets,
} from '../src';

const Screen = () => {
const insets = useSafeArea();
Expand Down Expand Up @@ -33,6 +37,10 @@ const Screen = () => {
}}
>
<Text>Insets: {JSON.stringify(insets, null, 2)}</Text>
<Text>
Initial insets:{' '}
{JSON.stringify(initialWindowSafeAreaInsets, null, 2)}
</Text>
</View>
</View>
</>
Expand Down
3 changes: 0 additions & 3 deletions src/InitialWindowSafeAreaInsets.ios.ts

This file was deleted.

11 changes: 9 additions & 2 deletions src/InitialWindowSafeAreaInsets.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,11 @@
import { UIManager } from 'react-native';
import { EdgeInsets } from './SafeArea.types';

const initialWindowSafeAreaInsets: EdgeInsets | null = null;
export default initialWindowSafeAreaInsets;
const RNCSafeAreaViewConfig = UIManager.getViewManagerConfig(
'RNCSafeAreaView',
// eslint-disable-next-line @typescript-eslint/no-explicit-any
) as any;

export default (RNCSafeAreaViewConfig.Constants != null
? RNCSafeAreaViewConfig.Constants.initialWindowSafeAreaInsets
: null) as EdgeInsets | null;
4 changes: 4 additions & 0 deletions src/InitialWindowSafeAreaInsets.web.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
import { EdgeInsets } from './SafeArea.types';

const initialWindowSafeAreaInsets: EdgeInsets | null = null;
export default initialWindowSafeAreaInsets;
4 changes: 3 additions & 1 deletion src/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,9 @@ import { StyleSheet, View, ViewProps } from 'react-native';
import { EdgeInsets as EdgeInsetsT, InsetChangedEvent } from './SafeArea.types';
import NativeSafeAreaView from './NativeSafeAreaView';

export { default as initialWindowSafeAreaInsets } from './InitialWindowSafeAreaInsets';
export {
default as initialWindowSafeAreaInsets,
} from './InitialWindowSafeAreaInsets';

export const SafeAreaContext = React.createContext<EdgeInsetsT | null>(null);

Expand Down

0 comments on commit 0baefa5

Please sign in to comment.