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

RFC + WIP: FullWindowOverlay on android #2421

Open
wants to merge 1 commit into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -0,0 +1,124 @@
package com.swmansion.rnscreens

import android.content.Context
import android.graphics.PixelFormat
import android.view.MotionEvent
import android.view.View
import android.view.WindowManager
import com.facebook.react.bridge.ReactContext
import com.facebook.react.bridge.UiThreadUtil
import com.facebook.react.module.annotations.ReactModule
import com.facebook.react.uimanager.JSPointerDispatcher
import com.facebook.react.uimanager.JSTouchDispatcher
import com.facebook.react.uimanager.RootView
import com.facebook.react.uimanager.ThemedReactContext
import com.facebook.react.uimanager.UIManagerHelper
import com.facebook.react.uimanager.ViewGroupManager
import com.facebook.react.uimanager.events.EventDispatcher
import com.facebook.react.views.view.ReactViewGroup

class FullWindowOverlayRootViewGroup(val reactContext: ThemedReactContext): ReactViewGroup(reactContext), RootView {
internal var eventDispatcher: EventDispatcher? = null

internal val jSTouchDispatcher: JSTouchDispatcher = JSTouchDispatcher(this)
internal var jSPointerDispatcher: JSPointerDispatcher? = null

override fun onChildStartedNativeGesture(childView: View, ev: MotionEvent) {
eventDispatcher?.let {
jSTouchDispatcher.onChildStartedNativeGesture(ev, it)
jSPointerDispatcher?.onChildStartedNativeGesture(childView, ev, it)
}
}

override fun onChildEndedNativeGesture(childView: View, ev: MotionEvent) {
eventDispatcher?.let {
jSTouchDispatcher.onChildEndedNativeGesture(ev, it)
jSPointerDispatcher?.onChildEndedNativeGesture()
}
}

override fun handleException(t: Throwable) {
reactContext.reactApplicationContext.handleException(RuntimeException(t))
}

override fun onInterceptTouchEvent(event: MotionEvent): Boolean {
eventDispatcher?.let { eventDispatcher ->
jSTouchDispatcher.handleTouchEvent(event, eventDispatcher)
jSPointerDispatcher?.handleMotionEvent(event, eventDispatcher, true)
}
return super.onInterceptTouchEvent(event)
}

fun addToViewHierarchy() {
val windowManager = reactContext.getSystemService(Context.WINDOW_SERVICE) as WindowManager
val windowParams = WindowManager.LayoutParams(
WindowManager.LayoutParams.WRAP_CONTENT,
WindowManager.LayoutParams.WRAP_CONTENT,
WindowManager.LayoutParams.TYPE_APPLICATION,
(WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL
or WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE
or WindowManager.LayoutParams.FLAG_LAYOUT_NO_LIMITS
or WindowManager.LayoutParams.FLAG_LAYOUT_IN_SCREEN),
PixelFormat.TRANSLUCENT
)
windowManager.addView(this, windowParams)
}
}

class FullWindowOverlay(val reactContext: ThemedReactContext): ReactViewGroup(reactContext) {
private val fullWindowOverlayRootViewGroup = FullWindowOverlayRootViewGroup(reactContext)

init {
fullWindowOverlayRootViewGroup.addToViewHierarchy()
}

public var eventDispatcher: EventDispatcher?
get() = fullWindowOverlayRootViewGroup.eventDispatcher
public set(eventDispatcher) {
fullWindowOverlayRootViewGroup.eventDispatcher = eventDispatcher
}

public override fun getChildCount(): Int = fullWindowOverlayRootViewGroup.childCount

public override fun getChildAt(index: Int): View? = fullWindowOverlayRootViewGroup.getChildAt(index)

override fun addView(child: View?, index: Int) {
UiThreadUtil.assertOnUiThread()
fullWindowOverlayRootViewGroup.addView(child, index)
}

override fun removeView(child: View?) {
UiThreadUtil.assertOnUiThread()

if (child != null) {
fullWindowOverlayRootViewGroup.removeView(child)
}
}

public override fun removeViewAt(index: Int) {
UiThreadUtil.assertOnUiThread()
val child = getChildAt(index)
fullWindowOverlayRootViewGroup.removeView(child)
}
}

