Skip to content

Commit

Permalink
RFC: FullWindowOverlay on android
Browse files Browse the repository at this point in the history
  • Loading branch information
mfazekas committed Oct 20, 2024
1 parent 791cb10 commit a2afc13
Show file tree
Hide file tree
Showing 6 changed files with 158 additions and 6 deletions.
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 @@ -10,10 +10,6 @@ const NativeFullWindowOverlay: React.ComponentType<
> = 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

0 comments on commit a2afc13

Please sign in to comment.