@ReactModule(name = FullWindowOverlayViewManager.REACT_CLASS)
class FullWindowOverlayViewManager: ViewGroupManager<FullWindowOverlay>() {
companion object {
const val REACT_CLASS = "RNSFullWindowOverlay"
}

override fun getName() = FullWindowOverlayViewManager.REACT_CLASS

override fun createViewInstance(reactContext: ThemedReactContext) = FullWindowOverlay(reactContext)

protected override fun addEventEmitters(
reactContext: ThemedReactContext,
view: FullWindowOverlay
) {
val dispatcher = UIManagerHelper.getEventDispatcherForReactTag(reactContext, view.id)
if (dispatcher != null) {
view.eventDispatcher = dispatcher
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@ class RNScreensPackage : TurboReactPackage() {
SearchBarManager(),
ScreenFooterManager(),
ScreenContentWrapperManager(),
FullWindowOverlayViewManager()
)
}

Expand Down
30 changes: 30 additions & 0 deletions apps/src/tests/TestFullWindowOverlay.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
import React from "react";
import { View, Modal, Text } from "react-native";
import { FullWindowOverlay } from "react-native-screens";

export default function TestFullScreenOverlay() {
return (
<View style={{flex:1}}>
<View style={{position:'absolute', top:0,left:0,right:0,bottom:0}}>
<View style={{ flex: 1, borderColor: 'black', borderWidth: 1, borderRadius: 20, backgroundColor: 'lightcyan', position: 'absolute', top: 200, padding: 40, marginHorizontal: 20 }}>
<Text>RNView</Text>
</View>
</View>
<Modal visible={true} transparent>
<View style={{ flex: 1, borderColor: 'black', borderWidth: 1, borderRadius: 20, backgroundColor: 'gainsboro', position: 'absolute', top: 200, left: 100, padding: 40, marginHorizontal: 20 }}>
<Text>Modal</Text>
</View>
</Modal>
<FullWindowOverlay>
<View style={{ position: 'absolute', top: 160, padding: 20, marginHorizontal: 50, backgroundColor: 'wheat', borderRadius: 10, shadowOffset: { width: 4, height: 4}, shadowOpacity: 0.2, shadowRadius: 10}}>
<Text>FullWindowOverlay #1</Text>
</View>
</FullWindowOverlay>
<FullWindowOverlay>
<View style={{ position: 'absolute', top: 280, padding: 20, marginHorizontal: 50, backgroundColor: 'wheat', borderRadius: 10, shadowOffset: { width: 4, height: 4}, shadowOpacity: 0.2, shadowRadius: 10}}>
<Text>FullWindowOverlay #2</Text>
</View>
</FullWindowOverlay>
</View>
)
}
1 change: 1 addition & 0 deletions apps/src/tests/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -121,3 +121,4 @@ export { default as TestActivityStateProgression } from './TestActivityStateProg
export { default as TestHeaderTitle } from './TestHeaderTitle';
export { default as TestModalNavigation } from './TestModalNavigation';
export { default as TestMemoryLeak } from './TestMemoryLeak';
export { default as TestFullWindowOverlay } from './TestFullWindowOverlay';
2 changes: 1 addition & 1 deletion guides/CONTRIBUTING.md
Original file line number Diff line number Diff line change
Expand Up @@ -91,7 +91,7 @@ To begin with, let install all dependencies:

1. `yarn`
2. `yarn submodules`
3. `(cd react-navigation && yarn prepare)`
3. `(cd react-navigation && yarn build)`
4. `cd Example`
5. `yarn`
6. `yarn start` &ndash; make sure to start metro bundler before building the app in Android Studio
Expand Down
6 changes: 1 addition & 5 deletions src/components/FullWindowOverlay.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import React, { PropsWithChildren, ReactNode } from 'react';
import { Platform, StyleProp, View, ViewStyle } from 'react-native';
import { StyleProp, ViewStyle } from 'react-native';

// Native components
import FullWindowOverlayNativeComponent from '../fabric/FullWindowOverlayNativeComponent';
Expand All @@ -7,13 +7,9 @@
PropsWithChildren<{
style: StyleProp<ViewStyle>;
}>
> = FullWindowOverlayNativeComponent as any;

Check warning on line 10 in src/components/FullWindowOverlay.tsx

View workflow job for this annotation

GitHub Actions / install-and-lint

Unexpected any. Specify a different type

function FullWindowOverlay(props: { children: ReactNode }) {
if (Platform.OS !== 'ios') {
console.warn('Using FullWindowOverlay is only valid on iOS devices.');
return <View {...props} />;
}
return (
<NativeFullWindowOverlay
style={{ position: 'absolute', width: '100%', height: '100%' }}>
Expand Down
Loading