From 3802f85579dacc989c0a9b4168da27543ef78e78 Mon Sep 17 00:00:00 2001 From: Tomasz Sapeta Date: Mon, 3 Oct 2022 13:43:54 +0200 Subject: [PATCH 1/8] Enable screens to use new vendoring on Android --- .../modules/api/screens/CustomSearchView.kt | 71 --- .../modules/api/screens/CustomToolbar.kt | 7 - .../api/screens/FabricEnabledViewGroup.kt | 10 - .../api/screens/FragmentBackPressOverrider.kt | 29 -- .../modules/api/screens/LifecycleHelper.kt | 61 --- .../modules/api/screens/RNScreensPackage.kt | 32 -- .../exponent/modules/api/screens/Screen.kt | 265 ----------- .../modules/api/screens/ScreenContainer.kt | 357 --------------- .../api/screens/ScreenContainerViewManager.kt | 52 --- .../modules/api/screens/ScreenFragment.kt | 333 -------------- .../modules/api/screens/ScreenStack.kt | 353 --------------- .../api/screens/ScreenStackFragment.kt | 252 ----------- .../api/screens/ScreenStackHeaderConfig.kt | 414 ------------------ .../ScreenStackHeaderConfigViewManager.kt | 208 --------- .../api/screens/ScreenStackHeaderSubview.kt | 42 -- .../ScreenStackHeaderSubviewManager.kt | 47 -- .../api/screens/ScreenStackViewManager.kt | 87 ---- .../modules/api/screens/ScreenViewManager.kt | 193 -------- .../modules/api/screens/ScreenWindowTraits.kt | 275 ------------ .../modules/api/screens/ScreensShadowNode.kt | 19 - .../modules/api/screens/SearchBarManager.kt | 107 ----- .../modules/api/screens/SearchBarView.kt | 155 ------- .../api/screens/SearchViewFormatter.kt | 67 --- .../events/HeaderBackButtonClickedEvent.kt | 24 - .../api/screens/events/ScreenAppearEvent.kt | 20 - .../screens/events/ScreenDisappearEvent.kt | 20 - .../screens/events/ScreenDismissedEvent.kt | 23 - .../events/ScreenTransitionProgressEvent.kt | 33 -- .../screens/events/ScreenWillAppearEvent.kt | 20 - .../events/ScreenWillDisappearEvent.kt | 20 - .../events/StackFinishTransitioningEvent.kt | 20 - tools/src/vendoring/config/expoGoConfig.ts | 33 +- 32 files changed, 16 insertions(+), 3633 deletions(-) delete mode 100644 android/expoview/src/main/java/versioned/host/exp/exponent/modules/api/screens/CustomSearchView.kt delete mode 100644 android/expoview/src/main/java/versioned/host/exp/exponent/modules/api/screens/CustomToolbar.kt delete mode 100644 android/expoview/src/main/java/versioned/host/exp/exponent/modules/api/screens/FabricEnabledViewGroup.kt delete mode 100644 android/expoview/src/main/java/versioned/host/exp/exponent/modules/api/screens/FragmentBackPressOverrider.kt delete mode 100644 android/expoview/src/main/java/versioned/host/exp/exponent/modules/api/screens/LifecycleHelper.kt delete mode 100644 android/expoview/src/main/java/versioned/host/exp/exponent/modules/api/screens/RNScreensPackage.kt delete mode 100644 android/expoview/src/main/java/versioned/host/exp/exponent/modules/api/screens/Screen.kt delete mode 100644 android/expoview/src/main/java/versioned/host/exp/exponent/modules/api/screens/ScreenContainer.kt delete mode 100644 android/expoview/src/main/java/versioned/host/exp/exponent/modules/api/screens/ScreenContainerViewManager.kt delete mode 100644 android/expoview/src/main/java/versioned/host/exp/exponent/modules/api/screens/ScreenFragment.kt delete mode 100644 android/expoview/src/main/java/versioned/host/exp/exponent/modules/api/screens/ScreenStack.kt delete mode 100644 android/expoview/src/main/java/versioned/host/exp/exponent/modules/api/screens/ScreenStackFragment.kt delete mode 100644 android/expoview/src/main/java/versioned/host/exp/exponent/modules/api/screens/ScreenStackHeaderConfig.kt delete mode 100644 android/expoview/src/main/java/versioned/host/exp/exponent/modules/api/screens/ScreenStackHeaderConfigViewManager.kt delete mode 100644 android/expoview/src/main/java/versioned/host/exp/exponent/modules/api/screens/ScreenStackHeaderSubview.kt delete mode 100644 android/expoview/src/main/java/versioned/host/exp/exponent/modules/api/screens/ScreenStackHeaderSubviewManager.kt delete mode 100644 android/expoview/src/main/java/versioned/host/exp/exponent/modules/api/screens/ScreenStackViewManager.kt delete mode 100644 android/expoview/src/main/java/versioned/host/exp/exponent/modules/api/screens/ScreenViewManager.kt delete mode 100644 android/expoview/src/main/java/versioned/host/exp/exponent/modules/api/screens/ScreenWindowTraits.kt delete mode 100644 android/expoview/src/main/java/versioned/host/exp/exponent/modules/api/screens/ScreensShadowNode.kt delete mode 100644 android/expoview/src/main/java/versioned/host/exp/exponent/modules/api/screens/SearchBarManager.kt delete mode 100644 android/expoview/src/main/java/versioned/host/exp/exponent/modules/api/screens/SearchBarView.kt delete mode 100644 android/expoview/src/main/java/versioned/host/exp/exponent/modules/api/screens/SearchViewFormatter.kt delete mode 100644 android/expoview/src/main/java/versioned/host/exp/exponent/modules/api/screens/events/HeaderBackButtonClickedEvent.kt delete mode 100644 android/expoview/src/main/java/versioned/host/exp/exponent/modules/api/screens/events/ScreenAppearEvent.kt delete mode 100644 android/expoview/src/main/java/versioned/host/exp/exponent/modules/api/screens/events/ScreenDisappearEvent.kt delete mode 100644 android/expoview/src/main/java/versioned/host/exp/exponent/modules/api/screens/events/ScreenDismissedEvent.kt delete mode 100644 android/expoview/src/main/java/versioned/host/exp/exponent/modules/api/screens/events/ScreenTransitionProgressEvent.kt delete mode 100644 android/expoview/src/main/java/versioned/host/exp/exponent/modules/api/screens/events/ScreenWillAppearEvent.kt delete mode 100644 android/expoview/src/main/java/versioned/host/exp/exponent/modules/api/screens/events/ScreenWillDisappearEvent.kt delete mode 100644 android/expoview/src/main/java/versioned/host/exp/exponent/modules/api/screens/events/StackFinishTransitioningEvent.kt diff --git a/android/expoview/src/main/java/versioned/host/exp/exponent/modules/api/screens/CustomSearchView.kt b/android/expoview/src/main/java/versioned/host/exp/exponent/modules/api/screens/CustomSearchView.kt deleted file mode 100644 index d610a4b1f39fc..0000000000000 --- a/android/expoview/src/main/java/versioned/host/exp/exponent/modules/api/screens/CustomSearchView.kt +++ /dev/null @@ -1,71 +0,0 @@ -package versioned.host.exp.exponent.modules.api.screens - -import android.content.Context -import androidx.activity.OnBackPressedCallback -import androidx.appcompat.widget.SearchView -import androidx.fragment.app.Fragment - -class CustomSearchView(context: Context, fragment: Fragment) : SearchView(context) { - /* - CustomSearchView uses some variables from SearchView. They are listed below with links to documentation - isIconified - https://developer.android.com/reference/android/widget/SearchView#setIconified(boolean) - maxWidth - https://developer.android.com/reference/android/widget/SearchView#setMaxWidth(int) - setOnSearchClickListener - https://developer.android.com/reference/android/widget/SearchView#setOnSearchClickListener(android.view.View.OnClickListener) - setOnCloseListener - https://developer.android.com/reference/android/widget/SearchView#setOnCloseListener(android.widget.SearchView.OnCloseListener) - */ - private var mCustomOnCloseListener: OnCloseListener? = null - private var mCustomOnSearchClickedListener: OnClickListener? = null - - private var mOnBackPressedCallback: OnBackPressedCallback = - object : OnBackPressedCallback(true) { - override fun handleOnBackPressed() { - isIconified = true - } - } - private val backPressOverrider = FragmentBackPressOverrider(fragment, mOnBackPressedCallback) - var overrideBackAction: Boolean - set(value) { - backPressOverrider.overrideBackAction = value - } - get() = backPressOverrider.overrideBackAction - - fun focus() { - isIconified = false - requestFocusFromTouch() - } - - override fun setOnCloseListener(listener: OnCloseListener?) { - mCustomOnCloseListener = listener - } - - override fun setOnSearchClickListener(listener: OnClickListener?) { - mCustomOnSearchClickedListener = listener - } - - override fun onAttachedToWindow() { - super.onAttachedToWindow() - if (!isIconified) { - backPressOverrider.maybeAddBackCallback() - } - } - - override fun onDetachedFromWindow() { - super.onDetachedFromWindow() - backPressOverrider.removeBackCallbackIfAdded() - } - - init { - super.setOnSearchClickListener { v -> - mCustomOnSearchClickedListener?.onClick(v) - backPressOverrider.maybeAddBackCallback() - } - - super.setOnCloseListener { - val result = mCustomOnCloseListener?.onClose() ?: false - backPressOverrider.removeBackCallbackIfAdded() - result - } - - maxWidth = Integer.MAX_VALUE - } -} diff --git a/android/expoview/src/main/java/versioned/host/exp/exponent/modules/api/screens/CustomToolbar.kt b/android/expoview/src/main/java/versioned/host/exp/exponent/modules/api/screens/CustomToolbar.kt deleted file mode 100644 index 61e1ea0f3a532..0000000000000 --- a/android/expoview/src/main/java/versioned/host/exp/exponent/modules/api/screens/CustomToolbar.kt +++ /dev/null @@ -1,7 +0,0 @@ -package versioned.host.exp.exponent.modules.api.screens - -import android.content.Context -import androidx.appcompat.widget.Toolbar - -// This class is used to store config closer to search bar -open class CustomToolbar(context: Context, val config: ScreenStackHeaderConfig) : Toolbar(context) diff --git a/android/expoview/src/main/java/versioned/host/exp/exponent/modules/api/screens/FabricEnabledViewGroup.kt b/android/expoview/src/main/java/versioned/host/exp/exponent/modules/api/screens/FabricEnabledViewGroup.kt deleted file mode 100644 index 3a52c45519f35..0000000000000 --- a/android/expoview/src/main/java/versioned/host/exp/exponent/modules/api/screens/FabricEnabledViewGroup.kt +++ /dev/null @@ -1,10 +0,0 @@ -package versioned.host.exp.exponent.modules.api.screens - -import android.view.ViewGroup -import com.facebook.react.bridge.ReactContext - -abstract class FabricEnabledViewGroup constructor(context: ReactContext?) : ViewGroup(context) { - protected fun updateScreenSizeFabric(width: Int, height: Int) { - // do nothing - } -} diff --git a/android/expoview/src/main/java/versioned/host/exp/exponent/modules/api/screens/FragmentBackPressOverrider.kt b/android/expoview/src/main/java/versioned/host/exp/exponent/modules/api/screens/FragmentBackPressOverrider.kt deleted file mode 100644 index 2885103b35566..0000000000000 --- a/android/expoview/src/main/java/versioned/host/exp/exponent/modules/api/screens/FragmentBackPressOverrider.kt +++ /dev/null @@ -1,29 +0,0 @@ -package versioned.host.exp.exponent.modules.api.screens - -import androidx.activity.OnBackPressedCallback -import androidx.fragment.app.Fragment - -class FragmentBackPressOverrider( - private val fragment: Fragment, - private val mOnBackPressedCallback: OnBackPressedCallback -) { - private var mIsBackCallbackAdded: Boolean = false - var overrideBackAction: Boolean = true - - fun maybeAddBackCallback() { - if (!mIsBackCallbackAdded && overrideBackAction) { - fragment.activity?.onBackPressedDispatcher?.addCallback( - fragment, - mOnBackPressedCallback - ) - mIsBackCallbackAdded = true - } - } - - fun removeBackCallbackIfAdded() { - if (mIsBackCallbackAdded) { - mOnBackPressedCallback.remove() - mIsBackCallbackAdded = false - } - } -} diff --git a/android/expoview/src/main/java/versioned/host/exp/exponent/modules/api/screens/LifecycleHelper.kt b/android/expoview/src/main/java/versioned/host/exp/exponent/modules/api/screens/LifecycleHelper.kt deleted file mode 100644 index 0c77431920def..0000000000000 --- a/android/expoview/src/main/java/versioned/host/exp/exponent/modules/api/screens/LifecycleHelper.kt +++ /dev/null @@ -1,61 +0,0 @@ -package versioned.host.exp.exponent.modules.api.screens - -import android.view.View -import androidx.fragment.app.Fragment -import androidx.lifecycle.Lifecycle -import androidx.lifecycle.LifecycleObserver - -class LifecycleHelper { - private val mViewToLifecycleMap: MutableMap = HashMap() - private val mRegisterOnLayoutChange: View.OnLayoutChangeListener = object : View.OnLayoutChangeListener { - override fun onLayoutChange( - view: View, - i: Int, - i1: Int, - i2: Int, - i3: Int, - i4: Int, - i5: Int, - i6: Int, - i7: Int - ) { - registerViewWithLifecycleOwner(view) - view.removeOnLayoutChangeListener(this) - } - } - - private fun registerViewWithLifecycleOwner(view: View) { - val parent = findNearestScreenFragmentAncestor(view) - if (parent != null && view is LifecycleObserver) { - val lifecycle = parent.lifecycle - lifecycle.addObserver((view as LifecycleObserver)) - mViewToLifecycleMap[view] = lifecycle - } - } - - fun register(view: T) where T : View, T : LifecycleObserver? { - // we need to wait until view is mounted in the hierarchy as this method is called only at the - // moment of the view creation. In order to register lifecycle observer we need to find ancestor - // of type Screen and this can only happen when the view is properly attached. We rely on - // Android's onLayout callback being triggered when the view gets added to the hierarchy and - // only then we attempt to locate lifecycle owner ancestor. - view.addOnLayoutChangeListener(mRegisterOnLayoutChange) - } - - fun unregister(view: T) where T : View, T : LifecycleObserver? { - val lifecycle = mViewToLifecycleMap[view] - lifecycle?.removeObserver(view) - } - - companion object { - fun findNearestScreenFragmentAncestor(view: View): Fragment? { - var parent = view.parent - while (parent != null && parent !is Screen) { - parent = parent.parent - } - return if (parent != null) { - (parent as Screen).fragment - } else null - } - } -} diff --git a/android/expoview/src/main/java/versioned/host/exp/exponent/modules/api/screens/RNScreensPackage.kt b/android/expoview/src/main/java/versioned/host/exp/exponent/modules/api/screens/RNScreensPackage.kt deleted file mode 100644 index 78b0720f8be5b..0000000000000 --- a/android/expoview/src/main/java/versioned/host/exp/exponent/modules/api/screens/RNScreensPackage.kt +++ /dev/null @@ -1,32 +0,0 @@ -package versioned.host.exp.exponent.modules.api.screens - -import com.facebook.react.ReactPackage -import com.facebook.react.bridge.NativeModule -import com.facebook.react.bridge.ReactApplicationContext -import com.facebook.react.uimanager.ViewManager -import com.facebook.soloader.SoLoader - -import host.exp.expoview.BuildConfig - -class RNScreensPackage : ReactPackage { - override fun createNativeModules(reactContext: ReactApplicationContext): List { - if (BuildConfig.IS_NEW_ARCHITECTURE_ENABLED) { - // For Fabric, we load c++ native library here, this triggers screen's Fabric - // component registration which is necessary in order to avoid asking users - // to manually add init calls in their application code. - // This should no longer be needed if RN's autolink mechanism has Fabric support - SoLoader.loadLibrary("rnscreens_modules") - } - return emptyList() - } - - override fun createViewManagers(reactContext: ReactApplicationContext) = - listOf>( - ScreenContainerViewManager(), - ScreenViewManager(), - ScreenStackViewManager(), - ScreenStackHeaderConfigViewManager(), - ScreenStackHeaderSubviewManager(), - SearchBarManager() - ) -} diff --git a/android/expoview/src/main/java/versioned/host/exp/exponent/modules/api/screens/Screen.kt b/android/expoview/src/main/java/versioned/host/exp/exponent/modules/api/screens/Screen.kt deleted file mode 100644 index acd8b56b066c3..0000000000000 --- a/android/expoview/src/main/java/versioned/host/exp/exponent/modules/api/screens/Screen.kt +++ /dev/null @@ -1,265 +0,0 @@ -package versioned.host.exp.exponent.modules.api.screens - -import android.annotation.SuppressLint -import android.content.pm.ActivityInfo -import android.graphics.Paint -import android.os.Parcelable -import android.util.SparseArray -import android.view.ViewGroup -import android.view.WindowManager -import android.webkit.WebView -import com.facebook.react.bridge.GuardedRunnable -import com.facebook.react.bridge.ReactContext -import com.facebook.react.uimanager.UIManagerModule - -import host.exp.expoview.BuildConfig - -@SuppressLint("ViewConstructor") -class Screen constructor(context: ReactContext?) : FabricEnabledViewGroup(context) { - - var fragment: ScreenFragment? = null - var container: ScreenContainer<*>? = null - var activityState: ActivityState? = null - private set - private var mTransitioning = false - var stackPresentation = StackPresentation.PUSH - var replaceAnimation = ReplaceAnimation.POP - var stackAnimation = StackAnimation.DEFAULT - var isGestureEnabled = true - var screenOrientation: Int? = null - private set - private var mStatusBarStyle: String? = null - private var mStatusBarHidden: Boolean? = null - private var mStatusBarTranslucent: Boolean? = null - private var mStatusBarColor: Int? = null - private var mNavigationBarColor: Int? = null - private var mNavigationBarHidden: Boolean? = null - var isStatusBarAnimated: Boolean? = null - private var mNativeBackButtonDismissalEnabled = true - - init { - // we set layout params as WindowManager.LayoutParams to workaround the issue with TextInputs - // not displaying modal menus (e.g., copy/paste or selection). The missing menus are due to the - // fact that TextView implementation is expected to be attached to window when layout happens. - // Then, at the moment of layout it checks whether window type is in a reasonable range to tell - // whether it should enable selection controls (see Editor.java#prepareCursorControllers). - // With screens, however, the text input component can be laid out before it is attached, in - // that case TextView tries to get window type property from the oldest existing parent, which - // in this case is a Screen class, as it is the root of the screen that is about to be attached. - // Setting params this way is not the most elegant way to solve this problem but workarounds it - // for the time being - layoutParams = WindowManager.LayoutParams(WindowManager.LayoutParams.TYPE_APPLICATION) - } - - override fun dispatchSaveInstanceState(container: SparseArray) { - // do nothing, react native will keep the view hierarchy so no need to serialize/deserialize - // view's states. The side effect of restoring is that TextInput components would trigger - // set-text events which may confuse text input handling. - } - - override fun dispatchRestoreInstanceState(container: SparseArray) { - // ignore restoring instance state too as we are not saving anything anyways. - } - - override fun onLayout(changed: Boolean, l: Int, t: Int, r: Int, b: Int) { - if (changed) { - val width = r - l - val height = b - t - if (BuildConfig.IS_NEW_ARCHITECTURE_ENABLED) { - updateScreenSizeFabric(width, height) - } else { - updateScreenSizePaper(width, height) - } - } - } - - private fun updateScreenSizePaper(width: Int, height: Int) { - val reactContext = context as ReactContext - reactContext.runOnNativeModulesQueueThread( - object : GuardedRunnable(reactContext) { - override fun runGuarded() { - reactContext - .getNativeModule(UIManagerModule::class.java) - ?.updateNodeSize(id, width, height) - } - }) - } - - val headerConfig: ScreenStackHeaderConfig? - get() { - val child = getChildAt(0) - return if (child is ScreenStackHeaderConfig) { - child - } else null - } - - /** - * While transitioning this property allows to optimize rendering behavior on Android and provide - * a correct blending options for the animated screen. It is turned on automatically by the - * container when transitioning is detected and turned off immediately after - */ - fun setTransitioning(transitioning: Boolean) { - if (mTransitioning == transitioning) { - return - } - mTransitioning = transitioning - val isWebViewInScreen = hasWebView(this) - if (isWebViewInScreen && layerType != LAYER_TYPE_HARDWARE) { - return - } - super.setLayerType( - if (transitioning && !isWebViewInScreen) LAYER_TYPE_HARDWARE else LAYER_TYPE_NONE, - null - ) - } - - private fun hasWebView(viewGroup: ViewGroup): Boolean { - for (i in 0 until viewGroup.childCount) { - val child = viewGroup.getChildAt(i) - if (child is WebView) { - return true - } else if (child is ViewGroup) { - if (hasWebView(child)) { - return true - } - } - } - return false - } - - override fun setLayerType(layerType: Int, paint: Paint?) { - // ignore - layer type is controlled by `transitioning` prop - } - - fun setActivityState(activityState: ActivityState) { - if (activityState == this.activityState) { - return - } - this.activityState = activityState - container?.notifyChildUpdate() - } - - fun setScreenOrientation(screenOrientation: String?) { - if (screenOrientation == null) { - this.screenOrientation = null - return - } - ScreenWindowTraits.applyDidSetOrientation() - this.screenOrientation = when (screenOrientation) { - "all" -> ActivityInfo.SCREEN_ORIENTATION_FULL_SENSOR - "portrait" -> ActivityInfo.SCREEN_ORIENTATION_SENSOR_PORTRAIT - "portrait_up" -> ActivityInfo.SCREEN_ORIENTATION_PORTRAIT - "portrait_down" -> ActivityInfo.SCREEN_ORIENTATION_REVERSE_PORTRAIT - "landscape" -> ActivityInfo.SCREEN_ORIENTATION_SENSOR_LANDSCAPE - "landscape_left" -> ActivityInfo.SCREEN_ORIENTATION_REVERSE_LANDSCAPE - "landscape_right" -> ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE - else -> ActivityInfo.SCREEN_ORIENTATION_UNSPECIFIED - } - - fragment?.let { ScreenWindowTraits.setOrientation(this, it.tryGetActivity()) } - } - - // Accepts one of 4 accessibility flags - // developer.android.com/reference/android/view/View#attr_android:importantForAccessibility - fun changeAccessibilityMode(mode: Int) { - this.importantForAccessibility = mode - this.headerConfig?.toolbar?.importantForAccessibility = mode - } - - var statusBarStyle: String? - get() = mStatusBarStyle - set(statusBarStyle) { - if (statusBarStyle != null) { - ScreenWindowTraits.applyDidSetStatusBarAppearance() - } - mStatusBarStyle = statusBarStyle - fragment?.let { ScreenWindowTraits.setStyle(this, it.tryGetActivity(), it.tryGetContext()) } - } - - var isStatusBarHidden: Boolean? - get() = mStatusBarHidden - set(statusBarHidden) { - if (statusBarHidden != null) { - ScreenWindowTraits.applyDidSetStatusBarAppearance() - } - mStatusBarHidden = statusBarHidden - fragment?.let { ScreenWindowTraits.setHidden(this, it.tryGetActivity()) } - } - - var isStatusBarTranslucent: Boolean? - get() = mStatusBarTranslucent - set(statusBarTranslucent) { - if (statusBarTranslucent != null) { - ScreenWindowTraits.applyDidSetStatusBarAppearance() - } - mStatusBarTranslucent = statusBarTranslucent - fragment?.let { - ScreenWindowTraits.setTranslucent( - this, - it.tryGetActivity(), - it.tryGetContext() - ) - } - } - - var statusBarColor: Int? - get() = mStatusBarColor - set(statusBarColor) { - if (statusBarColor != null) { - ScreenWindowTraits.applyDidSetStatusBarAppearance() - } - mStatusBarColor = statusBarColor - fragment?.let { ScreenWindowTraits.setColor(this, it.tryGetActivity(), it.tryGetContext()) } - } - - var navigationBarColor: Int? - get() = mNavigationBarColor - set(navigationBarColor) { - if (navigationBarColor != null) { - ScreenWindowTraits.applyDidSetNavigationBarAppearance() - } - mNavigationBarColor = navigationBarColor - fragment?.let { ScreenWindowTraits.setNavigationBarColor(this, it.tryGetActivity()) } - } - - var isNavigationBarHidden: Boolean? - get() = mNavigationBarHidden - set(navigationBarHidden) { - if (navigationBarHidden != null) { - ScreenWindowTraits.applyDidSetNavigationBarAppearance() - } - mNavigationBarHidden = navigationBarHidden - fragment?.let { - ScreenWindowTraits.setNavigationBarHidden( - this, - it.tryGetActivity(), - ) - } - } - - var nativeBackButtonDismissalEnabled: Boolean - get() = mNativeBackButtonDismissalEnabled - set(enableNativeBackButtonDismissal) { - mNativeBackButtonDismissalEnabled = enableNativeBackButtonDismissal - } - - enum class StackPresentation { - PUSH, MODAL, TRANSPARENT_MODAL - } - - enum class StackAnimation { - DEFAULT, NONE, FADE, SLIDE_FROM_BOTTOM, SLIDE_FROM_RIGHT, SLIDE_FROM_LEFT, FADE_FROM_BOTTOM - } - - enum class ReplaceAnimation { - PUSH, POP - } - - enum class ActivityState { - INACTIVE, TRANSITIONING_OR_BELOW_TOP, ON_TOP - } - - enum class WindowTraits { - ORIENTATION, COLOR, STYLE, TRANSLUCENT, HIDDEN, ANIMATED, NAVIGATION_BAR_COLOR, NAVIGATION_BAR_HIDDEN - } -} diff --git a/android/expoview/src/main/java/versioned/host/exp/exponent/modules/api/screens/ScreenContainer.kt b/android/expoview/src/main/java/versioned/host/exp/exponent/modules/api/screens/ScreenContainer.kt deleted file mode 100644 index 214b14b7ce147..0000000000000 --- a/android/expoview/src/main/java/versioned/host/exp/exponent/modules/api/screens/ScreenContainer.kt +++ /dev/null @@ -1,357 +0,0 @@ -package versioned.host.exp.exponent.modules.api.screens - -import android.content.Context -import android.content.ContextWrapper -import android.view.View -import android.view.ViewGroup -import android.view.ViewParent -import android.view.inputmethod.InputMethodManager -import androidx.fragment.app.Fragment -import androidx.fragment.app.FragmentActivity -import androidx.fragment.app.FragmentManager -import androidx.fragment.app.FragmentTransaction -import com.facebook.react.ReactRootView -import com.facebook.react.bridge.ReactContext -import com.facebook.react.modules.core.ChoreographerCompat -import com.facebook.react.modules.core.ReactChoreographer -import versioned.host.exp.exponent.modules.api.screens.Screen.ActivityState - -open class ScreenContainer(context: Context?) : ViewGroup(context) { - @JvmField - protected val mScreenFragments = ArrayList() - @JvmField - protected var mFragmentManager: FragmentManager? = null - private var mIsAttached = false - private var mNeedUpdate = false - private var mLayoutEnqueued = false - private val mLayoutCallback: ChoreographerCompat.FrameCallback = object : ChoreographerCompat.FrameCallback() { - override fun doFrame(frameTimeNanos: Long) { - mLayoutEnqueued = false - measure( - MeasureSpec.makeMeasureSpec(width, MeasureSpec.EXACTLY), - MeasureSpec.makeMeasureSpec(height, MeasureSpec.EXACTLY) - ) - layout(left, top, right, bottom) - } - } - private var mParentScreenFragment: ScreenFragment? = null - override fun onLayout(changed: Boolean, l: Int, t: Int, r: Int, b: Int) { - var i = 0 - val size = childCount - while (i < size) { - getChildAt(i).layout(0, 0, width, height) - i++ - } - } - - override fun removeView(view: View) { - // The below block is a workaround for an issue with keyboard handling within fragments. Despite - // Android handles input focus on the fragments that leave the screen, the keyboard stays open - // in a number of cases. The issue can be best reproduced on Android 5 devices, before some - // changes in Android's InputMethodManager have been introduced (specifically around dismissing - // the keyboard in onDetachedFromWindow). However, we also noticed the keyboard issue happen - // intermittently on recent versions of Android as well. The issue hasn't been previously - // noticed as in React Native <= 0.61 there was a logic that'd trigger keyboard dismiss upon a - // blur event (the blur even gets dispatched properly, the keyboard just stays open despite - // that) – note the change in RN core here: - // https://github.com/facebook/react-native/commit/e9b4928311513d3cbbd9d875827694eab6cfa932 - // The workaround is to force-hide keyboard when the screen that has focus is dismissed (we - // detect that in removeView as super.removeView causes the input view to un focus while keeping - // the keyboard open). - if (view === focusedChild) { - (context.getSystemService(Context.INPUT_METHOD_SERVICE) as InputMethodManager) - .hideSoftInputFromWindow(windowToken, InputMethodManager.HIDE_NOT_ALWAYS) - } - super.removeView(view) - } - - override fun requestLayout() { - super.requestLayout() - @Suppress("SENSELESS_COMPARISON") // mLayoutCallback can be null here since this method can be called in init - if (!mLayoutEnqueued && mLayoutCallback != null) { - mLayoutEnqueued = true - // we use NATIVE_ANIMATED_MODULE choreographer queue because it allows us to catch the current - // looper loop instead of enqueueing the update in the next loop causing a one frame delay. - ReactChoreographer.getInstance() - .postFrameCallback( - ReactChoreographer.CallbackType.NATIVE_ANIMATED_MODULE, mLayoutCallback - ) - } - } - - val isNested: Boolean - get() = mParentScreenFragment != null - - fun notifyChildUpdate() { - performUpdatesNow() - } - - protected open fun adapt(screen: Screen): T { - @Suppress("UNCHECKED_CAST") - return ScreenFragment(screen) as T - } - - fun addScreen(screen: Screen, index: Int) { - val fragment = adapt(screen) - screen.fragment = fragment - mScreenFragments.add(index, fragment) - screen.container = this - onScreenChanged() - } - - open fun removeScreenAt(index: Int) { - mScreenFragments[index].screen.container = null - mScreenFragments.removeAt(index) - onScreenChanged() - } - - open fun removeAllScreens() { - for (screenFragment in mScreenFragments) { - screenFragment.screen.container = null - } - mScreenFragments.clear() - onScreenChanged() - } - - val screenCount: Int - get() = mScreenFragments.size - - fun getScreenAt(index: Int): Screen { - return mScreenFragments[index].screen - } - - open val topScreen: Screen? - get() { - for (screenFragment in mScreenFragments) { - if (getActivityState(screenFragment) === ActivityState.ON_TOP) { - return screenFragment.screen - } - } - return null - } - - private fun setFragmentManager(fm: FragmentManager) { - mFragmentManager = fm - performUpdatesNow() - } - - private fun setupFragmentManager() { - var parent: ViewParent = this - // We traverse view hierarchy up until we find screen parent or a root view - while (!(parent is ReactRootView || parent is Screen) && - parent.parent != null - ) { - parent = parent.parent - } - // If parent is of type Screen it means we are inside a nested fragment structure. - // Otherwise we expect to connect directly with root view and get root fragment manager - if (parent is Screen) { - val screenFragment = parent.fragment - check(screenFragment != null) { "Parent Screen does not have its Fragment attached" } - mParentScreenFragment = screenFragment - screenFragment.registerChildScreenContainer(this) - setFragmentManager(screenFragment.childFragmentManager) - return - } - - // we expect top level view to be of type ReactRootView, this isn't really necessary but in - // order to find root view we test if parent is null. This could potentially happen also when - // the view is detached from the hierarchy and that test would not correctly indicate the root - // view. So in order to make sure we indeed reached the root we test if it is of a correct type. - // This allows us to provide a more descriptive error message for the aforementioned case. - check(parent is ReactRootView) { "ScreenContainer is not attached under ReactRootView" } - // ReactRootView is expected to be initialized with the main React Activity as a context but - // in case of Expo the activity is wrapped in ContextWrapper and we need to unwrap it - var context = parent.context - while (context !is FragmentActivity && context is ContextWrapper) { - context = context.baseContext - } - check(context is FragmentActivity) { "In order to use RNScreens components your app's activity need to extend ReactActivity" } - setFragmentManager(context.supportFragmentManager) - } - - protected fun createTransaction(): FragmentTransaction { - val fragmentManager = requireNotNull(mFragmentManager, { "mFragmentManager is null when creating transaction" }) - val transaction = fragmentManager.beginTransaction() - transaction.setReorderingAllowed(true) - return transaction - } - - private fun attachScreen(transaction: FragmentTransaction, screenFragment: ScreenFragment) { - transaction.add(id, screenFragment) - } - - private fun detachScreen(transaction: FragmentTransaction, screenFragment: ScreenFragment) { - transaction.remove(screenFragment) - } - - private fun getActivityState(screenFragment: ScreenFragment): ActivityState? { - return screenFragment.screen.activityState - } - - open fun hasScreen(screenFragment: ScreenFragment?): Boolean { - return mScreenFragments.contains(screenFragment) - } - - override fun onAttachedToWindow() { - super.onAttachedToWindow() - mIsAttached = true - setupFragmentManager() - } - - /** Removes fragments from fragment manager that are attached to this container */ - private fun removeMyFragments(fragmentManager: FragmentManager) { - val transaction = fragmentManager.beginTransaction() - var hasFragments = false - for (fragment in fragmentManager.fragments) { - if (fragment is ScreenFragment && - fragment.screen.container === this - ) { - transaction.remove(fragment) - hasFragments = true - } - } - - if (hasFragments) { - transaction.commitNowAllowingStateLoss() - } - } - - override fun onDetachedFromWindow() { - // if there are pending transactions and this view is about to get detached we need to perform - // them here as otherwise fragment manager will crash because it won't be able to find container - // view. We also need to make sure all the fragments attached to the given container are removed - // from fragment manager as in some cases fragment manager may be reused and in such case it'd - // attempt to reattach previously registered fragments that are not removed - mFragmentManager?.let { - if (!it.isDestroyed) { - removeMyFragments(it) - it.executePendingTransactions() - } - } - - mParentScreenFragment?.unregisterChildScreenContainer(this) - mParentScreenFragment = null - - super.onDetachedFromWindow() - mIsAttached = false - // When fragment container view is detached we force all its children to be removed. - // It is because children screens are controlled by their fragments, which can often have a - // delayed lifecycle (due to transitions). As a result due to ongoing transitions the fragment - // may choose not to remove the view despite the parent container being completely detached - // from the view hierarchy until the transition is over. In such a case when the container gets - // re-attached while the transition is ongoing, the child view would still be there and we'd - // attempt to re-attach it to with a misconfigured fragment. This would result in a crash. To - // avoid it we clear all the children here as we attach all the child fragments when the - // container is reattached anyways. We don't use `removeAllViews` since it does not check if the - // children are not already detached, which may lead to calling `onDetachedFromWindow` on them - // twice. - // We also get the size earlier, because we will be removing child views in `for` loop. - val size = childCount - for (i in size - 1 downTo 0) { - removeViewAt(i) - } - } - - override fun onMeasure(widthMeasureSpec: Int, heightMeasureSpec: Int) { - super.onMeasure(widthMeasureSpec, heightMeasureSpec) - var i = 0 - val size = childCount - while (i < size) { - getChildAt(i).measure(widthMeasureSpec, heightMeasureSpec) - i++ - } - } - - private fun onScreenChanged() { - // we perform update in `onBeforeLayout` of `ScreensShadowNode` by adding an UIBlock - // which is called after updating children of the ScreenContainer. - // We do it there because `onUpdate` logic requires all changes of children to be already - // made in order to provide proper animation for fragment transition for ScreenStack - // and this in turn makes nested ScreenContainers detach too early and disappear - // before transition if also not dispatched after children updates. - // The exception to this rule is `updateImmediately` which is triggered by actions - // not connected to React view hierarchy changes, but rather internal events - mNeedUpdate = true - (context as? ReactContext)?.runOnUiQueueThread { - // We schedule the update here because LayoutAnimations of `react-native-reanimated` - // sometimes attach/detach screens after the layout block of `ScreensShadowNode` has - // already run, and we want to update the container then too. In the other cases, - // this code will do nothing since it will run after the UIBlock when `mNeedUpdate` - // will already be false. - performUpdates() - } - } - - protected fun performUpdatesNow() { - // we want to update immediately when the fragment manager is set or native back button - // dismiss is dispatched or Screen's activityState changes since it is not connected to React - // view hierarchy changes and will not trigger `onBeforeLayout` method of `ScreensShadowNode` - mNeedUpdate = true - performUpdates() - } - - fun performUpdates() { - if (!mNeedUpdate || !mIsAttached || mFragmentManager == null || mFragmentManager?.isDestroyed == true) { - return - } - mNeedUpdate = false - onUpdate() - notifyContainerUpdate() - } - - open fun onUpdate() { - createTransaction().let { - // detach screens that are no longer active - val orphaned: MutableSet = HashSet(requireNotNull(mFragmentManager, { "mFragmentManager is null when performing update in ScreenContainer" }).fragments) - for (screenFragment in mScreenFragments) { - if (getActivityState(screenFragment) === ActivityState.INACTIVE && - screenFragment.isAdded - ) { - detachScreen(it, screenFragment) - } - orphaned.remove(screenFragment) - } - if (orphaned.isNotEmpty()) { - val orphanedAry = orphaned.toTypedArray() - for (fragment in orphanedAry) { - if (fragment is ScreenFragment) { - if (fragment.screen.container == null) { - detachScreen(it, fragment) - } - } - } - } - - // if there is an "onTop" screen it means the transition has ended - val transitioning = topScreen == null - - // attach newly activated screens - var addedBefore = false - val pendingFront: ArrayList = ArrayList() - - for (screenFragment in mScreenFragments) { - val activityState = getActivityState(screenFragment) - if (activityState !== ActivityState.INACTIVE && !screenFragment.isAdded) { - addedBefore = true - attachScreen(it, screenFragment) - } else if (activityState !== ActivityState.INACTIVE && addedBefore) { - // we detach the screen and then reattach it later to make it appear on front - detachScreen(it, screenFragment) - pendingFront.add(screenFragment) - } - screenFragment.screen.setTransitioning(transitioning) - } - - for (screenFragment in pendingFront) { - attachScreen(it, screenFragment) - } - - it.commitNowAllowingStateLoss() - } - } - - protected open fun notifyContainerUpdate() { - topScreen?.fragment?.onContainerUpdate() - } -} diff --git a/android/expoview/src/main/java/versioned/host/exp/exponent/modules/api/screens/ScreenContainerViewManager.kt b/android/expoview/src/main/java/versioned/host/exp/exponent/modules/api/screens/ScreenContainerViewManager.kt deleted file mode 100644 index a6ca814b6d882..0000000000000 --- a/android/expoview/src/main/java/versioned/host/exp/exponent/modules/api/screens/ScreenContainerViewManager.kt +++ /dev/null @@ -1,52 +0,0 @@ -package versioned.host.exp.exponent.modules.api.screens - -import android.view.View -import com.facebook.react.bridge.ReactApplicationContext -import com.facebook.react.module.annotations.ReactModule -import com.facebook.react.uimanager.LayoutShadowNode -import com.facebook.react.uimanager.ThemedReactContext -import com.facebook.react.uimanager.ViewGroupManager - -@ReactModule(name = ScreenContainerViewManager.REACT_CLASS) -class ScreenContainerViewManager : ViewGroupManager>() { - override fun getName(): String { - return REACT_CLASS - } - - override fun createViewInstance(reactContext: ThemedReactContext): ScreenContainer { - return ScreenContainer(reactContext) - } - - override fun addView(parent: ScreenContainer<*>, child: View, index: Int) { - require(child is Screen) { "Attempt attach child that is not of type RNScreens" } - parent.addScreen(child, index) - } - - override fun removeViewAt(parent: ScreenContainer<*>, index: Int) { - parent.removeScreenAt(index) - } - - override fun removeAllViews(parent: ScreenContainer<*>) { - parent.removeAllScreens() - } - - override fun getChildCount(parent: ScreenContainer<*>): Int { - return parent.screenCount - } - - override fun getChildAt(parent: ScreenContainer<*>, index: Int): View { - return parent.getScreenAt(index) - } - - override fun createShadowNodeInstance(context: ReactApplicationContext): LayoutShadowNode { - return ScreensShadowNode(context) - } - - override fun needsCustomLayoutForChildren(): Boolean { - return true - } - - companion object { - const val REACT_CLASS = "RNSScreenContainer" - } -} diff --git a/android/expoview/src/main/java/versioned/host/exp/exponent/modules/api/screens/ScreenFragment.kt b/android/expoview/src/main/java/versioned/host/exp/exponent/modules/api/screens/ScreenFragment.kt deleted file mode 100644 index 9169e76ac935e..0000000000000 --- a/android/expoview/src/main/java/versioned/host/exp/exponent/modules/api/screens/ScreenFragment.kt +++ /dev/null @@ -1,333 +0,0 @@ -package versioned.host.exp.exponent.modules.api.screens - -import android.annotation.SuppressLint -import android.app.Activity -import android.content.Context -import android.os.Bundle -import android.view.LayoutInflater -import android.view.View -import android.view.ViewGroup -import android.view.ViewParent -import android.widget.FrameLayout -import androidx.fragment.app.Fragment -import com.facebook.react.bridge.ReactContext -import com.facebook.react.bridge.UiThreadUtil -import com.facebook.react.uimanager.UIManagerModule -import com.facebook.react.uimanager.events.Event -import versioned.host.exp.exponent.modules.api.screens.events.HeaderBackButtonClickedEvent -import versioned.host.exp.exponent.modules.api.screens.events.ScreenAppearEvent -import versioned.host.exp.exponent.modules.api.screens.events.ScreenDisappearEvent -import versioned.host.exp.exponent.modules.api.screens.events.ScreenDismissedEvent -import versioned.host.exp.exponent.modules.api.screens.events.ScreenTransitionProgressEvent -import versioned.host.exp.exponent.modules.api.screens.events.ScreenWillAppearEvent -import versioned.host.exp.exponent.modules.api.screens.events.ScreenWillDisappearEvent -import kotlin.math.max -import kotlin.math.min - -open class ScreenFragment : Fragment { - enum class ScreenLifecycleEvent { - Appear, WillAppear, Disappear, WillDisappear - } - - // if we call empty constructor, there is no screen to be assigned so not sure why it is suggested - @Suppress("JoinDeclarationAndAssignment") - lateinit var screen: Screen - private val mChildScreenContainers: MutableList> = ArrayList() - private var shouldUpdateOnResume = false - // if we don't set it, it will be 0.0f at the beginning so the progress will not be sent - // due to progress value being already 0.0f - private var mProgress = -1f - - // those 2 vars are needed since sometimes the events would be dispatched twice in child containers - // (should only happen if parent has `NONE` animation) and we don't need too complicated logic. - // We just check if, after the event was dispatched, its "counter-event" has been also dispatched before sending the same event again. - // We do it for 'willAppear' -> 'willDisappear' and 'appear' -> 'disappear' - private var canDispatchWillAppear = true - private var canDispatchAppear = true - - // we want to know if we are currently transitioning in order not to fire lifecycle events - // in nested fragments. See more explanation in dispatchViewAnimationEvent - private var isTransitioning = false - - constructor() { - throw IllegalStateException( - "Screen fragments should never be restored. Follow instructions from https://github.com/software-mansion/react-native-screens/issues/17#issuecomment-424704067 to properly configure your main activity." - ) - } - - @SuppressLint("ValidFragment") - constructor(screenView: Screen) : super() { - screen = screenView - } - - override fun onResume() { - super.onResume() - if (shouldUpdateOnResume) { - shouldUpdateOnResume = false - ScreenWindowTraits.trySetWindowTraits(screen, tryGetActivity(), tryGetContext()) - } - } - - override fun onCreateView( - inflater: LayoutInflater, - container: ViewGroup?, - savedInstanceState: Bundle? - ): View? { - val wrapper = context?.let { ScreensFrameLayout(it) } - - val params = FrameLayout.LayoutParams( - ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT - ) - screen.layoutParams = params - wrapper?.addView(recycleView(screen)) - return wrapper - } - - private class ScreensFrameLayout( - context: Context, - ) : FrameLayout(context) { - /** - * This method implements a workaround for RN's autoFocus functionality. Because of the way - * autoFocus is implemented it dismisses soft keyboard in fragment transition - * due to change of visibility of the view at the start of the transition. Here we override the - * call to `clearFocus` when the visibility of view is `INVISIBLE` since `clearFocus` triggers the - * hiding of the keyboard in `ReactEditText.java`. - */ - override fun clearFocus() { - if (visibility != INVISIBLE) { - super.clearFocus() - } - } - } - - open fun onContainerUpdate() { - updateWindowTraits() - } - - private fun updateWindowTraits() { - val activity: Activity? = activity - if (activity == null) { - shouldUpdateOnResume = true - return - } - ScreenWindowTraits.trySetWindowTraits(screen, activity, tryGetContext()) - } - - fun tryGetActivity(): Activity? { - activity?.let { return it } - val context = screen.context - if (context is ReactContext && context.currentActivity != null) { - return context.currentActivity - } - var parent: ViewParent? = screen.container - while (parent != null) { - if (parent is Screen) { - val fragment = parent.fragment - fragment?.activity?.let { return it } - } - parent = parent.parent - } - return null - } - - fun tryGetContext(): ReactContext? { - if (context is ReactContext) { - return context as ReactContext - } - if (screen.context is ReactContext) { - return screen.context as ReactContext - } - var parent: ViewParent? = screen.container - while (parent != null) { - if (parent is Screen) { - if (parent.context is ReactContext) { - return parent.context as ReactContext - } - } - parent = parent.parent - } - return null - } - - val childScreenContainers: List> - get() = mChildScreenContainers - - private fun canDispatchEvent(event: ScreenLifecycleEvent): Boolean { - return when (event) { - ScreenLifecycleEvent.WillAppear -> canDispatchWillAppear - ScreenLifecycleEvent.Appear -> canDispatchAppear - ScreenLifecycleEvent.WillDisappear -> !canDispatchWillAppear - ScreenLifecycleEvent.Disappear -> !canDispatchAppear - } - } - - private fun setLastEventDispatched(event: ScreenLifecycleEvent) { - when (event) { - ScreenLifecycleEvent.WillAppear -> canDispatchWillAppear = false - ScreenLifecycleEvent.Appear -> canDispatchAppear = false - ScreenLifecycleEvent.WillDisappear -> canDispatchWillAppear = true - ScreenLifecycleEvent.Disappear -> canDispatchAppear = true - } - } - - private fun dispatchOnWillAppear() { - dispatchEvent(ScreenLifecycleEvent.WillAppear, this) - - dispatchTransitionProgress(0.0f, false) - } - - private fun dispatchOnAppear() { - dispatchEvent(ScreenLifecycleEvent.Appear, this) - - dispatchTransitionProgress(1.0f, false) - } - - private fun dispatchOnWillDisappear() { - dispatchEvent(ScreenLifecycleEvent.WillDisappear, this) - - dispatchTransitionProgress(0.0f, true) - } - - private fun dispatchOnDisappear() { - dispatchEvent(ScreenLifecycleEvent.Disappear, this) - - dispatchTransitionProgress(1.0f, true) - } - - private fun dispatchEvent(event: ScreenLifecycleEvent, fragment: ScreenFragment) { - if (fragment is ScreenStackFragment && fragment.canDispatchEvent(event)) { - fragment.screen.let { - fragment.setLastEventDispatched(event) - val lifecycleEvent: Event<*> = when (event) { - ScreenLifecycleEvent.WillAppear -> ScreenWillAppearEvent(it.id) - ScreenLifecycleEvent.Appear -> ScreenAppearEvent(it.id) - ScreenLifecycleEvent.WillDisappear -> ScreenWillDisappearEvent(it.id) - ScreenLifecycleEvent.Disappear -> ScreenDisappearEvent(it.id) - } - (it.context as ReactContext) - .getNativeModule(UIManagerModule::class.java) - ?.eventDispatcher - ?.dispatchEvent(lifecycleEvent) - fragment.dispatchEventInChildContainers(event) - } - } - } - - private fun dispatchEventInChildContainers(event: ScreenLifecycleEvent) { - for (sc in mChildScreenContainers) { - if (sc.screenCount > 0) { - sc.topScreen?.let { - sc.topScreen?.fragment?.let { fragment -> dispatchEvent(event, fragment) } - } - } - } - } - - fun dispatchHeaderBackButtonClickedEvent() { - (screen.context as ReactContext) - .getNativeModule(UIManagerModule::class.java) - ?.eventDispatcher - ?.dispatchEvent(HeaderBackButtonClickedEvent(screen.id)) - } - - fun dispatchTransitionProgress(alpha: Float, closing: Boolean) { - if (this is ScreenStackFragment) { - if (mProgress != alpha) { - mProgress = max(0.0f, min(1.0f, alpha)) - /* We want value of 0 and 1 to be always dispatched so we base coalescing key on the progress: - - progress is 0 -> key 1 - - progress is 1 -> key 2 - - progress is between 0 and 1 -> key 3 - */ - val coalescingKey = (if (mProgress == 0.0f) 1 else if (mProgress == 1.0f) 2 else 3).toShort() - val container: ScreenContainer<*>? = screen.container - val goingForward = if (container is ScreenStack) container.goingForward else false - (screen.context as ReactContext) - .getNativeModule(UIManagerModule::class.java) - ?.eventDispatcher - ?.dispatchEvent( - ScreenTransitionProgressEvent( - screen.id, mProgress, closing, goingForward, coalescingKey - ) - ) - } - } - } - - fun registerChildScreenContainer(screenContainer: ScreenContainer<*>) { - mChildScreenContainers.add(screenContainer) - } - - fun unregisterChildScreenContainer(screenContainer: ScreenContainer<*>) { - mChildScreenContainers.remove(screenContainer) - } - - fun onViewAnimationStart() { - dispatchViewAnimationEvent(false) - } - - open fun onViewAnimationEnd() { - dispatchViewAnimationEvent(true) - } - - private fun dispatchViewAnimationEvent(animationEnd: Boolean) { - isTransitioning = !animationEnd - // if parent fragment is transitioning, we do not want the events dispatched from the child, - // since we subscribe to parent's animation start/end and dispatch events in child from there - // check for `isTransitioning` should be enough since the child's animation should take only - // 20ms due to always being `StackAnimation.NONE` when nested stack being pushed - val parent = parentFragment - if (parent == null || (parent is ScreenFragment && !parent.isTransitioning)) { - // onViewAnimationStart/End is triggered from View#onAnimationStart/End method of the fragment's root - // view. We override an appropriate method of the StackFragment's - // root view in order to achieve this. - if (isResumed) { - // Android dispatches the animation start event for the fragment that is being added first - // however we want the one being dismissed first to match iOS. It also makes more sense from - // a navigation point of view to have the disappear event first. - // Since there are no explicit relationships between the fragment being added / removed the - // practical way to fix this is delaying dispatching the appear events at the end of the - // frame. - UiThreadUtil.runOnUiThread { - if (animationEnd) dispatchOnAppear() else dispatchOnWillAppear() - } - } else { - if (animationEnd) dispatchOnDisappear() else dispatchOnWillDisappear() - } - } - } - - override fun onDestroy() { - super.onDestroy() - val container = screen.container - if (container == null || !container.hasScreen(this)) { - // we only send dismissed even when the screen has been removed from its container - if (screen.context is ReactContext) { - (screen.context as ReactContext) - .getNativeModule(UIManagerModule::class.java) - ?.eventDispatcher - ?.dispatchEvent(ScreenDismissedEvent(screen.id)) - } - } - mChildScreenContainers.clear() - } - - companion object { - @JvmStatic - protected fun recycleView(view: View): View { - // screen fragments reuse view instances instead of creating new ones. In order to reuse a given - // view it needs to be detached from the view hierarchy to allow the fragment to attach it back. - val parent = view.parent - if (parent != null) { - (parent as ViewGroup).endViewTransition(view) - parent.removeView(view) - } - - // view detached from fragment manager get their visibility changed to GONE after their state is - // dumped. Since we don't restore the state but want to reuse the view we need to change - // visibility back to VISIBLE in order for the fragment manager to animate in the view. - view.visibility = View.VISIBLE - return view - } - } -} diff --git a/android/expoview/src/main/java/versioned/host/exp/exponent/modules/api/screens/ScreenStack.kt b/android/expoview/src/main/java/versioned/host/exp/exponent/modules/api/screens/ScreenStack.kt deleted file mode 100644 index 4e6a4269c2038..0000000000000 --- a/android/expoview/src/main/java/versioned/host/exp/exponent/modules/api/screens/ScreenStack.kt +++ /dev/null @@ -1,353 +0,0 @@ -package versioned.host.exp.exponent.modules.api.screens - -import android.content.Context -import android.graphics.Canvas -import android.view.View -import com.facebook.react.bridge.ReactContext -import com.facebook.react.uimanager.UIManagerModule -import versioned.host.exp.exponent.modules.api.screens.Screen.StackAnimation -import versioned.host.exp.exponent.modules.api.screens.events.StackFinishTransitioningEvent -import java.util.Collections -import kotlin.collections.ArrayList -import kotlin.collections.HashSet - -import host.exp.expoview.R - -class ScreenStack(context: Context?) : ScreenContainer(context) { - private val mStack = ArrayList() - private val mDismissed: MutableSet = HashSet() - private val drawingOpPool: MutableList = ArrayList() - private var drawingOps: MutableList = ArrayList() - private var mTopScreen: ScreenStackFragment? = null - private var mRemovalTransitionStarted = false - private var isDetachingCurrentScreen = false - private var reverseLastTwoChildren = false - private var previousChildrenCount = 0 - var goingForward = false - fun dismiss(screenFragment: ScreenStackFragment) { - mDismissed.add(screenFragment) - performUpdatesNow() - } - - override val topScreen: Screen? - get() = mTopScreen?.screen - val rootScreen: Screen - get() { - var i = 0 - val size = screenCount - while (i < size) { - val screen = getScreenAt(i) - if (!mDismissed.contains(screen.fragment)) { - return screen - } - i++ - } - throw IllegalStateException("Stack has no root screen set") - } - - override fun adapt(screen: Screen): ScreenStackFragment { - return ScreenStackFragment(screen) - } - - override fun startViewTransition(view: View) { - super.startViewTransition(view) - mRemovalTransitionStarted = true - } - - override fun endViewTransition(view: View) { - super.endViewTransition(view) - if (mRemovalTransitionStarted) { - mRemovalTransitionStarted = false - dispatchOnFinishTransitioning() - } - } - - fun onViewAppearTransitionEnd() { - if (!mRemovalTransitionStarted) { - dispatchOnFinishTransitioning() - } - } - - private fun dispatchOnFinishTransitioning() { - (context as ReactContext) - .getNativeModule(UIManagerModule::class.java) - ?.eventDispatcher - ?.dispatchEvent(StackFinishTransitioningEvent(id)) - } - - override fun removeScreenAt(index: Int) { - val toBeRemoved = getScreenAt(index) - mDismissed.remove(toBeRemoved.fragment) - super.removeScreenAt(index) - } - - override fun removeAllScreens() { - mDismissed.clear() - super.removeAllScreens() - } - - override fun hasScreen(screenFragment: ScreenFragment?): Boolean { - return super.hasScreen(screenFragment) && !mDismissed.contains(screenFragment) - } - - override fun onUpdate() { - // When going back from a nested stack with a single screen on it, we may hit an edge case - // when all screens are dismissed and no screen is to be displayed on top. We need to gracefully - // handle the case of newTop being NULL, which happens in several places below - var newTop: ScreenStackFragment? = null // newTop is nullable, see the above comment ^ - var visibleBottom: ScreenStackFragment? = null // this is only set if newTop has TRANSPARENT_MODAL presentation mode - isDetachingCurrentScreen = false // we reset it so the previous value is not used by mistake - for (i in mScreenFragments.indices.reversed()) { - val screen = mScreenFragments[i] - if (!mDismissed.contains(screen)) { - if (newTop == null) { - newTop = screen - } else { - visibleBottom = screen - } - if (!isTransparent(screen)) { - break - } - } - } - var shouldUseOpenAnimation = true - var stackAnimation: StackAnimation? = null - if (!mStack.contains(newTop)) { - // if new top screen wasn't on stack we do "open animation" so long it is not the very first - // screen on stack - if (mTopScreen != null && newTop != null) { - // there was some other screen attached before - // if the previous top screen does not exist anymore and the new top was not on the stack - // before, probably replace or reset was called, so we play the "close animation". - // Otherwise it's open animation - val containsTopScreen = mTopScreen?.let { mScreenFragments.contains(it) } == true - val isPushReplace = newTop.screen.replaceAnimation === Screen.ReplaceAnimation.PUSH - shouldUseOpenAnimation = containsTopScreen || isPushReplace - // if the replace animation is `push`, the new top screen provides the animation, otherwise the previous one - stackAnimation = if (shouldUseOpenAnimation) newTop.screen.stackAnimation else mTopScreen?.screen?.stackAnimation - } else if (mTopScreen == null && newTop != null) { - // mTopScreen was not present before so newTop is the first screen added to a stack - // and we don't want the animation when it is entering - stackAnimation = StackAnimation.NONE - goingForward = true - } - } else if (mTopScreen != null && mTopScreen != newTop) { - // otherwise if we are performing top screen change we do "close animation" - shouldUseOpenAnimation = false - stackAnimation = mTopScreen?.screen?.stackAnimation - } - - createTransaction().let { - // animation logic start - if (stackAnimation != null) { - if (shouldUseOpenAnimation) { - when (stackAnimation) { - StackAnimation.DEFAULT -> it.setCustomAnimations(R.anim.rns_default_enter_in, R.anim.rns_default_enter_out) - StackAnimation.NONE -> it.setCustomAnimations(R.anim.rns_no_animation_20, R.anim.rns_no_animation_20) - StackAnimation.FADE -> it.setCustomAnimations(R.anim.rns_fade_in, R.anim.rns_fade_out) - StackAnimation.SLIDE_FROM_RIGHT -> it.setCustomAnimations(R.anim.rns_slide_in_from_right, R.anim.rns_slide_out_to_left) - StackAnimation.SLIDE_FROM_LEFT -> it.setCustomAnimations(R.anim.rns_slide_in_from_left, R.anim.rns_slide_out_to_right) - StackAnimation.SLIDE_FROM_BOTTOM -> it.setCustomAnimations( - R.anim.rns_slide_in_from_bottom, R.anim.rns_no_animation_medium - ) - StackAnimation.FADE_FROM_BOTTOM -> it.setCustomAnimations(R.anim.rns_fade_from_bottom, R.anim.rns_no_animation_350) - } - } else { - when (stackAnimation) { - StackAnimation.DEFAULT -> it.setCustomAnimations(R.anim.rns_default_exit_in, R.anim.rns_default_exit_out) - StackAnimation.NONE -> it.setCustomAnimations(R.anim.rns_no_animation_20, R.anim.rns_no_animation_20) - StackAnimation.FADE -> it.setCustomAnimations(R.anim.rns_fade_in, R.anim.rns_fade_out) - StackAnimation.SLIDE_FROM_RIGHT -> it.setCustomAnimations(R.anim.rns_slide_in_from_left, R.anim.rns_slide_out_to_right) - StackAnimation.SLIDE_FROM_LEFT -> it.setCustomAnimations(R.anim.rns_slide_in_from_right, R.anim.rns_slide_out_to_left) - StackAnimation.SLIDE_FROM_BOTTOM -> it.setCustomAnimations( - R.anim.rns_no_animation_medium, R.anim.rns_slide_out_to_bottom - ) - StackAnimation.FADE_FROM_BOTTOM -> it.setCustomAnimations(R.anim.rns_no_animation_250, R.anim.rns_fade_to_bottom) - } - } - } - - // animation logic end - goingForward = shouldUseOpenAnimation - - if (shouldUseOpenAnimation && - newTop != null && needsDrawReordering(newTop) && - visibleBottom == null - ) { - // When using an open animation in which two screens overlap (eg. fade_from_bottom or - // slide_from_bottom), we want to draw the previous screen under the new one, - // which is apparently not the default option. Android always draws the disappearing view - // on top of the appearing one. We then reverse the order of the views so the new screen - // appears on top of the previous one. You can read more about in the comment - // for the code we use to change that behavior: - // https://github.com/airbnb/native-navigation/blob/9cf50bf9b751b40778f473f3b19fcfe2c4d40599/lib/android/src/main/java/com/airbnb/android/react/navigation/ScreenCoordinatorLayout.java#L18 - isDetachingCurrentScreen = true - } - - // remove all screens previously on stack - for (screen in mStack) { - if (!mScreenFragments.contains(screen) || mDismissed.contains(screen)) { - it.remove(screen) - } - } - for (screen in mScreenFragments) { - // Stop detaching screens when reaching visible bottom. All screens above bottom should be - // visible. - if (screen === visibleBottom) { - break - } - // detach all screens that should not be visible - if (screen !== newTop && !mDismissed.contains(screen)) { - it.remove(screen) - } - } - - // attach screens that just became visible - if (visibleBottom != null && !visibleBottom.isAdded) { - val top = newTop - var beneathVisibleBottom = true - for (screen in mScreenFragments) { - // ignore all screens beneath the visible bottom - if (beneathVisibleBottom) { - beneathVisibleBottom = if (screen === visibleBottom) { - false - } else continue - } - // when first visible screen found, make all screens after that visible - it.add(id, screen).runOnCommit { top?.screen?.bringToFront() } - } - } else if (newTop != null && !newTop.isAdded) { - it.add(id, newTop) - } - mTopScreen = newTop - mStack.clear() - mStack.addAll(mScreenFragments) - - turnOffA11yUnderTransparentScreen(visibleBottom) - - it.commitNowAllowingStateLoss() - } - } - - // only top visible screen should be accessible - private fun turnOffA11yUnderTransparentScreen(visibleBottom: ScreenStackFragment?) { - if (mScreenFragments.size > 1 && visibleBottom != null) { - mTopScreen?.let { - if (isTransparent(it)) { - val screenFragmentsBeneathTop = mScreenFragments.slice(0 until mScreenFragments.size - 1).asReversed() - // go from the top of the stack excluding the top screen - for (screenFragment in screenFragmentsBeneathTop) { - screenFragment.screen.changeAccessibilityMode(IMPORTANT_FOR_ACCESSIBILITY_NO_HIDE_DESCENDANTS) - - // don't change a11y below non-transparent screens - if (screenFragment == visibleBottom) { - break - } - } - } - } - } - - topScreen?.changeAccessibilityMode(IMPORTANT_FOR_ACCESSIBILITY_AUTO) - } - - override fun notifyContainerUpdate() { - for (screen in mStack) { - screen.onContainerUpdate() - } - } - - // below methods are taken from - // https://github.com/airbnb/native-navigation/blob/9cf50bf9b751b40778f473f3b19fcfe2c4d40599/lib/android/src/main/java/com/airbnb/android/react/navigation/ScreenCoordinatorLayout.java#L43 - // and are used to swap the order of drawing views when navigating forward with the transitions - // that are making transitioning fragments appear one on another. See more info in the comment to - // the linked class. - override fun removeView(view: View) { - // we set this property to reverse the order of drawing views - // when we want to push new fragment on top of the previous one and their animations collide. - // More information in: - // https://github.com/airbnb/native-navigation/blob/9cf50bf9b751b40778f473f3b19fcfe2c4d40599/lib/android/src/main/java/com/airbnb/android/react/navigation/ScreenCoordinatorLayout.java#L17 - if (isDetachingCurrentScreen) { - isDetachingCurrentScreen = false - reverseLastTwoChildren = true - } - super.removeView(view) - } - - private fun drawAndRelease() { - // We make a copy of the drawingOps and use it to dispatch draws in order to be sure - // that we do not modify the original list. There are cases when `op.draw` can call - // `drawChild` which would modify the list through which we are iterating. See more: - // https://github.com/software-mansion/react-native-screens/pull/1406 - val drawingOpsCopy = drawingOps - drawingOps = ArrayList() - for (op in drawingOpsCopy) { - op.draw() - drawingOpPool.add(op) - } - } - - override fun dispatchDraw(canvas: Canvas) { - super.dispatchDraw(canvas) - - // check the view removal is completed (by comparing the previous children count) - if (drawingOps.size < previousChildrenCount) { - reverseLastTwoChildren = false - } - previousChildrenCount = drawingOps.size - if (reverseLastTwoChildren && drawingOps.size >= 2) { - Collections.swap(drawingOps, drawingOps.size - 1, drawingOps.size - 2) - } - drawAndRelease() - } - - override fun drawChild(canvas: Canvas, child: View, drawingTime: Long): Boolean { - drawingOps.add(obtainDrawingOp().set(canvas, child, drawingTime)) - return true - } - - private fun performDraw(op: DrawingOp) { - super.drawChild(op.canvas, op.child, op.drawingTime) - } - - private fun obtainDrawingOp(): DrawingOp { - return if (drawingOpPool.isEmpty()) { - DrawingOp() - } else drawingOpPool.removeAt(drawingOpPool.size - 1) - } - - private inner class DrawingOp { - var canvas: Canvas? = null - var child: View? = null - var drawingTime: Long = 0 - operator fun set(canvas: Canvas?, child: View?, drawingTime: Long): DrawingOp { - this.canvas = canvas - this.child = child - this.drawingTime = drawingTime - return this - } - - fun draw() { - performDraw(this) - canvas = null - child = null - drawingTime = 0 - } - } - - companion object { - private fun isTransparent(fragment: ScreenStackFragment): Boolean { - return ( - fragment.screen.stackPresentation - === Screen.StackPresentation.TRANSPARENT_MODAL - ) - } - - private fun needsDrawReordering(fragment: ScreenStackFragment): Boolean { - return ( - fragment.screen.stackAnimation === StackAnimation.SLIDE_FROM_BOTTOM || - fragment.screen.stackAnimation === StackAnimation.FADE_FROM_BOTTOM - ) - } - } -} diff --git a/android/expoview/src/main/java/versioned/host/exp/exponent/modules/api/screens/ScreenStackFragment.kt b/android/expoview/src/main/java/versioned/host/exp/exponent/modules/api/screens/ScreenStackFragment.kt deleted file mode 100644 index a266321d224bd..0000000000000 --- a/android/expoview/src/main/java/versioned/host/exp/exponent/modules/api/screens/ScreenStackFragment.kt +++ /dev/null @@ -1,252 +0,0 @@ -package versioned.host.exp.exponent.modules.api.screens - -import android.annotation.SuppressLint -import android.content.Context -import android.graphics.Color -import android.os.Bundle -import android.view.LayoutInflater -import android.view.Menu -import android.view.MenuInflater -import android.view.MenuItem -import android.view.View -import android.view.ViewGroup -import android.view.animation.Animation -import android.view.animation.AnimationSet -import android.view.animation.Transformation -import android.widget.LinearLayout -import androidx.appcompat.widget.Toolbar -import androidx.coordinatorlayout.widget.CoordinatorLayout -import com.facebook.react.uimanager.PixelUtil -import com.google.android.material.appbar.AppBarLayout -import com.google.android.material.appbar.AppBarLayout.ScrollingViewBehavior - -class ScreenStackFragment : ScreenFragment { - private var mAppBarLayout: AppBarLayout? = null - private var mToolbar: Toolbar? = null - private var mShadowHidden = false - private var mIsTranslucent = false - - var searchView: CustomSearchView? = null - var onSearchViewCreate: ((searchView: CustomSearchView) -> Unit)? = null - - @SuppressLint("ValidFragment") - constructor(screenView: Screen) : super(screenView) - - constructor() { - throw IllegalStateException( - "ScreenStack fragments should never be restored. Follow instructions from https://github.com/software-mansion/react-native-screens/issues/17#issuecomment-424704067 to properly configure your main activity." - ) - } - - fun removeToolbar() { - mAppBarLayout?.let { - mToolbar?.let { toolbar -> - if (toolbar.parent === it) { - it.removeView(toolbar) - } - } - } - mToolbar = null - } - - fun setToolbar(toolbar: Toolbar) { - mAppBarLayout?.addView(toolbar) - val params = AppBarLayout.LayoutParams( - AppBarLayout.LayoutParams.MATCH_PARENT, AppBarLayout.LayoutParams.WRAP_CONTENT - ) - params.scrollFlags = 0 - toolbar.layoutParams = params - mToolbar = toolbar - } - - fun setToolbarShadowHidden(hidden: Boolean) { - if (mShadowHidden != hidden) { - mAppBarLayout?.targetElevation = if (hidden) 0f else PixelUtil.toPixelFromDIP(4f) - mShadowHidden = hidden - } - } - - fun setToolbarTranslucent(translucent: Boolean) { - if (mIsTranslucent != translucent) { - val params = screen.layoutParams - (params as CoordinatorLayout.LayoutParams).behavior = - if (translucent) null else ScrollingViewBehavior() - mIsTranslucent = translucent - } - } - - override fun onContainerUpdate() { - val headerConfig = screen.headerConfig - headerConfig?.onUpdate() - } - - override fun onViewAnimationEnd() { - super.onViewAnimationEnd() - notifyViewAppearTransitionEnd() - } - - private fun notifyViewAppearTransitionEnd() { - val screenStack = view?.parent - if (screenStack is ScreenStack) { - screenStack.onViewAppearTransitionEnd() - } - } - - override fun onCreateView( - inflater: LayoutInflater, - container: ViewGroup?, - savedInstanceState: Bundle? - ): View? { - val view: ScreensCoordinatorLayout? = - context?.let { ScreensCoordinatorLayout(it, this) } - val params = CoordinatorLayout.LayoutParams( - LinearLayout.LayoutParams.MATCH_PARENT, LinearLayout.LayoutParams.MATCH_PARENT - ) - params.behavior = if (mIsTranslucent) null else ScrollingViewBehavior() - screen.layoutParams = params - view?.addView(recycleView(screen)) - - mAppBarLayout = context?.let { AppBarLayout(it) } - // By default AppBarLayout will have a background color set but since we cover the whole layout - // with toolbar (that can be semi-transparent) the bar layout background color does not pay a - // role. On top of that it breaks screens animations when alfa offscreen compositing is off - // (which is the default) - mAppBarLayout?.setBackgroundColor(Color.TRANSPARENT) - mAppBarLayout?.layoutParams = AppBarLayout.LayoutParams( - AppBarLayout.LayoutParams.MATCH_PARENT, AppBarLayout.LayoutParams.WRAP_CONTENT - ) - view?.addView(mAppBarLayout) - if (mShadowHidden) { - mAppBarLayout?.targetElevation = 0f - } - mToolbar?.let { mAppBarLayout?.addView(recycleView(it)) } - setHasOptionsMenu(true) - return view - } - - override fun onPrepareOptionsMenu(menu: Menu) { - updateToolbarMenu(menu) - return super.onPrepareOptionsMenu(menu) - } - - override fun onCreateOptionsMenu(menu: Menu, inflater: MenuInflater) { - updateToolbarMenu(menu) - return super.onCreateOptionsMenu(menu, inflater) - } - - private fun shouldShowSearchBar(): Boolean { - val config = screen.headerConfig - val numberOfSubViews = config?.configSubviewsCount ?: 0 - if (config != null && numberOfSubViews > 0) { - for (i in 0 until numberOfSubViews) { - val subView = config.getConfigSubview(i) - if (subView.type == ScreenStackHeaderSubview.Type.SEARCH_BAR) { - return true - } - } - } - return false - } - - private fun updateToolbarMenu(menu: Menu) { - menu.clear() - if (shouldShowSearchBar()) { - val currentContext = context - if (searchView == null && currentContext != null) { - val newSearchView = CustomSearchView(currentContext, this) - searchView = newSearchView - onSearchViewCreate?.invoke(newSearchView) - } - val item = menu.add("") - item.setShowAsAction(MenuItem.SHOW_AS_ACTION_ALWAYS) - item.actionView = searchView - } - } - - fun canNavigateBack(): Boolean { - val container: ScreenContainer<*>? = screen.container - check(container is ScreenStack) { "ScreenStackFragment added into a non-stack container" } - return if (container.rootScreen == screen) { - // this screen is the root of the container, if it is nested we can check parent container - // if it is also a root or not - val parentFragment = parentFragment - if (parentFragment is ScreenStackFragment) { - parentFragment.canNavigateBack() - } else { - false - } - } else { - true - } - } - - fun dismiss() { - val container: ScreenContainer<*>? = screen.container - check(container is ScreenStack) { "ScreenStackFragment added into a non-stack container" } - container.dismiss(this) - } - - private class ScreensCoordinatorLayout( - context: Context, - private val mFragment: ScreenFragment - ) : CoordinatorLayout(context) { - private val mAnimationListener: Animation.AnimationListener = - object : Animation.AnimationListener { - override fun onAnimationStart(animation: Animation) { - mFragment.onViewAnimationStart() - } - - override fun onAnimationEnd(animation: Animation) { - mFragment.onViewAnimationEnd() - } - - override fun onAnimationRepeat(animation: Animation) {} - } - - override fun startAnimation(animation: Animation) { - // For some reason View##onAnimationEnd doesn't get called for - // exit transitions so we explicitly attach animation listener. - // We also have some animations that are an AnimationSet, so we don't wrap them - // in another set since it causes some visual glitches when going forward. - // We also set the listener only when going forward, since when going back, - // there is already a listener for dismiss action added, which would be overridden - // and also this is not necessary when going back since the lifecycle methods - // are correctly dispatched then. - // We also add fakeAnimation to the set of animations, which sends the progress of animation - val fakeAnimation = ScreensAnimation(mFragment) - fakeAnimation.duration = animation.duration - if (animation is AnimationSet && !mFragment.isRemoving) { - animation.addAnimation(fakeAnimation) - animation.setAnimationListener(mAnimationListener) - super.startAnimation(animation) - } else { - val set = AnimationSet(true) - set.addAnimation(animation) - set.addAnimation(fakeAnimation) - set.setAnimationListener(mAnimationListener) - super.startAnimation(set) - } - } - - /** - * This method implements a workaround for RN's autoFocus functionality. Because of the way - * autoFocus is implemented it dismisses soft keyboard in fragment transition - * due to change of visibility of the view at the start of the transition. Here we override the - * call to `clearFocus` when the visibility of view is `INVISIBLE` since `clearFocus` triggers the - * hiding of the keyboard in `ReactEditText.java`. - */ - override fun clearFocus() { - if (visibility != INVISIBLE) { - super.clearFocus() - } - } - } - - private class ScreensAnimation(private val mFragment: ScreenFragment) : Animation() { - override fun applyTransformation(interpolatedTime: Float, t: Transformation) { - super.applyTransformation(interpolatedTime, t) - // interpolated time should be the progress of the current transition - mFragment.dispatchTransitionProgress(interpolatedTime, !mFragment.isResumed) - } - } -} diff --git a/android/expoview/src/main/java/versioned/host/exp/exponent/modules/api/screens/ScreenStackHeaderConfig.kt b/android/expoview/src/main/java/versioned/host/exp/exponent/modules/api/screens/ScreenStackHeaderConfig.kt deleted file mode 100644 index 111132fb2b72d..0000000000000 --- a/android/expoview/src/main/java/versioned/host/exp/exponent/modules/api/screens/ScreenStackHeaderConfig.kt +++ /dev/null @@ -1,414 +0,0 @@ -package versioned.host.exp.exponent.modules.api.screens - -import android.annotation.SuppressLint -import android.content.Context -import android.graphics.PorterDuff -import android.os.Build -import android.text.TextUtils -import android.util.TypedValue -import android.view.Gravity -import android.view.View.OnClickListener -import android.view.ViewGroup -import android.widget.ImageView -import android.widget.TextView -import androidx.appcompat.app.AppCompatActivity -import androidx.appcompat.widget.Toolbar -import androidx.fragment.app.Fragment -import com.facebook.react.ReactApplication -import com.facebook.react.bridge.JSApplicationIllegalArgumentException -import com.facebook.react.bridge.ReactContext -import com.facebook.react.bridge.WritableMap -import com.facebook.react.uimanager.events.RCTEventEmitter -import com.facebook.react.views.text.ReactTypefaceUtils - -import host.exp.expoview.BuildConfig -import host.exp.expoview.R - -class ScreenStackHeaderConfig(context: Context) : ViewGroup(context) { - private val mConfigSubviews = ArrayList(3) - val toolbar: CustomToolbar - private var headerTopInset: Int? = null - private var mTitle: String? = null - private var mTitleColor = 0 - private var mTitleFontFamily: String? = null - private var mDirection: String? = null - private var mTitleFontSize = 0f - private var mTitleFontWeight = 0 - private var mBackgroundColor: Int? = null - private var mIsHidden = false - private var mIsBackButtonHidden = false - private var mIsShadowHidden = false - private var mDestroyed = false - private var mBackButtonInCustomView = false - private var mIsTopInsetEnabled = true - private var mIsTranslucent = false - private var mTintColor = 0 - private var mIsAttachedToWindow = false - private val mDefaultStartInset: Int - private val mDefaultStartInsetWithNavigation: Int - private val mBackClickListener = OnClickListener { - screenFragment?.let { - val stack = screenStack - if (stack != null && stack.rootScreen == it.screen) { - val parentFragment = it.parentFragment - if (parentFragment is ScreenStackFragment) { - if (parentFragment.screen.nativeBackButtonDismissalEnabled) { - parentFragment.dismiss() - } else { - parentFragment.dispatchHeaderBackButtonClickedEvent() - } - } - } else { - if (it.screen.nativeBackButtonDismissalEnabled) { - it.dismiss() - } else { - it.dispatchHeaderBackButtonClickedEvent() - } - } - } - } - - private fun sendEvent(eventName: String, eventContent: WritableMap?) { - (context as ReactContext).getJSModule(RCTEventEmitter::class.java) - ?.receiveEvent(id, eventName, eventContent) - } - - override fun onLayout(changed: Boolean, l: Int, t: Int, r: Int, b: Int) { - // no-op - } - - fun destroy() { - mDestroyed = true - } - - override fun onAttachedToWindow() { - super.onAttachedToWindow() - mIsAttachedToWindow = true - sendEvent("onAttached", null) - // we want to save the top inset before the status bar can be hidden, which would resolve in - // inset being 0 - if (headerTopInset == null) { - headerTopInset = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) - rootWindowInsets.systemWindowInsetTop - else - // Hacky fallback for old android. Before Marshmallow, the status bar height was always 25 - (25 * resources.displayMetrics.density).toInt() - } - onUpdate() - } - - override fun onDetachedFromWindow() { - super.onDetachedFromWindow() - mIsAttachedToWindow = false - sendEvent("onDetached", null) - } - - private val screen: Screen? - get() { - val screen = parent - return if (screen is Screen) { - screen - } else null - } - private val screenStack: ScreenStack? - get() { - val screen = screen - if (screen != null) { - val container = screen.container - if (container is ScreenStack) { - return container - } - } - return null - } - val screenFragment: ScreenStackFragment? - get() { - val screen = parent - if (screen is Screen) { - val fragment: Fragment? = screen.fragment - if (fragment is ScreenStackFragment) { - return fragment - } - } - return null - } - - @SuppressLint("ObsoleteSdkInt") // to be removed when support for < 0.64 is dropped - fun onUpdate() { - val stack = screenStack - val isTop = stack == null || stack.topScreen == parent - if (!mIsAttachedToWindow || !isTop || mDestroyed) { - return - } - val activity = screenFragment?.activity as AppCompatActivity? ?: return - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR1 && mDirection != null) { - if (mDirection == "rtl") { - toolbar.layoutDirection = LAYOUT_DIRECTION_RTL - } else if (mDirection == "ltr") { - toolbar.layoutDirection = LAYOUT_DIRECTION_LTR - } - } - - // orientation and status bar management - screen?.let { - // we set the traits here too, not only when the prop for Screen is passed - // because sometimes we don't have the Fragment and Activity available then yet, e.g. on the - // first setting of props. Similar thing is done for Screens of ScreenContainers, but in - // `onContainerUpdate` of their Fragment - val reactContext = if (context is ReactContext) { - context as ReactContext - } else { - it.fragment?.tryGetContext() - } - ScreenWindowTraits.trySetWindowTraits(it, activity, reactContext) - } - if (mIsHidden) { - if (toolbar.parent != null) { - screenFragment?.removeToolbar() - } - return - } - if (toolbar.parent == null) { - screenFragment?.setToolbar(toolbar) - } - if (mIsTopInsetEnabled) { - headerTopInset.let { - toolbar.setPadding(0, it ?: 0, 0, 0) - } - } else { - if (toolbar.paddingTop > 0) { - toolbar.setPadding(0, 0, 0, 0) - } - } - activity.setSupportActionBar(toolbar) - // non-null toolbar is set in the line above and it is used here - val actionBar = requireNotNull(activity.supportActionBar) - - // Reset toolbar insets. By default we set symmetric inset for start and end to match iOS - // implementation where both right and left icons are offset from the edge by default. We also - // reset startWithNavigation inset which corresponds to the distance between navigation icon and - // title. If title isn't set we clear that value few lines below to give more space to custom - // center-mounted views. - toolbar.contentInsetStartWithNavigation = mDefaultStartInsetWithNavigation - toolbar.setContentInsetsRelative(mDefaultStartInset, mDefaultStartInset) - - // hide back button - actionBar.setDisplayHomeAsUpEnabled( - screenFragment?.canNavigateBack() == true && !mIsBackButtonHidden - ) - - // when setSupportActionBar is called a toolbar wrapper gets initialized that overwrites - // navigation click listener. The default behavior set in the wrapper is to call into - // menu options handlers, but we prefer the back handling logic to stay here instead. - toolbar.setNavigationOnClickListener(mBackClickListener) - - // shadow - screenFragment?.setToolbarShadowHidden(mIsShadowHidden) - - // translucent - screenFragment?.setToolbarTranslucent(mIsTranslucent) - - // title - actionBar.title = mTitle - if (TextUtils.isEmpty(mTitle)) { - // if title is empty we set start navigation inset to 0 to give more space to custom rendered - // views. When it is set to default it'd take up additional distance from the back button - // which would impact the position of custom header views rendered at the center. - toolbar.contentInsetStartWithNavigation = 0 - } - val titleTextView = titleTextView - if (mTitleColor != 0) { - toolbar.setTitleTextColor(mTitleColor) - } - if (titleTextView != null) { - if (mTitleFontFamily != null || mTitleFontWeight > 0) { - val titleTypeface = ReactTypefaceUtils.applyStyles( - null, 0, mTitleFontWeight, mTitleFontFamily, context.assets - ) - titleTextView.typeface = titleTypeface - } - if (mTitleFontSize > 0) { - titleTextView.textSize = mTitleFontSize - } - } - - // background - mBackgroundColor?.let { toolbar.setBackgroundColor(it) } - - // color - if (mTintColor != 0) { - val navigationIcon = toolbar.navigationIcon - navigationIcon?.setColorFilter(mTintColor, PorterDuff.Mode.SRC_ATOP) - } - - // subviews - for (i in toolbar.childCount - 1 downTo 0) { - if (toolbar.getChildAt(i) is ScreenStackHeaderSubview) { - toolbar.removeViewAt(i) - } - } - var i = 0 - val size = mConfigSubviews.size - while (i < size) { - val view = mConfigSubviews[i] - val type = view.type - if (type === ScreenStackHeaderSubview.Type.BACK) { - // we special case BACK button header config type as we don't add it as a view into toolbar - // but instead just copy the drawable from imageview that's added as a first child to it. - val firstChild = view.getChildAt(0) as? ImageView - ?: throw JSApplicationIllegalArgumentException( - "Back button header config view should have Image as first child" - ) - actionBar.setHomeAsUpIndicator(firstChild.drawable) - i++ - continue - } - val params = Toolbar.LayoutParams(LayoutParams.WRAP_CONTENT, LayoutParams.MATCH_PARENT) - when (type) { - ScreenStackHeaderSubview.Type.LEFT -> { - // when there is a left item we need to disable navigation icon by default - // we also hide title as there is no other way to display left side items - if (!mBackButtonInCustomView) { - toolbar.navigationIcon = null - } - toolbar.title = null - params.gravity = Gravity.START - } - ScreenStackHeaderSubview.Type.RIGHT -> params.gravity = Gravity.END - ScreenStackHeaderSubview.Type.CENTER -> { - params.width = LayoutParams.MATCH_PARENT - params.gravity = Gravity.CENTER_HORIZONTAL - toolbar.title = null - } - else -> {} - } - view.layoutParams = params - toolbar.addView(view) - i++ - } - } - - private fun maybeUpdate() { - if (parent != null && !mDestroyed) { - onUpdate() - } - } - - fun getConfigSubview(index: Int): ScreenStackHeaderSubview { - return mConfigSubviews[index] - } - - val configSubviewsCount: Int - get() = mConfigSubviews.size - - fun removeConfigSubview(index: Int) { - mConfigSubviews.removeAt(index) - maybeUpdate() - } - - fun removeAllConfigSubviews() { - mConfigSubviews.clear() - maybeUpdate() - } - - fun addConfigSubview(child: ScreenStackHeaderSubview, index: Int) { - mConfigSubviews.add(index, child) - maybeUpdate() - } - - private val titleTextView: TextView? - get() { - var i = 0 - val size = toolbar.childCount - while (i < size) { - val view = toolbar.getChildAt(i) - if (view is TextView) { - if (view.text == toolbar.title) { - return view - } - } - i++ - } - return null - } - - fun setTitle(title: String?) { - mTitle = title - } - - fun setTitleFontFamily(titleFontFamily: String?) { - mTitleFontFamily = titleFontFamily - } - - fun setTitleFontWeight(fontWeightString: String?) { - mTitleFontWeight = ReactTypefaceUtils.parseFontWeight(fontWeightString) - } - - fun setTitleFontSize(titleFontSize: Float) { - mTitleFontSize = titleFontSize - } - - fun setTitleColor(color: Int) { - mTitleColor = color - } - - fun setTintColor(color: Int) { - mTintColor = color - } - - fun setTopInsetEnabled(topInsetEnabled: Boolean) { - mIsTopInsetEnabled = topInsetEnabled - } - - fun setBackgroundColor(color: Int?) { - mBackgroundColor = color - } - - fun setHideShadow(hideShadow: Boolean) { - mIsShadowHidden = hideShadow - } - - fun setHideBackButton(hideBackButton: Boolean) { - mIsBackButtonHidden = hideBackButton - } - - fun setHidden(hidden: Boolean) { - mIsHidden = hidden - } - - fun setTranslucent(translucent: Boolean) { - mIsTranslucent = translucent - } - - fun setBackButtonInCustomView(backButtonInCustomView: Boolean) { - mBackButtonInCustomView = backButtonInCustomView - } - - fun setDirection(direction: String?) { - mDirection = direction - } - - private class DebugMenuToolbar(context: Context, config: ScreenStackHeaderConfig) : CustomToolbar(context, config) { - override fun showOverflowMenu(): Boolean { - (context.applicationContext as ReactApplication) - .reactNativeHost - .reactInstanceManager - .showDevOptionsDialog() - return true - } - } - - init { - visibility = GONE - toolbar = if (BuildConfig.DEBUG) DebugMenuToolbar(context, this) else CustomToolbar(context, this) - mDefaultStartInset = toolbar.contentInsetStart - mDefaultStartInsetWithNavigation = toolbar.contentInsetStartWithNavigation - - // set primary color as background by default - val tv = TypedValue() - if (context.theme.resolveAttribute(R.attr.colorPrimary, tv, true)) { - toolbar.setBackgroundColor(tv.data) - } - toolbar.clipChildren = false - } -} diff --git a/android/expoview/src/main/java/versioned/host/exp/exponent/modules/api/screens/ScreenStackHeaderConfigViewManager.kt b/android/expoview/src/main/java/versioned/host/exp/exponent/modules/api/screens/ScreenStackHeaderConfigViewManager.kt deleted file mode 100644 index 6895cfc6a73c0..0000000000000 --- a/android/expoview/src/main/java/versioned/host/exp/exponent/modules/api/screens/ScreenStackHeaderConfigViewManager.kt +++ /dev/null @@ -1,208 +0,0 @@ -package versioned.host.exp.exponent.modules.api.screens - -import android.util.Log -import android.view.View -import com.facebook.react.bridge.JSApplicationCausedNativeException -import com.facebook.react.common.MapBuilder -import com.facebook.react.module.annotations.ReactModule -import com.facebook.react.uimanager.ThemedReactContext -import com.facebook.react.uimanager.ViewGroupManager -import com.facebook.react.uimanager.ViewManagerDelegate -import com.facebook.react.uimanager.annotations.ReactProp -import com.facebook.react.viewmanagers.RNSScreenStackHeaderConfigManagerDelegate -import com.facebook.react.viewmanagers.RNSScreenStackHeaderConfigManagerInterface -import javax.annotation.Nonnull - -@ReactModule(name = ScreenStackHeaderConfigViewManager.REACT_CLASS) -class ScreenStackHeaderConfigViewManager : ViewGroupManager(), RNSScreenStackHeaderConfigManagerInterface { - private val mDelegate: ViewManagerDelegate - - init { - mDelegate = RNSScreenStackHeaderConfigManagerDelegate(this) - } - - override fun getName(): String { - return REACT_CLASS - } - - override fun createViewInstance(reactContext: ThemedReactContext): ScreenStackHeaderConfig { - return ScreenStackHeaderConfig(reactContext) - } - - override fun addView(parent: ScreenStackHeaderConfig, child: View, index: Int) { - if (child !is ScreenStackHeaderSubview) { - throw JSApplicationCausedNativeException( - "Config children should be of type " + ScreenStackHeaderSubviewManager.REACT_CLASS - ) - } - parent.addConfigSubview(child, index) - } - - override fun onDropViewInstance(@Nonnull view: ScreenStackHeaderConfig) { - view.destroy() - } - - override fun removeAllViews(parent: ScreenStackHeaderConfig) { - parent.removeAllConfigSubviews() - } - - override fun removeViewAt(parent: ScreenStackHeaderConfig, index: Int) { - parent.removeConfigSubview(index) - } - - override fun getChildCount(parent: ScreenStackHeaderConfig): Int { - return parent.configSubviewsCount - } - - override fun getChildAt(parent: ScreenStackHeaderConfig, index: Int): View { - return parent.getConfigSubview(index) - } - - override fun needsCustomLayoutForChildren(): Boolean { - return true - } - - override fun onAfterUpdateTransaction(parent: ScreenStackHeaderConfig) { - super.onAfterUpdateTransaction(parent) - parent.onUpdate() - } - - @ReactProp(name = "title") - override fun setTitle(config: ScreenStackHeaderConfig, title: String?) { - config.setTitle(title) - } - - @ReactProp(name = "titleFontFamily") - override fun setTitleFontFamily(config: ScreenStackHeaderConfig, titleFontFamily: String?) { - config.setTitleFontFamily(titleFontFamily) - } - - @ReactProp(name = "titleFontSize") - override fun setTitleFontSize(config: ScreenStackHeaderConfig, titleFontSize: Int) { - config.setTitleFontSize(titleFontSize.toFloat()) - } - - @ReactProp(name = "titleFontWeight") - override fun setTitleFontWeight(config: ScreenStackHeaderConfig, titleFontWeight: String?) { - config.setTitleFontWeight(titleFontWeight) - } - - @ReactProp(name = "titleColor", customType = "Color") - override fun setTitleColor(config: ScreenStackHeaderConfig, titleColor: Int?) { - if (titleColor != null) { - config.setTitleColor(titleColor) - } - } - - @ReactProp(name = "backgroundColor", customType = "Color") - override fun setBackgroundColor(config: ScreenStackHeaderConfig, backgroundColor: Int?) { - config.setBackgroundColor(backgroundColor) - } - - @ReactProp(name = "hideShadow") - override fun setHideShadow(config: ScreenStackHeaderConfig, hideShadow: Boolean) { - config.setHideShadow(hideShadow) - } - - @ReactProp(name = "hideBackButton") - override fun setHideBackButton(config: ScreenStackHeaderConfig, hideBackButton: Boolean) { - config.setHideBackButton(hideBackButton) - } - - @ReactProp(name = "topInsetEnabled") - override fun setTopInsetEnabled(config: ScreenStackHeaderConfig, topInsetEnabled: Boolean) { - config.setTopInsetEnabled(topInsetEnabled) - } - - @ReactProp(name = "color", customType = "Color") - override fun setColor(config: ScreenStackHeaderConfig, color: Int?) { - config.setTintColor(color ?: 0) - } - - @ReactProp(name = "hidden") - override fun setHidden(config: ScreenStackHeaderConfig, hidden: Boolean) { - config.setHidden(hidden) - } - - @ReactProp(name = "translucent") - override fun setTranslucent(config: ScreenStackHeaderConfig, translucent: Boolean) { - config.setTranslucent(translucent) - } - - @ReactProp(name = "backButtonInCustomView") - override fun setBackButtonInCustomView( - config: ScreenStackHeaderConfig, - backButtonInCustomView: Boolean - ) { - config.setBackButtonInCustomView(backButtonInCustomView) - } - - @ReactProp(name = "direction") - override fun setDirection(config: ScreenStackHeaderConfig, direction: String?) { - config.setDirection(direction) - } - - override fun getExportedCustomDirectEventTypeConstants(): Map? { - return MapBuilder.builder() - .put("onAttached", MapBuilder.of("registrationName", "onAttached")) - .put("onDetached", MapBuilder.of("registrationName", "onDetached")) - .build() - } - - protected override fun getDelegate(): ViewManagerDelegate { - return mDelegate - } - - companion object { - const val REACT_CLASS = "RNSScreenStackHeaderConfig" - } - - // TODO: Find better way to handle platform specific props - private fun logNotAvailable(propName: String) { - Log.w("RN SCREENS", "$propName prop is not available on Android") - } - - override fun setBackTitle(view: ScreenStackHeaderConfig?, value: String?) { - logNotAvailable("backTitle") - } - - override fun setBackTitleFontFamily(view: ScreenStackHeaderConfig?, value: String?) { - logNotAvailable("backTitleFontFamily") - } - - override fun setBackTitleFontSize(view: ScreenStackHeaderConfig?, value: Int) { - logNotAvailable("backTitleFontSize") - } - - override fun setLargeTitle(view: ScreenStackHeaderConfig?, value: Boolean) { - logNotAvailable("largeTitle") - } - - override fun setLargeTitleFontFamily(view: ScreenStackHeaderConfig?, value: String?) { - logNotAvailable("largeTitleFontFamily") - } - - override fun setLargeTitleFontSize(view: ScreenStackHeaderConfig?, value: Int) { - logNotAvailable("largeTitleFontSize") - } - - override fun setLargeTitleFontWeight(view: ScreenStackHeaderConfig?, value: String?) { - logNotAvailable("largeTitleFontWeight") - } - - override fun setLargeTitleBackgroundColor(view: ScreenStackHeaderConfig?, value: Int?) { - logNotAvailable("largeTitleBackgroundColor") - } - - override fun setLargeTitleHideShadow(view: ScreenStackHeaderConfig?, value: Boolean) { - logNotAvailable("largeTitleHideShadow") - } - - override fun setLargeTitleColor(view: ScreenStackHeaderConfig?, value: Int?) { - logNotAvailable("largeTitleColor") - } - - override fun setDisableBackButtonMenu(view: ScreenStackHeaderConfig?, value: Boolean) { - logNotAvailable("disableBackButtonMenu") - } -} diff --git a/android/expoview/src/main/java/versioned/host/exp/exponent/modules/api/screens/ScreenStackHeaderSubview.kt b/android/expoview/src/main/java/versioned/host/exp/exponent/modules/api/screens/ScreenStackHeaderSubview.kt deleted file mode 100644 index cde35dec37592..0000000000000 --- a/android/expoview/src/main/java/versioned/host/exp/exponent/modules/api/screens/ScreenStackHeaderSubview.kt +++ /dev/null @@ -1,42 +0,0 @@ -package versioned.host.exp.exponent.modules.api.screens - -import android.annotation.SuppressLint -import android.view.View -import com.facebook.react.bridge.ReactContext -import com.facebook.react.views.view.ReactViewGroup - -@SuppressLint("ViewConstructor") -class ScreenStackHeaderSubview(context: ReactContext?) : ReactViewGroup(context) { - private var mReactWidth = 0 - private var mReactHeight = 0 - var type = Type.RIGHT - - val config: ScreenStackHeaderConfig? - get() { - return (parent as? CustomToolbar)?.config - } - - override fun onMeasure(widthMeasureSpec: Int, heightMeasureSpec: Int) { - if (MeasureSpec.getMode(widthMeasureSpec) == MeasureSpec.EXACTLY && - MeasureSpec.getMode(heightMeasureSpec) == MeasureSpec.EXACTLY - ) { - // dimensions provided by react - mReactWidth = MeasureSpec.getSize(widthMeasureSpec) - mReactHeight = MeasureSpec.getSize(heightMeasureSpec) - val parent = parent - if (parent != null) { - forceLayout() - (parent as View).requestLayout() - } - } - setMeasuredDimension(mReactWidth, mReactHeight) - } - - override fun onLayout(changed: Boolean, left: Int, top: Int, right: Int, bottom: Int) { - // no-op - } - - enum class Type { - LEFT, CENTER, RIGHT, BACK, SEARCH_BAR - } -} diff --git a/android/expoview/src/main/java/versioned/host/exp/exponent/modules/api/screens/ScreenStackHeaderSubviewManager.kt b/android/expoview/src/main/java/versioned/host/exp/exponent/modules/api/screens/ScreenStackHeaderSubviewManager.kt deleted file mode 100644 index e56bd636db465..0000000000000 --- a/android/expoview/src/main/java/versioned/host/exp/exponent/modules/api/screens/ScreenStackHeaderSubviewManager.kt +++ /dev/null @@ -1,47 +0,0 @@ -package versioned.host.exp.exponent.modules.api.screens - -import com.facebook.react.bridge.JSApplicationIllegalArgumentException -import com.facebook.react.module.annotations.ReactModule -import com.facebook.react.uimanager.ThemedReactContext -import com.facebook.react.uimanager.ViewGroupManager -import com.facebook.react.uimanager.ViewManagerDelegate -import com.facebook.react.uimanager.annotations.ReactProp -import com.facebook.react.viewmanagers.RNSScreenStackHeaderSubviewManagerDelegate -import com.facebook.react.viewmanagers.RNSScreenStackHeaderSubviewManagerInterface - -@ReactModule(name = ScreenStackHeaderSubviewManager.REACT_CLASS) -class ScreenStackHeaderSubviewManager : ViewGroupManager(), RNSScreenStackHeaderSubviewManagerInterface { - private val mDelegate: ViewManagerDelegate - - init { - mDelegate = RNSScreenStackHeaderSubviewManagerDelegate(this) - } - - override fun getName(): String { - return REACT_CLASS - } - - override fun createViewInstance(context: ThemedReactContext): ScreenStackHeaderSubview { - return ScreenStackHeaderSubview(context) - } - - @ReactProp(name = "type") - override fun setType(view: ScreenStackHeaderSubview, type: String?) { - view.type = when (type) { - "left" -> ScreenStackHeaderSubview.Type.LEFT - "center" -> ScreenStackHeaderSubview.Type.CENTER - "right" -> ScreenStackHeaderSubview.Type.RIGHT - "back" -> ScreenStackHeaderSubview.Type.BACK - "searchBar" -> ScreenStackHeaderSubview.Type.SEARCH_BAR - else -> throw JSApplicationIllegalArgumentException("Unknown type $type") - } - } - - protected override fun getDelegate(): ViewManagerDelegate { - return mDelegate - } - - companion object { - const val REACT_CLASS = "RNSScreenStackHeaderSubview" - } -} diff --git a/android/expoview/src/main/java/versioned/host/exp/exponent/modules/api/screens/ScreenStackViewManager.kt b/android/expoview/src/main/java/versioned/host/exp/exponent/modules/api/screens/ScreenStackViewManager.kt deleted file mode 100644 index c06512250104c..0000000000000 --- a/android/expoview/src/main/java/versioned/host/exp/exponent/modules/api/screens/ScreenStackViewManager.kt +++ /dev/null @@ -1,87 +0,0 @@ -package versioned.host.exp.exponent.modules.api.screens - -import android.view.View -import android.view.ViewGroup -import com.facebook.react.bridge.ReactApplicationContext -import com.facebook.react.module.annotations.ReactModule -import com.facebook.react.uimanager.LayoutShadowNode -import com.facebook.react.uimanager.ThemedReactContext -import com.facebook.react.uimanager.ViewGroupManager -import com.facebook.react.uimanager.ViewManagerDelegate -import com.facebook.react.viewmanagers.RNSScreenStackManagerDelegate -import com.facebook.react.viewmanagers.RNSScreenStackManagerInterface - -@ReactModule(name = ScreenStackViewManager.REACT_CLASS) -class ScreenStackViewManager : ViewGroupManager(), RNSScreenStackManagerInterface { - private val mDelegate: ViewManagerDelegate - - init { - mDelegate = RNSScreenStackManagerDelegate(this) - } - - override fun getName(): String { - return REACT_CLASS - } - - override fun createViewInstance(reactContext: ThemedReactContext): ScreenStack { - return ScreenStack(reactContext) - } - - override fun addView(parent: ScreenStack, child: View, index: Int) { - require(child is Screen) { "Attempt attach child that is not of type RNScreen" } - parent.addScreen(child, index) - } - - override fun removeViewAt(parent: ScreenStack, index: Int) { - prepareOutTransition(parent.getScreenAt(index)) - parent.removeScreenAt(index) - } - - private fun prepareOutTransition(screen: Screen?) { - startTransitionRecursive(screen) - } - - private fun startTransitionRecursive(parent: ViewGroup?) { - var i = 0 - parent?.let { - val size = it.childCount - while (i < size) { - val child = it.getChildAt(i) - child?.let { view -> it.startViewTransition(view) } - if (child is ScreenStackHeaderConfig) { - // we want to start transition on children of the toolbar too, - // which is not a child of ScreenStackHeaderConfig - startTransitionRecursive(child.toolbar) - } - if (child is ViewGroup) { - startTransitionRecursive(child) - } - i++ - } - } - } - - override fun getChildCount(parent: ScreenStack): Int { - return parent.screenCount - } - - override fun getChildAt(parent: ScreenStack, index: Int): View { - return parent.getScreenAt(index) - } - - override fun createShadowNodeInstance(context: ReactApplicationContext): LayoutShadowNode { - return ScreensShadowNode(context) - } - - override fun needsCustomLayoutForChildren(): Boolean { - return true - } - - protected override fun getDelegate(): ViewManagerDelegate { - return mDelegate - } - - companion object { - const val REACT_CLASS = "RNSScreenStack" - } -} diff --git a/android/expoview/src/main/java/versioned/host/exp/exponent/modules/api/screens/ScreenViewManager.kt b/android/expoview/src/main/java/versioned/host/exp/exponent/modules/api/screens/ScreenViewManager.kt deleted file mode 100644 index 0a330f8b6272e..0000000000000 --- a/android/expoview/src/main/java/versioned/host/exp/exponent/modules/api/screens/ScreenViewManager.kt +++ /dev/null @@ -1,193 +0,0 @@ -package versioned.host.exp.exponent.modules.api.screens - -import com.facebook.react.bridge.JSApplicationIllegalArgumentException -import com.facebook.react.bridge.ReadableMap -import com.facebook.react.common.MapBuilder -import com.facebook.react.module.annotations.ReactModule -import com.facebook.react.uimanager.ThemedReactContext -import com.facebook.react.uimanager.ViewGroupManager -import com.facebook.react.uimanager.ViewManagerDelegate -import com.facebook.react.uimanager.annotations.ReactProp -import com.facebook.react.viewmanagers.RNSScreenManagerDelegate -import com.facebook.react.viewmanagers.RNSScreenManagerInterface -import versioned.host.exp.exponent.modules.api.screens.events.HeaderBackButtonClickedEvent -import versioned.host.exp.exponent.modules.api.screens.events.ScreenAppearEvent -import versioned.host.exp.exponent.modules.api.screens.events.ScreenDisappearEvent -import versioned.host.exp.exponent.modules.api.screens.events.ScreenDismissedEvent -import versioned.host.exp.exponent.modules.api.screens.events.ScreenTransitionProgressEvent -import versioned.host.exp.exponent.modules.api.screens.events.ScreenWillAppearEvent -import versioned.host.exp.exponent.modules.api.screens.events.ScreenWillDisappearEvent -import versioned.host.exp.exponent.modules.api.screens.events.StackFinishTransitioningEvent - -@ReactModule(name = ScreenViewManager.REACT_CLASS) -class ScreenViewManager : ViewGroupManager(), RNSScreenManagerInterface { - private val mDelegate: ViewManagerDelegate - - init { - mDelegate = RNSScreenManagerDelegate(this) - } - - override fun getName(): String { - return REACT_CLASS - } - - override fun createViewInstance(reactContext: ThemedReactContext): Screen { - return Screen(reactContext) - } - - override fun setActivityState(view: Screen, activityState: Float) { - setActivityState(view, activityState.toInt()) - } - - @ReactProp(name = "activityState") - fun setActivityState(view: Screen, activityState: Int) { - if (activityState == -1) { - // Null will be provided when activityState is set as an animated value and we change - // it from JS to be a plain value (non animated). - // In case when null is received, we want to ignore such value and not make - // any updates as the actual non-null value will follow immediately. - return - } - when (activityState) { - 0 -> view.setActivityState(Screen.ActivityState.INACTIVE) - 1 -> view.setActivityState(Screen.ActivityState.TRANSITIONING_OR_BELOW_TOP) - 2 -> view.setActivityState(Screen.ActivityState.ON_TOP) - } - } - - @ReactProp(name = "stackPresentation") - override fun setStackPresentation(view: Screen, presentation: String?) { - view.stackPresentation = when (presentation) { - "push" -> Screen.StackPresentation.PUSH - "modal", "containedModal", "fullScreenModal", "formSheet" -> - Screen.StackPresentation.MODAL - "transparentModal", "containedTransparentModal" -> - Screen.StackPresentation.TRANSPARENT_MODAL - else -> throw JSApplicationIllegalArgumentException("Unknown presentation type $presentation") - } - } - - @ReactProp(name = "stackAnimation") - override fun setStackAnimation(view: Screen, animation: String?) { - view.stackAnimation = when (animation) { - null, "default", "flip", "simple_push" -> Screen.StackAnimation.DEFAULT - "none" -> Screen.StackAnimation.NONE - "fade" -> Screen.StackAnimation.FADE - "slide_from_right" -> Screen.StackAnimation.SLIDE_FROM_RIGHT - "slide_from_left" -> Screen.StackAnimation.SLIDE_FROM_LEFT - "slide_from_bottom" -> Screen.StackAnimation.SLIDE_FROM_BOTTOM - "fade_from_bottom" -> Screen.StackAnimation.FADE_FROM_BOTTOM - else -> throw JSApplicationIllegalArgumentException("Unknown animation type $animation") - } - } - - @ReactProp(name = "gestureEnabled", defaultBoolean = true) - override fun setGestureEnabled(view: Screen, gestureEnabled: Boolean) { - view.isGestureEnabled = gestureEnabled - } - - @ReactProp(name = "replaceAnimation") - override fun setReplaceAnimation(view: Screen, animation: String?) { - view.replaceAnimation = when (animation) { - null, "pop" -> Screen.ReplaceAnimation.POP - "push" -> Screen.ReplaceAnimation.PUSH - else -> throw JSApplicationIllegalArgumentException("Unknown replace animation type $animation") - } - } - - @ReactProp(name = "screenOrientation") - override fun setScreenOrientation(view: Screen, screenOrientation: String?) { - view.setScreenOrientation(screenOrientation) - } - - @ReactProp(name = "statusBarAnimation") - override fun setStatusBarAnimation(view: Screen, statusBarAnimation: String?) { - val animated = statusBarAnimation != null && "none" != statusBarAnimation - view.isStatusBarAnimated = animated - } - - @ReactProp(name = "statusBarColor", customType = "Color") - override fun setStatusBarColor(view: Screen, statusBarColor: Int?) { - view.statusBarColor = statusBarColor - } - - @ReactProp(name = "statusBarStyle") - override fun setStatusBarStyle(view: Screen, statusBarStyle: String?) { - view.statusBarStyle = statusBarStyle - } - - @ReactProp(name = "statusBarTranslucent") - override fun setStatusBarTranslucent(view: Screen, statusBarTranslucent: Boolean) { - view.isStatusBarTranslucent = statusBarTranslucent - } - - @ReactProp(name = "statusBarHidden") - override fun setStatusBarHidden(view: Screen, statusBarHidden: Boolean) { - view.isStatusBarHidden = statusBarHidden - } - - @ReactProp(name = "navigationBarColor", customType = "Color") - override fun setNavigationBarColor(view: Screen, navigationBarColor: Int?) { - view.navigationBarColor = navigationBarColor - } - - @ReactProp(name = "navigationBarHidden") - override fun setNavigationBarHidden(view: Screen, navigationBarHidden: Boolean) { - view.isNavigationBarHidden = navigationBarHidden - } - - @ReactProp(name = "nativeBackButtonDismissalEnabled") - override fun setNativeBackButtonDismissalEnabled( - view: Screen, - nativeBackButtonDismissalEnabled: Boolean - ) { - view.nativeBackButtonDismissalEnabled = nativeBackButtonDismissalEnabled - } - - // these props are not available on Android, however we must override their setters - override fun setFullScreenSwipeEnabled(view: Screen?, value: Boolean) = Unit - - override fun setTransitionDuration(view: Screen?, value: Int) = Unit - - override fun setHideKeyboardOnSwipe(view: Screen?, value: Boolean) = Unit - - override fun setCustomAnimationOnSwipe(view: Screen?, value: Boolean) = Unit - - override fun setGestureResponseDistance(view: Screen?, value: ReadableMap?) = Unit - - override fun setHomeIndicatorHidden(view: Screen?, value: Boolean) = Unit - - override fun setPreventNativeDismiss(view: Screen?, value: Boolean) = Unit - - override fun setSwipeDirection(view: Screen?, value: String?) = Unit - - override fun getExportedCustomDirectEventTypeConstants(): MutableMap { - val map: MutableMap = MapBuilder.of( - ScreenDismissedEvent.EVENT_NAME, - MapBuilder.of("registrationName", "onDismissed"), - ScreenWillAppearEvent.EVENT_NAME, - MapBuilder.of("registrationName", "onWillAppear"), - ScreenAppearEvent.EVENT_NAME, - MapBuilder.of("registrationName", "onAppear"), - ScreenWillDisappearEvent.EVENT_NAME, - MapBuilder.of("registrationName", "onWillDisappear"), - ScreenDisappearEvent.EVENT_NAME, - MapBuilder.of("registrationName", "onDisappear"), - StackFinishTransitioningEvent.EVENT_NAME, - MapBuilder.of("registrationName", "onFinishTransitioning"), - ScreenTransitionProgressEvent.EVENT_NAME, - MapBuilder.of("registrationName", "onTransitionProgress") - ) - // there is no `MapBuilder.of` with more than 7 items - map[HeaderBackButtonClickedEvent.EVENT_NAME] = MapBuilder.of("registrationName", "onHeaderBackButtonClicked") - return map - } - - protected override fun getDelegate(): ViewManagerDelegate { - return mDelegate - } - - companion object { - const val REACT_CLASS = "RNSScreen" - } -} diff --git a/android/expoview/src/main/java/versioned/host/exp/exponent/modules/api/screens/ScreenWindowTraits.kt b/android/expoview/src/main/java/versioned/host/exp/exponent/modules/api/screens/ScreenWindowTraits.kt deleted file mode 100644 index 33779c1813bac..0000000000000 --- a/android/expoview/src/main/java/versioned/host/exp/exponent/modules/api/screens/ScreenWindowTraits.kt +++ /dev/null @@ -1,275 +0,0 @@ -package versioned.host.exp.exponent.modules.api.screens - -import android.animation.ArgbEvaluator -import android.animation.ValueAnimator -import android.annotation.SuppressLint -import android.annotation.TargetApi -import android.app.Activity -import android.content.pm.ActivityInfo -import android.graphics.Color -import android.os.Build -import android.view.ViewParent -import androidx.core.view.ViewCompat -import androidx.core.view.WindowCompat -import androidx.core.view.WindowInsetsCompat -import androidx.core.view.WindowInsetsControllerCompat -import com.facebook.react.bridge.GuardedRunnable -import com.facebook.react.bridge.ReactContext -import com.facebook.react.bridge.UiThreadUtil -import versioned.host.exp.exponent.modules.api.screens.Screen.WindowTraits - -object ScreenWindowTraits { - // Methods concerning statusBar management were taken from `react-native`'s status bar module: - // https://github.com/facebook/react-native/blob/master/ReactAndroid/src/main/java/com/facebook/react/modules/statusbar/StatusBarModule.java - private var mDidSetOrientation = false - private var mDidSetStatusBarAppearance = false - private var mDidSetNavigationBarAppearance = false - private var mDefaultStatusBarColor: Int? = null - internal fun applyDidSetOrientation() { - mDidSetOrientation = true - } - - internal fun applyDidSetStatusBarAppearance() { - mDidSetStatusBarAppearance = true - } - - internal fun applyDidSetNavigationBarAppearance() { - mDidSetNavigationBarAppearance = true - } - - internal fun setOrientation(screen: Screen, activity: Activity?) { - if (activity == null) { - return - } - val screenForOrientation = findScreenForTrait(screen, WindowTraits.ORIENTATION) - val orientation = screenForOrientation?.screenOrientation ?: ActivityInfo.SCREEN_ORIENTATION_UNSPECIFIED - activity.requestedOrientation = orientation - } - - @SuppressLint("ObsoleteSdkInt") // to be removed when support for < 0.64 is dropped - internal fun setColor(screen: Screen, activity: Activity?, context: ReactContext?) { - if (activity == null || context == null || Build.VERSION.SDK_INT < Build.VERSION_CODES.LOLLIPOP) { - return - } - if (mDefaultStatusBarColor == null) { - mDefaultStatusBarColor = activity.window.statusBarColor - } - val screenForColor = findScreenForTrait(screen, WindowTraits.COLOR) - val screenForAnimated = findScreenForTrait(screen, WindowTraits.ANIMATED) - val color = screenForColor?.statusBarColor ?: mDefaultStatusBarColor - val animated = screenForAnimated?.isStatusBarAnimated ?: false - - UiThreadUtil.runOnUiThread( - object : GuardedRunnable(context) { - override fun runGuarded() { - val window = activity.window - val curColor: Int = window.statusBarColor - val colorAnimation = ValueAnimator.ofObject(ArgbEvaluator(), curColor, color) - colorAnimation.addUpdateListener { animator -> - window.statusBarColor = animator.animatedValue as Int - } - if (animated) { - colorAnimation.setDuration(300).startDelay = 0 - } else { - colorAnimation.setDuration(0).startDelay = 300 - } - colorAnimation.start() - } - }) - } - - internal fun setStyle(screen: Screen, activity: Activity?, context: ReactContext?) { - if (activity == null || context == null || Build.VERSION.SDK_INT < Build.VERSION_CODES.M) { - return - } - val screenForStyle = findScreenForTrait(screen, WindowTraits.STYLE) - val style = screenForStyle?.statusBarStyle ?: "light" - - UiThreadUtil.runOnUiThread { - val decorView = activity.window.decorView - val window = activity.window - val controller = WindowInsetsControllerCompat(window, decorView) - - controller.isAppearanceLightStatusBars = style == "dark" - } - } - - internal fun setTranslucent( - screen: Screen, - activity: Activity?, - context: ReactContext? - ) { - if (activity == null || context == null) { - return - } - val screenForTranslucent = findScreenForTrait(screen, WindowTraits.TRANSLUCENT) - val translucent = screenForTranslucent?.isStatusBarTranslucent ?: false - UiThreadUtil.runOnUiThread( - object : GuardedRunnable(context) { - @TargetApi(Build.VERSION_CODES.LOLLIPOP) - override fun runGuarded() { - // If the status bar is translucent hook into the window insets calculations - // and consume all the top insets so no padding will be added under the status bar. - val decorView = activity.window.decorView - if (translucent) { - ViewCompat.setOnApplyWindowInsetsListener(decorView) { v, insets -> - val defaultInsets = ViewCompat.onApplyWindowInsets(v, insets) - defaultInsets.replaceSystemWindowInsets( - defaultInsets.systemWindowInsetLeft, - 0, - defaultInsets.systemWindowInsetRight, - defaultInsets.systemWindowInsetBottom - ) - } - } else { - ViewCompat.setOnApplyWindowInsetsListener(decorView, null) - } - ViewCompat.requestApplyInsets(decorView) - } - }) - } - - internal fun setHidden(screen: Screen, activity: Activity?) { - if (activity == null) { - return - } - val screenForHidden = findScreenForTrait(screen, WindowTraits.HIDDEN) - val hidden = screenForHidden?.isStatusBarHidden ?: false - val window = activity.window - val controller = WindowInsetsControllerCompat(window, window.decorView) - - UiThreadUtil.runOnUiThread { - if (hidden) { - controller.hide(WindowInsetsCompat.Type.statusBars()) - } else { - controller.show(WindowInsetsCompat.Type.statusBars()) - } - } - } - - // Methods concerning navigationBar management were taken from `react-native-navigation`'s repo: - // https://github.com/wix/react-native-navigation/blob/9bb70d81700692141a2c505c081c2d86c7f9c66e/lib/android/app/src/main/java/com/reactnativenavigation/utils/SystemUiUtils.kt - internal fun setNavigationBarColor(screen: Screen, activity: Activity?) { - if (activity == null) { - return - } - - val window = activity.window - - val screenForNavBarColor = findScreenForTrait(screen, WindowTraits.NAVIGATION_BAR_COLOR) - val color = screenForNavBarColor?.navigationBarColor ?: window.navigationBarColor - - UiThreadUtil.runOnUiThread { - WindowInsetsControllerCompat(window, window.decorView).isAppearanceLightNavigationBars = - isColorLight(color) - } - window.navigationBarColor = color - } - - internal fun setNavigationBarHidden(screen: Screen, activity: Activity?) { - if (activity == null) { - return - } - - val window = activity.window - - val screenForNavBarHidden = findScreenForTrait(screen, WindowTraits.NAVIGATION_BAR_HIDDEN) - val hidden = screenForNavBarHidden?.isNavigationBarHidden ?: false - - WindowCompat.setDecorFitsSystemWindows(window, hidden) - if (hidden) { - WindowInsetsControllerCompat(window, window.decorView).let { controller -> - controller.hide(WindowInsetsCompat.Type.navigationBars()) - controller.systemBarsBehavior = - WindowInsetsControllerCompat.BEHAVIOR_SHOW_TRANSIENT_BARS_BY_SWIPE - } - } else { - WindowInsetsControllerCompat( - window, - window.decorView - ).show(WindowInsetsCompat.Type.navigationBars()) - } - } - - internal fun trySetWindowTraits(screen: Screen, activity: Activity?, context: ReactContext?) { - if (mDidSetOrientation) { - setOrientation(screen, activity) - } - if (mDidSetStatusBarAppearance) { - setColor(screen, activity, context) - setStyle(screen, activity, context) - setTranslucent(screen, activity, context) - setHidden(screen, activity) - } - if (mDidSetNavigationBarAppearance) { - setNavigationBarColor(screen, activity) - setNavigationBarHidden(screen, activity) - } - } - - private fun findScreenForTrait(screen: Screen, trait: WindowTraits): Screen? { - val childWithTrait = childScreenWithTraitSet(screen, trait) - if (childWithTrait != null) { - return childWithTrait - } - return if (checkTraitForScreen(screen, trait)) { - screen - } else { - // if there is no child with trait set and this screen has no trait set, we look for a parent - // that has the trait set - findParentWithTraitSet(screen, trait) - } - } - - private fun findParentWithTraitSet(screen: Screen, trait: WindowTraits): Screen? { - var parent: ViewParent? = screen.container - while (parent != null) { - if (parent is Screen) { - if (checkTraitForScreen(parent, trait)) { - return parent - } - } - parent = parent.parent - } - return null - } - - private fun childScreenWithTraitSet( - screen: Screen?, - trait: WindowTraits - ): Screen? { - screen?.fragment?.let { - for (sc in it.childScreenContainers) { - // we check only the top screen for the trait - val topScreen = sc.topScreen - val child = childScreenWithTraitSet(topScreen, trait) - if (child != null) { - return child - } - if (topScreen != null && checkTraitForScreen(topScreen, trait)) { - return topScreen - } - } - } - return null - } - - private fun checkTraitForScreen(screen: Screen, trait: WindowTraits): Boolean { - return when (trait) { - WindowTraits.ORIENTATION -> screen.screenOrientation != null - WindowTraits.COLOR -> screen.statusBarColor != null - WindowTraits.STYLE -> screen.statusBarStyle != null - WindowTraits.TRANSLUCENT -> screen.isStatusBarTranslucent != null - WindowTraits.HIDDEN -> screen.isStatusBarHidden != null - WindowTraits.ANIMATED -> screen.isStatusBarAnimated != null - WindowTraits.NAVIGATION_BAR_COLOR -> screen.navigationBarColor != null - WindowTraits.NAVIGATION_BAR_HIDDEN -> screen.isNavigationBarHidden != null - } - } - - private fun isColorLight(color: Int): Boolean { - val darkness: Double = - 1 - (0.299 * Color.red(color) + 0.587 * Color.green(color) + 0.114 * Color.blue(color)) / 255 - return darkness < 0.5 - } -} diff --git a/android/expoview/src/main/java/versioned/host/exp/exponent/modules/api/screens/ScreensShadowNode.kt b/android/expoview/src/main/java/versioned/host/exp/exponent/modules/api/screens/ScreensShadowNode.kt deleted file mode 100644 index 6f06eb485d549..0000000000000 --- a/android/expoview/src/main/java/versioned/host/exp/exponent/modules/api/screens/ScreensShadowNode.kt +++ /dev/null @@ -1,19 +0,0 @@ -package versioned.host.exp.exponent.modules.api.screens - -import com.facebook.react.bridge.ReactContext -import com.facebook.react.uimanager.LayoutShadowNode -import com.facebook.react.uimanager.NativeViewHierarchyManager -import com.facebook.react.uimanager.NativeViewHierarchyOptimizer -import com.facebook.react.uimanager.UIManagerModule - -internal class ScreensShadowNode(private var mContext: ReactContext) : LayoutShadowNode() { - override fun onBeforeLayout(nativeViewHierarchyOptimizer: NativeViewHierarchyOptimizer) { - super.onBeforeLayout(nativeViewHierarchyOptimizer) - (mContext.getNativeModule(UIManagerModule::class.java))?.addUIBlock { nativeViewHierarchyManager: NativeViewHierarchyManager -> - val view = nativeViewHierarchyManager.resolveView(reactTag) - if (view is ScreenContainer<*>) { - view.performUpdates() - } - } - } -} diff --git a/android/expoview/src/main/java/versioned/host/exp/exponent/modules/api/screens/SearchBarManager.kt b/android/expoview/src/main/java/versioned/host/exp/exponent/modules/api/screens/SearchBarManager.kt deleted file mode 100644 index 8294cf6a7cd27..0000000000000 --- a/android/expoview/src/main/java/versioned/host/exp/exponent/modules/api/screens/SearchBarManager.kt +++ /dev/null @@ -1,107 +0,0 @@ -package versioned.host.exp.exponent.modules.api.screens - -import com.facebook.react.bridge.JSApplicationIllegalArgumentException -import com.facebook.react.common.MapBuilder -import com.facebook.react.module.annotations.ReactModule -import com.facebook.react.uimanager.ThemedReactContext -import com.facebook.react.uimanager.ViewGroupManager -import com.facebook.react.uimanager.annotations.ReactProp - -@ReactModule(name = SearchBarManager.REACT_CLASS) -class SearchBarManager : ViewGroupManager() { - override fun getName(): String { - return REACT_CLASS - } - - override fun createViewInstance(context: ThemedReactContext): SearchBarView { - return SearchBarView(context) - } - - override fun onAfterUpdateTransaction(view: SearchBarView) { - super.onAfterUpdateTransaction(view) - view.onUpdate() - } - - @ReactProp(name = "autoCapitalize") - fun setAutoCapitalize(view: SearchBarView, autoCapitalize: String?) { - view.autoCapitalize = when (autoCapitalize) { - null, "none" -> SearchBarView.SearchBarAutoCapitalize.NONE - "words" -> SearchBarView.SearchBarAutoCapitalize.WORDS - "sentences" -> SearchBarView.SearchBarAutoCapitalize.SENTENCES - "characters" -> SearchBarView.SearchBarAutoCapitalize.CHARACTERS - else -> throw JSApplicationIllegalArgumentException( - "Forbidden auto capitalize value passed" - ) - } - } - - @ReactProp(name = "autoFocus") - fun setAutoFocus(view: SearchBarView, autoFocus: Boolean?) { - view.autoFocus = autoFocus ?: false - } - - @ReactProp(name = "barTintColor", customType = "Color") - fun setTintColor(view: SearchBarView, color: Int?) { - view.tintColor = color - } - - @ReactProp(name = "disableBackButtonOverride") - fun setDisableBackButtonOverride(view: SearchBarView, disableBackButtonOverride: Boolean?) { - view.shouldOverrideBackButton = disableBackButtonOverride != true - } - - @ReactProp(name = "inputType") - fun setInputType(view: SearchBarView, inputType: String?) { - view.inputType = when (inputType) { - null, "text" -> SearchBarView.SearchBarInputTypes.TEXT - "phone" -> SearchBarView.SearchBarInputTypes.PHONE - "number" -> SearchBarView.SearchBarInputTypes.NUMBER - "email" -> SearchBarView.SearchBarInputTypes.EMAIL - else -> throw JSApplicationIllegalArgumentException( - "Forbidden input type value" - ) - } - } - - @ReactProp(name = "placeholder") - fun setPlaceholder(view: SearchBarView, placeholder: String?) { - if (placeholder != null) { - view.placeholder = placeholder - } - } - - @ReactProp(name = "textColor", customType = "Color") - fun setTextColor(view: SearchBarView, color: Int?) { - view.textColor = color - } - - @ReactProp(name = "headerIconColor", customType = "Color") - fun setHeaderIconColor(view: SearchBarView, color: Int?) { - view.headerIconColor = color - } - - @ReactProp(name = "hintTextColor", customType = "Color") - fun setHintTextColor(view: SearchBarView, color: Int?) { - view.hintTextColor = color - } - - @ReactProp(name = "shouldShowHintSearchIcon") - fun setShouldShowHintSearchIcon(view: SearchBarView, shouldShowHintSearchIcon: Boolean?) { - view.shouldShowHintSearchIcon = shouldShowHintSearchIcon ?: true - } - - override fun getExportedCustomDirectEventTypeConstants(): Map? { - return MapBuilder.builder() - .put("onChangeText", MapBuilder.of("registrationName", "onChangeText")) - .put("onSearchButtonPress", MapBuilder.of("registrationName", "onSearchButtonPress")) - .put("onFocus", MapBuilder.of("registrationName", "onFocus")) - .put("onBlur", MapBuilder.of("registrationName", "onBlur")) - .put("onClose", MapBuilder.of("registrationName", "onClose")) - .put("onOpen", MapBuilder.of("registrationName", "onOpen")) - .build() - } - - companion object { - const val REACT_CLASS = "RNSSearchBar" - } -} diff --git a/android/expoview/src/main/java/versioned/host/exp/exponent/modules/api/screens/SearchBarView.kt b/android/expoview/src/main/java/versioned/host/exp/exponent/modules/api/screens/SearchBarView.kt deleted file mode 100644 index 6e68cebf4fd8b..0000000000000 --- a/android/expoview/src/main/java/versioned/host/exp/exponent/modules/api/screens/SearchBarView.kt +++ /dev/null @@ -1,155 +0,0 @@ -package versioned.host.exp.exponent.modules.api.screens - -import android.annotation.SuppressLint -import android.text.InputType -import androidx.appcompat.widget.SearchView -import com.facebook.react.bridge.Arguments -import com.facebook.react.bridge.ReactContext -import com.facebook.react.bridge.WritableMap -import com.facebook.react.uimanager.events.RCTEventEmitter -import com.facebook.react.views.view.ReactViewGroup - -@SuppressLint("ViewConstructor") -class SearchBarView(reactContext: ReactContext?) : ReactViewGroup(reactContext) { - var inputType: SearchBarInputTypes = SearchBarInputTypes.TEXT - var autoCapitalize: SearchBarAutoCapitalize = SearchBarAutoCapitalize.NONE - var textColor: Int? = null - var tintColor: Int? = null - var headerIconColor: Int? = null - var hintTextColor: Int? = null - var placeholder: String = "" - var shouldOverrideBackButton: Boolean = true - var autoFocus: Boolean = false - var shouldShowHintSearchIcon: Boolean = true - - private var mSearchViewFormatter: SearchViewFormatter? = null - - private var mAreListenersSet: Boolean = false - - private val screenStackFragment: ScreenStackFragment? - get() { - val currentParent = parent - if (currentParent is ScreenStackHeaderSubview) { - return currentParent.config?.screenFragment - } - return null - } - - fun onUpdate() { - setSearchViewProps() - } - - private fun setSearchViewProps() { - val searchView = screenStackFragment?.searchView - if (searchView != null) { - if (!mAreListenersSet) { - setSearchViewListeners(searchView) - mAreListenersSet = true - } - - searchView.inputType = inputType.toAndroidInputType(autoCapitalize) - mSearchViewFormatter?.setTextColor(textColor) - mSearchViewFormatter?.setTintColor(tintColor) - mSearchViewFormatter?.setHeaderIconColor(headerIconColor) - mSearchViewFormatter?.setHintTextColor(hintTextColor) - mSearchViewFormatter?.setPlaceholder(placeholder, shouldShowHintSearchIcon) - searchView.overrideBackAction = shouldOverrideBackButton - } - } - - override fun onAttachedToWindow() { - super.onAttachedToWindow() - - screenStackFragment?.onSearchViewCreate = { newSearchView -> - if (mSearchViewFormatter == null) mSearchViewFormatter = - SearchViewFormatter(newSearchView) - setSearchViewProps() - if (autoFocus) { - screenStackFragment?.searchView?.focus() - } - } - } - - private fun setSearchViewListeners(searchView: SearchView) { - searchView.setOnQueryTextListener(object : SearchView.OnQueryTextListener { - override fun onQueryTextChange(newText: String?): Boolean { - handleTextChange(newText) - return true - } - - override fun onQueryTextSubmit(query: String?): Boolean { - handleTextSubmit(query) - return true - } - }) - searchView.setOnQueryTextFocusChangeListener { _, hasFocus -> - handleFocusChange(hasFocus) - } - searchView.setOnCloseListener { - handleClose() - false - } - searchView.setOnSearchClickListener { - handleOpen() - } - } - - private fun handleTextChange(newText: String?) { - val event = Arguments.createMap() - event.putString("text", newText) - sendEvent("onChangeText", event) - } - - private fun handleFocusChange(hasFocus: Boolean) { - sendEvent(if (hasFocus) "onFocus" else "onBlur", null) - } - - private fun handleClose() { - sendEvent("onClose", null) - } - - private fun handleOpen() { - sendEvent("onOpen", null) - } - - private fun handleTextSubmit(newText: String?) { - val event = Arguments.createMap() - event.putString("text", newText) - sendEvent("onSearchButtonPress", event) - } - - private fun sendEvent(eventName: String, eventContent: WritableMap?) { - (context as ReactContext).getJSModule(RCTEventEmitter::class.java) - ?.receiveEvent(id, eventName, eventContent) - } - - enum class SearchBarAutoCapitalize { - NONE, WORDS, SENTENCES, CHARACTERS - } - - enum class SearchBarInputTypes { - TEXT { - override fun toAndroidInputType(capitalize: SearchBarAutoCapitalize) = - when (capitalize) { - SearchBarAutoCapitalize.NONE -> InputType.TYPE_CLASS_TEXT - SearchBarAutoCapitalize.WORDS -> InputType.TYPE_TEXT_FLAG_CAP_WORDS - SearchBarAutoCapitalize.SENTENCES -> InputType.TYPE_TEXT_FLAG_CAP_SENTENCES - SearchBarAutoCapitalize.CHARACTERS -> InputType.TYPE_TEXT_FLAG_CAP_CHARACTERS - } - }, - PHONE { - override fun toAndroidInputType(capitalize: SearchBarAutoCapitalize) = - InputType.TYPE_CLASS_PHONE - }, - NUMBER { - override fun toAndroidInputType(capitalize: SearchBarAutoCapitalize) = - InputType.TYPE_CLASS_NUMBER - }, - EMAIL { - override fun toAndroidInputType(capitalize: SearchBarAutoCapitalize) = - InputType.TYPE_TEXT_VARIATION_EMAIL_ADDRESS - }; - - abstract fun toAndroidInputType(capitalize: SearchBarAutoCapitalize): Int - } -} diff --git a/android/expoview/src/main/java/versioned/host/exp/exponent/modules/api/screens/SearchViewFormatter.kt b/android/expoview/src/main/java/versioned/host/exp/exponent/modules/api/screens/SearchViewFormatter.kt deleted file mode 100644 index bca57c3776f3c..0000000000000 --- a/android/expoview/src/main/java/versioned/host/exp/exponent/modules/api/screens/SearchViewFormatter.kt +++ /dev/null @@ -1,67 +0,0 @@ -package versioned.host.exp.exponent.modules.api.screens - -import android.graphics.drawable.Drawable -import android.view.View -import android.widget.EditText -import android.widget.ImageView -import androidx.appcompat.R -import androidx.appcompat.widget.SearchView - -class SearchViewFormatter(var searchView: SearchView) { - private var mDefaultTextColor: Int? = null - private var mDefaultTintBackground: Drawable? = null - - private val searchEditText - get() = searchView.findViewById(R.id.search_src_text) as? EditText - private val searchTextPlate - get() = searchView.findViewById(R.id.search_plate) - private val searchIcon - get() = searchView.findViewById(R.id.search_button) - private val searchCloseIcon - get() = searchView.findViewById(R.id.search_close_btn) - - fun setTextColor(textColor: Int?) { - val currentDefaultTextColor = mDefaultTextColor - if (textColor != null) { - if (mDefaultTextColor == null) { - mDefaultTextColor = searchEditText?.textColors?.defaultColor - } - searchEditText?.setTextColor(textColor) - } else if (currentDefaultTextColor != null) { - searchEditText?.setTextColor(currentDefaultTextColor) - } - } - - fun setTintColor(tintColor: Int?) { - val currentDefaultTintColor = mDefaultTintBackground - if (tintColor != null) { - if (mDefaultTintBackground == null) { - mDefaultTintBackground = searchTextPlate.background - } - searchTextPlate.setBackgroundColor(tintColor) - } else if (currentDefaultTintColor != null) { - searchTextPlate.background = currentDefaultTintColor - } - } - - fun setHeaderIconColor(headerIconColor: Int?) { - headerIconColor?.let { - searchIcon.setColorFilter(it) - searchCloseIcon.setColorFilter(it) - } - } - - fun setHintTextColor(hintTextColor: Int?) { - hintTextColor?.let { - searchEditText?.setHintTextColor(it) - } - } - - fun setPlaceholder(placeholder: String, shouldShowHintSearchIcon: Boolean) { - if (shouldShowHintSearchIcon) { - searchView.queryHint = placeholder - } else { - searchEditText?.hint = placeholder - } - } -} diff --git a/android/expoview/src/main/java/versioned/host/exp/exponent/modules/api/screens/events/HeaderBackButtonClickedEvent.kt b/android/expoview/src/main/java/versioned/host/exp/exponent/modules/api/screens/events/HeaderBackButtonClickedEvent.kt deleted file mode 100644 index 1d84b62f4ff93..0000000000000 --- a/android/expoview/src/main/java/versioned/host/exp/exponent/modules/api/screens/events/HeaderBackButtonClickedEvent.kt +++ /dev/null @@ -1,24 +0,0 @@ -package versioned.host.exp.exponent.modules.api.screens.events - -import com.facebook.react.bridge.Arguments -import com.facebook.react.uimanager.events.Event -import com.facebook.react.uimanager.events.RCTEventEmitter - -class HeaderBackButtonClickedEvent(viewId: Int) : Event(viewId) { - override fun getEventName(): String { - return EVENT_NAME - } - - override fun getCoalescingKey(): Short { - // All events for a given view can be coalesced. - return 0 - } - - override fun dispatch(rctEventEmitter: RCTEventEmitter) { - rctEventEmitter.receiveEvent(viewTag, eventName, Arguments.createMap()) - } - - companion object { - const val EVENT_NAME = "topHeaderBackButtonClickedEvent" - } -} diff --git a/android/expoview/src/main/java/versioned/host/exp/exponent/modules/api/screens/events/ScreenAppearEvent.kt b/android/expoview/src/main/java/versioned/host/exp/exponent/modules/api/screens/events/ScreenAppearEvent.kt deleted file mode 100644 index 3c16f6aea4e77..0000000000000 --- a/android/expoview/src/main/java/versioned/host/exp/exponent/modules/api/screens/events/ScreenAppearEvent.kt +++ /dev/null @@ -1,20 +0,0 @@ -package versioned.host.exp.exponent.modules.api.screens.events - -import com.facebook.react.bridge.Arguments -import com.facebook.react.uimanager.events.Event -import com.facebook.react.uimanager.events.RCTEventEmitter - -class ScreenAppearEvent(viewId: Int) : Event(viewId) { - override fun getEventName() = EVENT_NAME - - // All events for a given view can be coalesced. - override fun getCoalescingKey(): Short = 0 - - override fun dispatch(rctEventEmitter: RCTEventEmitter) { - rctEventEmitter.receiveEvent(viewTag, eventName, Arguments.createMap()) - } - - companion object { - const val EVENT_NAME = "topAppear" - } -} diff --git a/android/expoview/src/main/java/versioned/host/exp/exponent/modules/api/screens/events/ScreenDisappearEvent.kt b/android/expoview/src/main/java/versioned/host/exp/exponent/modules/api/screens/events/ScreenDisappearEvent.kt deleted file mode 100644 index a6424847f0fc1..0000000000000 --- a/android/expoview/src/main/java/versioned/host/exp/exponent/modules/api/screens/events/ScreenDisappearEvent.kt +++ /dev/null @@ -1,20 +0,0 @@ -package versioned.host.exp.exponent.modules.api.screens.events - -import com.facebook.react.bridge.Arguments -import com.facebook.react.uimanager.events.Event -import com.facebook.react.uimanager.events.RCTEventEmitter - -class ScreenDisappearEvent(viewId: Int) : Event(viewId) { - override fun getEventName() = EVENT_NAME - - // All events for a given view can be coalesced. - override fun getCoalescingKey(): Short = 0 - - override fun dispatch(rctEventEmitter: RCTEventEmitter) { - rctEventEmitter.receiveEvent(viewTag, eventName, Arguments.createMap()) - } - - companion object { - const val EVENT_NAME = "topDisappear" - } -} diff --git a/android/expoview/src/main/java/versioned/host/exp/exponent/modules/api/screens/events/ScreenDismissedEvent.kt b/android/expoview/src/main/java/versioned/host/exp/exponent/modules/api/screens/events/ScreenDismissedEvent.kt deleted file mode 100644 index ba34d7c1a56fa..0000000000000 --- a/android/expoview/src/main/java/versioned/host/exp/exponent/modules/api/screens/events/ScreenDismissedEvent.kt +++ /dev/null @@ -1,23 +0,0 @@ -package versioned.host.exp.exponent.modules.api.screens.events - -import com.facebook.react.bridge.Arguments -import com.facebook.react.uimanager.events.Event -import com.facebook.react.uimanager.events.RCTEventEmitter - -class ScreenDismissedEvent(viewId: Int) : Event(viewId) { - override fun getEventName() = EVENT_NAME - - // All events for a given view can be coalesced. - override fun getCoalescingKey(): Short = 0 - - override fun dispatch(rctEventEmitter: RCTEventEmitter) { - val args = Arguments.createMap() - // on Android we always dismiss one screen at a time - args.putInt("dismissCount", 1) - rctEventEmitter.receiveEvent(viewTag, eventName, args) - } - - companion object { - const val EVENT_NAME = "topDismissed" - } -} diff --git a/android/expoview/src/main/java/versioned/host/exp/exponent/modules/api/screens/events/ScreenTransitionProgressEvent.kt b/android/expoview/src/main/java/versioned/host/exp/exponent/modules/api/screens/events/ScreenTransitionProgressEvent.kt deleted file mode 100644 index f3766dc84b5a6..0000000000000 --- a/android/expoview/src/main/java/versioned/host/exp/exponent/modules/api/screens/events/ScreenTransitionProgressEvent.kt +++ /dev/null @@ -1,33 +0,0 @@ -package versioned.host.exp.exponent.modules.api.screens.events - -import com.facebook.react.bridge.Arguments -import com.facebook.react.uimanager.events.Event -import com.facebook.react.uimanager.events.RCTEventEmitter - -class ScreenTransitionProgressEvent( - viewId: Int, - private val mProgress: Float, - private val mClosing: Boolean, - private val mGoingForward: Boolean, - private val mCoalescingKey: Short -) : Event(viewId) { - override fun getEventName(): String { - return EVENT_NAME - } - - override fun getCoalescingKey(): Short { - return mCoalescingKey - } - - override fun dispatch(rctEventEmitter: RCTEventEmitter) { - val map = Arguments.createMap() - map.putDouble("progress", mProgress.toDouble()) - map.putInt("closing", if (mClosing) 1 else 0) - map.putInt("goingForward", if (mGoingForward) 1 else 0) - rctEventEmitter.receiveEvent(viewTag, eventName, map) - } - - companion object { - const val EVENT_NAME = "topTransitionProgress" - } -} diff --git a/android/expoview/src/main/java/versioned/host/exp/exponent/modules/api/screens/events/ScreenWillAppearEvent.kt b/android/expoview/src/main/java/versioned/host/exp/exponent/modules/api/screens/events/ScreenWillAppearEvent.kt deleted file mode 100644 index a6b00e77bd9fc..0000000000000 --- a/android/expoview/src/main/java/versioned/host/exp/exponent/modules/api/screens/events/ScreenWillAppearEvent.kt +++ /dev/null @@ -1,20 +0,0 @@ -package versioned.host.exp.exponent.modules.api.screens.events - -import com.facebook.react.bridge.Arguments -import com.facebook.react.uimanager.events.Event -import com.facebook.react.uimanager.events.RCTEventEmitter - -class ScreenWillAppearEvent(viewId: Int) : Event(viewId) { - override fun getEventName() = EVENT_NAME - - // All events for a given view can be coalesced. - override fun getCoalescingKey(): Short = 0 - - override fun dispatch(rctEventEmitter: RCTEventEmitter) { - rctEventEmitter.receiveEvent(viewTag, eventName, Arguments.createMap()) - } - - companion object { - const val EVENT_NAME = "topWillAppear" - } -} diff --git a/android/expoview/src/main/java/versioned/host/exp/exponent/modules/api/screens/events/ScreenWillDisappearEvent.kt b/android/expoview/src/main/java/versioned/host/exp/exponent/modules/api/screens/events/ScreenWillDisappearEvent.kt deleted file mode 100644 index e9985f48fcd9c..0000000000000 --- a/android/expoview/src/main/java/versioned/host/exp/exponent/modules/api/screens/events/ScreenWillDisappearEvent.kt +++ /dev/null @@ -1,20 +0,0 @@ -package versioned.host.exp.exponent.modules.api.screens.events - -import com.facebook.react.bridge.Arguments -import com.facebook.react.uimanager.events.Event -import com.facebook.react.uimanager.events.RCTEventEmitter - -class ScreenWillDisappearEvent(viewId: Int) : Event(viewId) { - override fun getEventName() = EVENT_NAME - - // All events for a given view can be coalesced. - override fun getCoalescingKey(): Short = 0 - - override fun dispatch(rctEventEmitter: RCTEventEmitter) { - rctEventEmitter.receiveEvent(viewTag, eventName, Arguments.createMap()) - } - - companion object { - const val EVENT_NAME = "topWillDisappear" - } -} diff --git a/android/expoview/src/main/java/versioned/host/exp/exponent/modules/api/screens/events/StackFinishTransitioningEvent.kt b/android/expoview/src/main/java/versioned/host/exp/exponent/modules/api/screens/events/StackFinishTransitioningEvent.kt deleted file mode 100644 index 8d9c25faf404c..0000000000000 --- a/android/expoview/src/main/java/versioned/host/exp/exponent/modules/api/screens/events/StackFinishTransitioningEvent.kt +++ /dev/null @@ -1,20 +0,0 @@ -package versioned.host.exp.exponent.modules.api.screens.events - -import com.facebook.react.bridge.Arguments -import com.facebook.react.uimanager.events.Event -import com.facebook.react.uimanager.events.RCTEventEmitter - -class StackFinishTransitioningEvent(viewId: Int) : Event(viewId) { - override fun getEventName() = EVENT_NAME - - // All events for a given view can be coalesced. - override fun getCoalescingKey(): Short = 0 - - override fun dispatch(rctEventEmitter: RCTEventEmitter) { - rctEventEmitter.receiveEvent(viewTag, eventName, Arguments.createMap()) - } - - companion object { - const val EVENT_NAME = "topFinishTransitioning" - } -} diff --git a/tools/src/vendoring/config/expoGoConfig.ts b/tools/src/vendoring/config/expoGoConfig.ts index ac06e4239e4e3..f745e46e1f096 100644 --- a/tools/src/vendoring/config/expoGoConfig.ts +++ b/tools/src/vendoring/config/expoGoConfig.ts @@ -109,23 +109,22 @@ const config: VendoringTargetConfig = { source: 'https://github.com/software-mansion/react-native-screens.git', semverPrefix: '~', ios: {}, - // TODO: Uncomment once the new vendoring scripts supports Android - // android: { - // transforms: { - // content: [ - // { - // paths: 'ScreenStack.kt', - // find: /(?=^class ScreenStack\()/m, - // replaceWith: `import host.exp.expoview.R\n\n`, - // }, - // { - // paths: 'ScreenStackHeaderConfig.kt', - // find: /(?=^class ScreenStackHeaderConfig\()/m, - // replaceWith: `import host.exp.expoview.BuildConfig\nimport host.exp.expoview.R\n\n`, - // }, - // ], - // }, - // }, + android: { + transforms: { + content: [ + { + paths: 'ScreenStack.kt', + find: /(?=^class ScreenStack\()/m, + replaceWith: `import host.exp.expoview.R\n\n`, + }, + { + paths: 'ScreenStackHeaderConfig.kt', + find: /(?=^class ScreenStackHeaderConfig\()/m, + replaceWith: `import host.exp.expoview.BuildConfig\nimport host.exp.expoview.R\n\n`, + }, + ], + }, + }, }, 'amazon-cognito-identity-js': { source: 'https://github.com/aws-amplify/amplify-js.git', From 39719d6dccccb8c37b4a5c0f5856b57e457e1d50 Mon Sep 17 00:00:00 2001 From: Tomasz Sapeta Date: Tue, 4 Oct 2022 09:51:14 +0200 Subject: [PATCH 2/8] Update vendored code on Android --- android/expoview/build.gradle | 5 - .../host/exp/exponent/ExponentPackage.kt | 2 +- .../react-native-screens/android/build.gradle | 117 +++++ .../android/gradle.properties | 23 + .../gradle/wrapper/gradle-wrapper.properties | 5 + .../react-native-screens/android/gradlew | 164 ++++++++ .../react-native-screens/android/gradlew.bat | 90 ++++ .../android/settings.gradle | 1 + .../android/spotless.gradle | 12 + .../rnscreens/FabricEnabledViewGroup.kt | 49 +++ .../android/src/main/AndroidManifest.xml | 5 + .../swmansion/rnscreens/CustomSearchView.kt | 73 ++++ .../com/swmansion/rnscreens/CustomToolbar.kt | 7 + .../rnscreens/FragmentBackPressOverrider.kt | 29 ++ .../swmansion/rnscreens/LifecycleHelper.kt | 60 +++ .../swmansion/rnscreens/RNScreensPackage.kt | 21 + .../java/com/swmansion/rnscreens/Screen.kt | 258 ++++++++++++ .../swmansion/rnscreens/ScreenContainer.kt | 373 ++++++++++++++++ .../rnscreens/ScreenContainerViewManager.kt | 40 ++ .../com/swmansion/rnscreens/ScreenFragment.kt | 323 ++++++++++++++ .../com/swmansion/rnscreens/ScreenStack.kt | 334 +++++++++++++++ .../rnscreens/ScreenStackFragment.kt | 257 +++++++++++ .../rnscreens/ScreenStackHeaderConfig.kt | 398 ++++++++++++++++++ .../ScreenStackHeaderConfigViewManager.kt | 196 +++++++++ .../rnscreens/ScreenStackHeaderSubview.kt | 38 ++ .../ScreenStackHeaderSubviewManager.kt | 41 ++ .../rnscreens/ScreenStackViewManager.kt | 75 ++++ .../swmansion/rnscreens/ScreenViewManager.kt | 183 ++++++++ .../swmansion/rnscreens/ScreenWindowTraits.kt | 276 ++++++++++++ .../swmansion/rnscreens/ScreensShadowNode.kt | 19 + .../swmansion/rnscreens/SearchBarManager.kt | 107 +++++ .../com/swmansion/rnscreens/SearchBarView.kt | 155 +++++++ .../rnscreens/SearchViewFormatter.kt | 67 +++ .../events/HeaderBackButtonClickedEvent.kt | 24 ++ .../rnscreens/events/ScreenAppearEvent.kt | 20 + .../rnscreens/events/ScreenDisappearEvent.kt | 20 + .../rnscreens/events/ScreenDismissedEvent.kt | 23 + .../events/ScreenTransitionProgressEvent.kt | 33 ++ .../rnscreens/events/ScreenWillAppearEvent.kt | 20 + .../events/ScreenWillDisappearEvent.kt | 20 + .../events/StackFinishTransitioningEvent.kt | 20 + .../android/src/main/jni/CMakeLists.txt | 72 ++++ .../android/src/main/jni/rnscreens.cpp | 16 + .../android/src/main/jni/rnscreens.h | 28 ++ .../main/res/anim/rns_default_enter_in.xml | 18 + .../main/res/anim/rns_default_enter_out.xml | 19 + .../src/main/res/anim/rns_default_exit_in.xml | 17 + .../main/res/anim/rns_default_exit_out.xml | 18 + .../main/res/anim/rns_fade_from_bottom.xml | 14 + .../android/src/main/res/anim/rns_fade_in.xml | 7 + .../src/main/res/anim/rns_fade_out.xml | 7 + .../src/main/res/anim/rns_fade_to_bottom.xml | 15 + .../src/main/res/anim/rns_no_animation_20.xml | 6 + .../main/res/anim/rns_no_animation_250.xml | 7 + .../main/res/anim/rns_no_animation_350.xml | 6 + .../main/res/anim/rns_no_animation_medium.xml | 7 + .../res/anim/rns_slide_in_from_bottom.xml | 7 + .../main/res/anim/rns_slide_in_from_left.xml | 5 + .../main/res/anim/rns_slide_in_from_right.xml | 5 + .../main/res/anim/rns_slide_out_to_bottom.xml | 7 + .../main/res/anim/rns_slide_out_to_left.xml | 5 + .../main/res/anim/rns_slide_out_to_right.xml | 5 + .../RNSScreenManagerDelegate.java | 0 .../RNSScreenManagerInterface.java | 0 ...creenStackHeaderConfigManagerDelegate.java | 0 ...reenStackHeaderConfigManagerInterface.java | 0 ...reenStackHeaderSubviewManagerDelegate.java | 0 ...eenStackHeaderSubviewManagerInterface.java | 0 .../RNSScreenStackManagerDelegate.java | 0 .../RNSScreenStackManagerInterface.java | 0 .../rnscreens/FabricEnabledViewGroup.kt | 10 + apps/bare-expo/package.json | 2 +- apps/bare-sandbox/package.json | 2 +- apps/native-component-list/package.json | 2 +- home/package.json | 2 +- packages/expo-stories/package.json | 2 +- packages/expo/bundledNativeModules.json | 2 +- tools/src/vendoring/config/expoGoConfig.ts | 17 +- 78 files changed, 4285 insertions(+), 28 deletions(-) create mode 100644 android/vendored/unversioned/react-native-screens/android/build.gradle create mode 100644 android/vendored/unversioned/react-native-screens/android/gradle.properties create mode 100644 android/vendored/unversioned/react-native-screens/android/gradle/wrapper/gradle-wrapper.properties create mode 100755 android/vendored/unversioned/react-native-screens/android/gradlew create mode 100644 android/vendored/unversioned/react-native-screens/android/gradlew.bat create mode 100644 android/vendored/unversioned/react-native-screens/android/settings.gradle create mode 100644 android/vendored/unversioned/react-native-screens/android/spotless.gradle create mode 100644 android/vendored/unversioned/react-native-screens/android/src/fabric/java/com/swmansion/rnscreens/FabricEnabledViewGroup.kt create mode 100644 android/vendored/unversioned/react-native-screens/android/src/main/AndroidManifest.xml create mode 100644 android/vendored/unversioned/react-native-screens/android/src/main/java/com/swmansion/rnscreens/CustomSearchView.kt create mode 100644 android/vendored/unversioned/react-native-screens/android/src/main/java/com/swmansion/rnscreens/CustomToolbar.kt create mode 100644 android/vendored/unversioned/react-native-screens/android/src/main/java/com/swmansion/rnscreens/FragmentBackPressOverrider.kt create mode 100644 android/vendored/unversioned/react-native-screens/android/src/main/java/com/swmansion/rnscreens/LifecycleHelper.kt create mode 100644 android/vendored/unversioned/react-native-screens/android/src/main/java/com/swmansion/rnscreens/RNScreensPackage.kt create mode 100644 android/vendored/unversioned/react-native-screens/android/src/main/java/com/swmansion/rnscreens/Screen.kt create mode 100644 android/vendored/unversioned/react-native-screens/android/src/main/java/com/swmansion/rnscreens/ScreenContainer.kt create mode 100644 android/vendored/unversioned/react-native-screens/android/src/main/java/com/swmansion/rnscreens/ScreenContainerViewManager.kt create mode 100644 android/vendored/unversioned/react-native-screens/android/src/main/java/com/swmansion/rnscreens/ScreenFragment.kt create mode 100644 android/vendored/unversioned/react-native-screens/android/src/main/java/com/swmansion/rnscreens/ScreenStack.kt create mode 100644 android/vendored/unversioned/react-native-screens/android/src/main/java/com/swmansion/rnscreens/ScreenStackFragment.kt create mode 100644 android/vendored/unversioned/react-native-screens/android/src/main/java/com/swmansion/rnscreens/ScreenStackHeaderConfig.kt create mode 100644 android/vendored/unversioned/react-native-screens/android/src/main/java/com/swmansion/rnscreens/ScreenStackHeaderConfigViewManager.kt create mode 100644 android/vendored/unversioned/react-native-screens/android/src/main/java/com/swmansion/rnscreens/ScreenStackHeaderSubview.kt create mode 100644 android/vendored/unversioned/react-native-screens/android/src/main/java/com/swmansion/rnscreens/ScreenStackHeaderSubviewManager.kt create mode 100644 android/vendored/unversioned/react-native-screens/android/src/main/java/com/swmansion/rnscreens/ScreenStackViewManager.kt create mode 100644 android/vendored/unversioned/react-native-screens/android/src/main/java/com/swmansion/rnscreens/ScreenViewManager.kt create mode 100644 android/vendored/unversioned/react-native-screens/android/src/main/java/com/swmansion/rnscreens/ScreenWindowTraits.kt create mode 100644 android/vendored/unversioned/react-native-screens/android/src/main/java/com/swmansion/rnscreens/ScreensShadowNode.kt create mode 100644 android/vendored/unversioned/react-native-screens/android/src/main/java/com/swmansion/rnscreens/SearchBarManager.kt create mode 100644 android/vendored/unversioned/react-native-screens/android/src/main/java/com/swmansion/rnscreens/SearchBarView.kt create mode 100644 android/vendored/unversioned/react-native-screens/android/src/main/java/com/swmansion/rnscreens/SearchViewFormatter.kt create mode 100644 android/vendored/unversioned/react-native-screens/android/src/main/java/com/swmansion/rnscreens/events/HeaderBackButtonClickedEvent.kt create mode 100644 android/vendored/unversioned/react-native-screens/android/src/main/java/com/swmansion/rnscreens/events/ScreenAppearEvent.kt create mode 100644 android/vendored/unversioned/react-native-screens/android/src/main/java/com/swmansion/rnscreens/events/ScreenDisappearEvent.kt create mode 100644 android/vendored/unversioned/react-native-screens/android/src/main/java/com/swmansion/rnscreens/events/ScreenDismissedEvent.kt create mode 100644 android/vendored/unversioned/react-native-screens/android/src/main/java/com/swmansion/rnscreens/events/ScreenTransitionProgressEvent.kt create mode 100644 android/vendored/unversioned/react-native-screens/android/src/main/java/com/swmansion/rnscreens/events/ScreenWillAppearEvent.kt create mode 100644 android/vendored/unversioned/react-native-screens/android/src/main/java/com/swmansion/rnscreens/events/ScreenWillDisappearEvent.kt create mode 100644 android/vendored/unversioned/react-native-screens/android/src/main/java/com/swmansion/rnscreens/events/StackFinishTransitioningEvent.kt create mode 100644 android/vendored/unversioned/react-native-screens/android/src/main/jni/CMakeLists.txt create mode 100644 android/vendored/unversioned/react-native-screens/android/src/main/jni/rnscreens.cpp create mode 100644 android/vendored/unversioned/react-native-screens/android/src/main/jni/rnscreens.h create mode 100644 android/vendored/unversioned/react-native-screens/android/src/main/res/anim/rns_default_enter_in.xml create mode 100644 android/vendored/unversioned/react-native-screens/android/src/main/res/anim/rns_default_enter_out.xml create mode 100644 android/vendored/unversioned/react-native-screens/android/src/main/res/anim/rns_default_exit_in.xml create mode 100644 android/vendored/unversioned/react-native-screens/android/src/main/res/anim/rns_default_exit_out.xml create mode 100644 android/vendored/unversioned/react-native-screens/android/src/main/res/anim/rns_fade_from_bottom.xml create mode 100644 android/vendored/unversioned/react-native-screens/android/src/main/res/anim/rns_fade_in.xml create mode 100644 android/vendored/unversioned/react-native-screens/android/src/main/res/anim/rns_fade_out.xml create mode 100644 android/vendored/unversioned/react-native-screens/android/src/main/res/anim/rns_fade_to_bottom.xml create mode 100644 android/vendored/unversioned/react-native-screens/android/src/main/res/anim/rns_no_animation_20.xml create mode 100644 android/vendored/unversioned/react-native-screens/android/src/main/res/anim/rns_no_animation_250.xml create mode 100644 android/vendored/unversioned/react-native-screens/android/src/main/res/anim/rns_no_animation_350.xml create mode 100644 android/vendored/unversioned/react-native-screens/android/src/main/res/anim/rns_no_animation_medium.xml create mode 100644 android/vendored/unversioned/react-native-screens/android/src/main/res/anim/rns_slide_in_from_bottom.xml create mode 100644 android/vendored/unversioned/react-native-screens/android/src/main/res/anim/rns_slide_in_from_left.xml create mode 100644 android/vendored/unversioned/react-native-screens/android/src/main/res/anim/rns_slide_in_from_right.xml create mode 100644 android/vendored/unversioned/react-native-screens/android/src/main/res/anim/rns_slide_out_to_bottom.xml create mode 100644 android/vendored/unversioned/react-native-screens/android/src/main/res/anim/rns_slide_out_to_left.xml create mode 100644 android/vendored/unversioned/react-native-screens/android/src/main/res/anim/rns_slide_out_to_right.xml rename android/{expoview/src/main => vendored/unversioned/react-native-screens/android/src/paper}/java/com/facebook/react/viewmanagers/RNSScreenManagerDelegate.java (100%) rename android/{expoview/src/main => vendored/unversioned/react-native-screens/android/src/paper}/java/com/facebook/react/viewmanagers/RNSScreenManagerInterface.java (100%) rename android/{expoview/src/main => vendored/unversioned/react-native-screens/android/src/paper}/java/com/facebook/react/viewmanagers/RNSScreenStackHeaderConfigManagerDelegate.java (100%) rename android/{expoview/src/main => vendored/unversioned/react-native-screens/android/src/paper}/java/com/facebook/react/viewmanagers/RNSScreenStackHeaderConfigManagerInterface.java (100%) rename android/{expoview/src/main => vendored/unversioned/react-native-screens/android/src/paper}/java/com/facebook/react/viewmanagers/RNSScreenStackHeaderSubviewManagerDelegate.java (100%) rename android/{expoview/src/main => vendored/unversioned/react-native-screens/android/src/paper}/java/com/facebook/react/viewmanagers/RNSScreenStackHeaderSubviewManagerInterface.java (100%) rename android/{expoview/src/main => vendored/unversioned/react-native-screens/android/src/paper}/java/com/facebook/react/viewmanagers/RNSScreenStackManagerDelegate.java (100%) rename android/{expoview/src/main => vendored/unversioned/react-native-screens/android/src/paper}/java/com/facebook/react/viewmanagers/RNSScreenStackManagerInterface.java (100%) create mode 100644 android/vendored/unversioned/react-native-screens/android/src/paper/java/com/swmansion/rnscreens/FabricEnabledViewGroup.kt diff --git a/android/expoview/build.gradle b/android/expoview/build.gradle index 2507a96d5b25b..792e9cb5a65d7 100644 --- a/android/expoview/build.gradle +++ b/android/expoview/build.gradle @@ -392,11 +392,6 @@ dependencies { } } - // react-native-screens - api 'androidx.fragment:fragment:1.2.2' - api 'androidx.coordinatorlayout:coordinatorlayout:1.1.0' - api 'com.google.android.material:material:1.1.0' - api 'com.google.firebase:firebase-core:17.2.3' api 'com.google.firebase:firebase-messaging:22.0.0' api 'com.google.maps.android:android-maps-utils:0.5' diff --git a/android/expoview/src/main/java/versioned/host/exp/exponent/ExponentPackage.kt b/android/expoview/src/main/java/versioned/host/exp/exponent/ExponentPackage.kt index d463e6a680e33..6f5b87a638f12 100644 --- a/android/expoview/src/main/java/versioned/host/exp/exponent/ExponentPackage.kt +++ b/android/expoview/src/main/java/versioned/host/exp/exponent/ExponentPackage.kt @@ -9,6 +9,7 @@ import com.facebook.react.bridge.ReactApplicationContext import com.facebook.react.uimanager.ViewManager import com.shopify.reactnative.flash_list.ReactNativeFlashListPackage import com.shopify.reactnative.skia.RNSkiaPackage +import com.swmansion.rnscreens.RNScreensPackage import com.swmansion.gesturehandler.RNGestureHandlerPackage import com.swmansion.gesturehandler.react.RNGestureHandlerModule import expo.modules.adapters.react.ReactModuleRegistryProvider @@ -43,7 +44,6 @@ import versioned.host.exp.exponent.modules.api.components.webview.RNCWebViewPack import versioned.host.exp.exponent.modules.api.netinfo.NetInfoModule import versioned.host.exp.exponent.modules.api.notifications.NotificationsModule import versioned.host.exp.exponent.modules.api.safeareacontext.SafeAreaContextPackage -import versioned.host.exp.exponent.modules.api.screens.RNScreensPackage import versioned.host.exp.exponent.modules.api.viewshot.RNViewShotModule import versioned.host.exp.exponent.modules.internal.DevMenuModule import versioned.host.exp.exponent.modules.test.ExponentTestNativeModule diff --git a/android/vendored/unversioned/react-native-screens/android/build.gradle b/android/vendored/unversioned/react-native-screens/android/build.gradle new file mode 100644 index 0000000000000..58fe0e43d4811 --- /dev/null +++ b/android/vendored/unversioned/react-native-screens/android/build.gradle @@ -0,0 +1,117 @@ +buildscript { + ext.safeExtGet = {prop, fallback -> + rootProject.ext.has(prop) ? rootProject.ext.get(prop) : fallback + } + repositories { + google() + mavenCentral() + } + dependencies { + classpath('com.android.tools.build:gradle:4.2.2') + classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:${safeExtGet('kotlinVersion', '1.6.21')}" + classpath "com.diffplug.spotless:spotless-plugin-gradle:5.15.0" + } +} + +def isNewArchitectureEnabled() { + // To opt-in for the New Architecture, you can either: + // - Set `newArchEnabled` to true inside the `gradle.properties` file + // - Invoke gradle with `-newArchEnabled=true` + // - Set an environment variable `ORG_GRADLE_PROJECT_newArchEnabled=true` + return project.hasProperty("newArchEnabled") && project.newArchEnabled == "true" +} + +// spotless is only accessible within react-native-screens repo +if (project == rootProject) { + apply from: 'spotless.gradle' +} + +if (isNewArchitectureEnabled()) { + apply plugin: "com.facebook.react" +} +apply plugin: 'com.android.library' +apply plugin: 'kotlin-android' + +def reactNativeArchitectures() { + def value = project.getProperties().get("reactNativeArchitectures") + return value ? value.split(",") : ["armeabi-v7a", "x86", "x86_64", "arm64-v8a"] +} + +android { + compileSdkVersion safeExtGet('compileSdkVersion', 28) + + // Used to override the NDK path/version on internal CI or by allowing + // users to customize the NDK path/version from their root project (e.g. for M1 support) + if (rootProject.hasProperty("ndkPath")) { + ndkPath rootProject.ext.ndkPath + } + if (rootProject.hasProperty("ndkVersion")) { + ndkVersion rootProject.ext.ndkVersion + } + + defaultConfig { + minSdkVersion safeExtGet('minSdkVersion', 21) + targetSdkVersion safeExtGet('targetSdkVersion', 22) + versionCode 1 + versionName "1.0" + buildConfigField "boolean", "IS_NEW_ARCHITECTURE_ENABLED", isNewArchitectureEnabled().toString() + ndk { + abiFilters (*reactNativeArchitectures()) + } + } + lintOptions { + abortOnError false + } + compileOptions { + sourceCompatibility JavaVersion.VERSION_1_8 + targetCompatibility JavaVersion.VERSION_1_8 + } + packagingOptions { + // For some reason gradle only complains about the duplicated version of libreact_render libraries + // while there are more libraries copied in intermediates folder of the lib build directory, we exlude + // only the ones that make the build fail (ideally we should only include librnscreens_modules but we + // are only allowed to specify exlude patterns) + exclude "**/libreact_render*.so" + } + sourceSets.main { + java { + if (isNewArchitectureEnabled()) { + srcDirs += [ + "src/fabric/java", + ] + } else { + srcDirs += [ + "src/paper/java", + "build/generated/source/codegen/java" + ] + } + + } + } +} + +repositories { + maven { + // All of React Native (JS, Obj-C sources, Android binaries) is installed from npm + // Matches the RN Hello World template + // https://github.com/facebook/react-native/blob/1e8f3b11027fe0a7514b4fc97d0798d3c64bc895/local-cli/templates/HelloWorld/android/build.gradle#L21 + url "$projectDir/../node_modules/react-native/android" + } + mavenCentral() + mavenLocal() + google() +} + +dependencies { + if (isNewArchitectureEnabled()) { + implementation project(":ReactAndroid") + } else { + implementation 'com.facebook.react:react-native:+' + } + implementation 'androidx.appcompat:appcompat:1.1.0' + implementation 'androidx.fragment:fragment:1.2.1' + implementation 'androidx.coordinatorlayout:coordinatorlayout:1.1.0' + implementation 'androidx.swiperefreshlayout:swiperefreshlayout:1.0.0' + implementation 'com.google.android.material:material:1.1.0' + implementation "androidx.core:core-ktx:1.5.0" +} diff --git a/android/vendored/unversioned/react-native-screens/android/gradle.properties b/android/vendored/unversioned/react-native-screens/android/gradle.properties new file mode 100644 index 0000000000000..7913518b0167f --- /dev/null +++ b/android/vendored/unversioned/react-native-screens/android/gradle.properties @@ -0,0 +1,23 @@ +# Project-wide Gradle settings. + +# IDE (e.g. Android Studio) users: +# Gradle settings configured through the IDE *will override* +# any settings specified in this file. + +# For more details on how to configure your build environment visit +# http://www.gradle.org/docs/current/userguide/build_environment.html + +# Specifies the JVM arguments used for the daemon process. +# The setting is particularly useful for tweaking memory settings. +# Default value: -Xmx10248m -XX:MaxPermSize=256m +org.gradle.jvmargs=-Xmx2048m -XX:MaxPermSize=512m -XX:+HeapDumpOnOutOfMemoryError -Dfile.encoding=UTF-8 + +# When configured, Gradle will run in incubating parallel mode. +# This option should only be used with decoupled projects. More details, visit +# http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects +# org.gradle.parallel=true + +android.useAndroidX=true +android.enableJetifier=true + +kotlin.code.style=official \ No newline at end of file diff --git a/android/vendored/unversioned/react-native-screens/android/gradle/wrapper/gradle-wrapper.properties b/android/vendored/unversioned/react-native-screens/android/gradle/wrapper/gradle-wrapper.properties new file mode 100644 index 0000000000000..9946c0130b4b9 --- /dev/null +++ b/android/vendored/unversioned/react-native-screens/android/gradle/wrapper/gradle-wrapper.properties @@ -0,0 +1,5 @@ +distributionBase=GRADLE_USER_HOME +distributionPath=wrapper/dists +zipStoreBase=GRADLE_USER_HOME +zipStorePath=wrapper/dists +distributionUrl=https\://services.gradle.org/distributions/gradle-6.9-all.zip \ No newline at end of file diff --git a/android/vendored/unversioned/react-native-screens/android/gradlew b/android/vendored/unversioned/react-native-screens/android/gradlew new file mode 100755 index 0000000000000..91a7e269e19df --- /dev/null +++ b/android/vendored/unversioned/react-native-screens/android/gradlew @@ -0,0 +1,164 @@ +#!/usr/bin/env bash + +############################################################################## +## +## Gradle start up script for UN*X +## +############################################################################## + +# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. +DEFAULT_JVM_OPTS="" + +APP_NAME="Gradle" +APP_BASE_NAME=`basename "$0"` + +# Use the maximum available, or set MAX_FD != -1 to use that value. +MAX_FD="maximum" + +warn ( ) { + echo "$*" +} + +die ( ) { + echo + echo "$*" + echo + exit 1 +} + +# OS specific support (must be 'true' or 'false'). +cygwin=false +msys=false +darwin=false +case "`uname`" in + CYGWIN* ) + cygwin=true + ;; + Darwin* ) + darwin=true + ;; + MINGW* ) + msys=true + ;; +esac + +# For Cygwin, ensure paths are in UNIX format before anything is touched. +if $cygwin ; then + [ -n "$JAVA_HOME" ] && JAVA_HOME=`cygpath --unix "$JAVA_HOME"` +fi + +# Attempt to set APP_HOME +# Resolve links: $0 may be a link +PRG="$0" +# Need this for relative symlinks. +while [ -h "$PRG" ] ; do + ls=`ls -ld "$PRG"` + link=`expr "$ls" : '.*-> \(.*\)$'` + if expr "$link" : '/.*' > /dev/null; then + PRG="$link" + else + PRG=`dirname "$PRG"`"/$link" + fi +done +SAVED="`pwd`" +cd "`dirname \"$PRG\"`/" >&- +APP_HOME="`pwd -P`" +cd "$SAVED" >&- + +CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar + +# Determine the Java command to use to start the JVM. +if [ -n "$JAVA_HOME" ] ; then + if [ -x "$JAVA_HOME/jre/sh/java" ] ; then + # IBM's JDK on AIX uses strange locations for the executables + JAVACMD="$JAVA_HOME/jre/sh/java" + else + JAVACMD="$JAVA_HOME/bin/java" + fi + if [ ! -x "$JAVACMD" ] ; then + die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME + +Please set the JAVA_HOME variable in your environment to match the +location of your Java installation." + fi +else + JAVACMD="java" + which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. + +Please set the JAVA_HOME variable in your environment to match the +location of your Java installation." +fi + +# Increase the maximum file descriptors if we can. +if [ "$cygwin" = "false" -a "$darwin" = "false" ] ; then + MAX_FD_LIMIT=`ulimit -H -n` + if [ $? -eq 0 ] ; then + if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then + MAX_FD="$MAX_FD_LIMIT" + fi + ulimit -n $MAX_FD + if [ $? -ne 0 ] ; then + warn "Could not set maximum file descriptor limit: $MAX_FD" + fi + else + warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT" + fi +fi + +# For Darwin, add options to specify how the application appears in the dock +if $darwin; then + GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\"" +fi + +# For Cygwin, switch paths to Windows format before running java +if $cygwin ; then + APP_HOME=`cygpath --path --mixed "$APP_HOME"` + CLASSPATH=`cygpath --path --mixed "$CLASSPATH"` + + # We build the pattern for arguments to be converted via cygpath + ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null` + SEP="" + for dir in $ROOTDIRSRAW ; do + ROOTDIRS="$ROOTDIRS$SEP$dir" + SEP="|" + done + OURCYGPATTERN="(^($ROOTDIRS))" + # Add a user-defined pattern to the cygpath arguments + if [ "$GRADLE_CYGPATTERN" != "" ] ; then + OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)" + fi + # Now convert the arguments - kludge to limit ourselves to /bin/sh + i=0 + for arg in "$@" ; do + CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -` + CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option + + if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition + eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"` + else + eval `echo args$i`="\"$arg\"" + fi + i=$((i+1)) + done + case $i in + (0) set -- ;; + (1) set -- "$args0" ;; + (2) set -- "$args0" "$args1" ;; + (3) set -- "$args0" "$args1" "$args2" ;; + (4) set -- "$args0" "$args1" "$args2" "$args3" ;; + (5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;; + (6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;; + (7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;; + (8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;; + (9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;; + esac +fi + +# Split up the JVM_OPTS And GRADLE_OPTS values into an array, following the shell quoting and substitution rules +function splitJvmOpts() { + JVM_OPTS=("$@") +} +eval splitJvmOpts $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS +JVM_OPTS[${#JVM_OPTS[*]}]="-Dorg.gradle.appname=$APP_BASE_NAME" + +exec "$JAVACMD" "${JVM_OPTS[@]}" -classpath "$CLASSPATH" org.gradle.wrapper.GradleWrapperMain "$@" diff --git a/android/vendored/unversioned/react-native-screens/android/gradlew.bat b/android/vendored/unversioned/react-native-screens/android/gradlew.bat new file mode 100644 index 0000000000000..aec99730b4e8f --- /dev/null +++ b/android/vendored/unversioned/react-native-screens/android/gradlew.bat @@ -0,0 +1,90 @@ +@if "%DEBUG%" == "" @echo off +@rem ########################################################################## +@rem +@rem Gradle startup script for Windows +@rem +@rem ########################################################################## + +@rem Set local scope for the variables with windows NT shell +if "%OS%"=="Windows_NT" setlocal + +@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. +set DEFAULT_JVM_OPTS= + +set DIRNAME=%~dp0 +if "%DIRNAME%" == "" set DIRNAME=. +set APP_BASE_NAME=%~n0 +set APP_HOME=%DIRNAME% + +@rem Find java.exe +if defined JAVA_HOME goto findJavaFromJavaHome + +set JAVA_EXE=java.exe +%JAVA_EXE% -version >NUL 2>&1 +if "%ERRORLEVEL%" == "0" goto init + +echo. +echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. +echo. +echo Please set the JAVA_HOME variable in your environment to match the +echo location of your Java installation. + +goto fail + +:findJavaFromJavaHome +set JAVA_HOME=%JAVA_HOME:"=% +set JAVA_EXE=%JAVA_HOME%/bin/java.exe + +if exist "%JAVA_EXE%" goto init + +echo. +echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% +echo. +echo Please set the JAVA_HOME variable in your environment to match the +echo location of your Java installation. + +goto fail + +:init +@rem Get command-line arguments, handling Windowz variants + +if not "%OS%" == "Windows_NT" goto win9xME_args +if "%@eval[2+2]" == "4" goto 4NT_args + +:win9xME_args +@rem Slurp the command line arguments. +set CMD_LINE_ARGS= +set _SKIP=2 + +:win9xME_args_slurp +if "x%~1" == "x" goto execute + +set CMD_LINE_ARGS=%* +goto execute + +:4NT_args +@rem Get arguments from the 4NT Shell from JP Software +set CMD_LINE_ARGS=%$ + +:execute +@rem Setup the command line + +set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar + +@rem Execute Gradle +"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS% + +:end +@rem End local scope for the variables with windows NT shell +if "%ERRORLEVEL%"=="0" goto mainEnd + +:fail +rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of +rem the _cmd.exe /c_ return code! +if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1 +exit /b 1 + +:mainEnd +if "%OS%"=="Windows_NT" endlocal + +:omega diff --git a/android/vendored/unversioned/react-native-screens/android/settings.gradle b/android/vendored/unversioned/react-native-screens/android/settings.gradle new file mode 100644 index 0000000000000..c8e23f4979eb4 --- /dev/null +++ b/android/vendored/unversioned/react-native-screens/android/settings.gradle @@ -0,0 +1 @@ +include 'lib' \ No newline at end of file diff --git a/android/vendored/unversioned/react-native-screens/android/spotless.gradle b/android/vendored/unversioned/react-native-screens/android/spotless.gradle new file mode 100644 index 0000000000000..7c9f86c0aa7c4 --- /dev/null +++ b/android/vendored/unversioned/react-native-screens/android/spotless.gradle @@ -0,0 +1,12 @@ +// formatter & linter configuration for kotlin +apply plugin: 'com.diffplug.spotless' + +spotless { + kotlin { + target 'src/**/*.kt' + ktlint("0.40.0") + trimTrailingWhitespace() + indentWithSpaces() + endWithNewline() + } +} diff --git a/android/vendored/unversioned/react-native-screens/android/src/fabric/java/com/swmansion/rnscreens/FabricEnabledViewGroup.kt b/android/vendored/unversioned/react-native-screens/android/src/fabric/java/com/swmansion/rnscreens/FabricEnabledViewGroup.kt new file mode 100644 index 0000000000000..d9e0e58dac49c --- /dev/null +++ b/android/vendored/unversioned/react-native-screens/android/src/fabric/java/com/swmansion/rnscreens/FabricEnabledViewGroup.kt @@ -0,0 +1,49 @@ +package com.swmansion.rnscreens + +import android.view.ViewGroup +import androidx.annotation.UiThread +import com.facebook.react.bridge.ReactContext +import com.facebook.react.bridge.ReadableMap +import com.facebook.react.bridge.WritableMap +import com.facebook.react.bridge.WritableNativeMap +import com.facebook.react.uimanager.FabricViewStateManager +import com.facebook.react.uimanager.PixelUtil +import kotlin.math.abs + +abstract class FabricEnabledViewGroup constructor(context: ReactContext?) : ViewGroup(context), FabricViewStateManager.HasFabricViewStateManager { + private val mFabricViewStateManager: FabricViewStateManager = FabricViewStateManager() + + override fun getFabricViewStateManager(): FabricViewStateManager { + return mFabricViewStateManager + } + + protected fun updateScreenSizeFabric(width: Int, height: Int) { + updateState(width, height) + } + + @UiThread + fun updateState(width: Int, height: Int) { + val realWidth: Float = PixelUtil.toDIPFromPixel(width.toFloat()) + val realHeight: Float = PixelUtil.toDIPFromPixel(height.toFloat()) + + // Check incoming state values. If they're already the correct value, return early to prevent + // infinite UpdateState/SetState loop. + val currentState: ReadableMap? = mFabricViewStateManager.getStateData() + if (currentState != null) { + val delta = 0.9f + val stateFrameHeight: Float = if (currentState.hasKey("frameHeight")) currentState.getDouble("frameHeight").toFloat() else 0f + val stateFrameWidth: Float = if (currentState.hasKey("frameWidth")) currentState.getDouble("frameWidth").toFloat() else 0f + if (abs(stateFrameWidth - realWidth) < delta && + abs(stateFrameHeight - realHeight) < delta + ) { + return + } + } + mFabricViewStateManager.setState { + val map: WritableMap = WritableNativeMap() + map.putDouble("frameWidth", realWidth.toDouble()) + map.putDouble("frameHeight", realHeight.toDouble()) + map + } + } +} diff --git a/android/vendored/unversioned/react-native-screens/android/src/main/AndroidManifest.xml b/android/vendored/unversioned/react-native-screens/android/src/main/AndroidManifest.xml new file mode 100644 index 0000000000000..2de74e803d009 --- /dev/null +++ b/android/vendored/unversioned/react-native-screens/android/src/main/AndroidManifest.xml @@ -0,0 +1,5 @@ + + + + diff --git a/android/vendored/unversioned/react-native-screens/android/src/main/java/com/swmansion/rnscreens/CustomSearchView.kt b/android/vendored/unversioned/react-native-screens/android/src/main/java/com/swmansion/rnscreens/CustomSearchView.kt new file mode 100644 index 0000000000000..b6961844b51b3 --- /dev/null +++ b/android/vendored/unversioned/react-native-screens/android/src/main/java/com/swmansion/rnscreens/CustomSearchView.kt @@ -0,0 +1,73 @@ +package com.swmansion.rnscreens + +import android.content.Context +import androidx.activity.OnBackPressedCallback +import androidx.appcompat.widget.SearchView +import androidx.fragment.app.Fragment + +class CustomSearchView(context: Context, fragment: Fragment) : SearchView(context) { + /* + CustomSearchView uses some variables from SearchView. They are listed below with links to documentation + isIconified - https://developer.android.com/reference/android/widget/SearchView#setIconified(boolean) + maxWidth - https://developer.android.com/reference/android/widget/SearchView#setMaxWidth(int) + setOnSearchClickListener - https://developer.android.com/reference/android/widget/SearchView#setOnSearchClickListener(android.view.View.OnClickListener) + setOnCloseListener - https://developer.android.com/reference/android/widget/SearchView#setOnCloseListener(android.widget.SearchView.OnCloseListener) + */ + private var mCustomOnCloseListener: OnCloseListener? = null + private var mCustomOnSearchClickedListener: OnClickListener? = null + + private var mOnBackPressedCallback: OnBackPressedCallback = + object : OnBackPressedCallback(true) { + override fun handleOnBackPressed() { + isIconified = true + } + } + + private val backPressOverrider = FragmentBackPressOverrider(fragment, mOnBackPressedCallback) + + var overrideBackAction: Boolean + set(value) { + backPressOverrider.overrideBackAction = value + } + get() = backPressOverrider.overrideBackAction + + fun focus() { + isIconified = false + requestFocusFromTouch() + } + + override fun setOnCloseListener(listener: OnCloseListener?) { + mCustomOnCloseListener = listener + } + + override fun setOnSearchClickListener(listener: OnClickListener?) { + mCustomOnSearchClickedListener = listener + } + + override fun onAttachedToWindow() { + super.onAttachedToWindow() + if (!isIconified) { + backPressOverrider.maybeAddBackCallback() + } + } + + override fun onDetachedFromWindow() { + super.onDetachedFromWindow() + backPressOverrider.removeBackCallbackIfAdded() + } + + init { + super.setOnSearchClickListener { v -> + mCustomOnSearchClickedListener?.onClick(v) + backPressOverrider.maybeAddBackCallback() + } + + super.setOnCloseListener { + val result = mCustomOnCloseListener?.onClose() ?: false + backPressOverrider.removeBackCallbackIfAdded() + result + } + + maxWidth = Integer.MAX_VALUE + } +} diff --git a/android/vendored/unversioned/react-native-screens/android/src/main/java/com/swmansion/rnscreens/CustomToolbar.kt b/android/vendored/unversioned/react-native-screens/android/src/main/java/com/swmansion/rnscreens/CustomToolbar.kt new file mode 100644 index 0000000000000..3bc36355aad65 --- /dev/null +++ b/android/vendored/unversioned/react-native-screens/android/src/main/java/com/swmansion/rnscreens/CustomToolbar.kt @@ -0,0 +1,7 @@ +package com.swmansion.rnscreens + +import android.content.Context +import androidx.appcompat.widget.Toolbar + +// This class is used to store config closer to search bar +open class CustomToolbar(context: Context, val config: ScreenStackHeaderConfig) : Toolbar(context) diff --git a/android/vendored/unversioned/react-native-screens/android/src/main/java/com/swmansion/rnscreens/FragmentBackPressOverrider.kt b/android/vendored/unversioned/react-native-screens/android/src/main/java/com/swmansion/rnscreens/FragmentBackPressOverrider.kt new file mode 100644 index 0000000000000..0cf1706a85c69 --- /dev/null +++ b/android/vendored/unversioned/react-native-screens/android/src/main/java/com/swmansion/rnscreens/FragmentBackPressOverrider.kt @@ -0,0 +1,29 @@ +package com.swmansion.rnscreens + +import androidx.activity.OnBackPressedCallback +import androidx.fragment.app.Fragment + +class FragmentBackPressOverrider( + private val fragment: Fragment, + private val mOnBackPressedCallback: OnBackPressedCallback +) { + private var mIsBackCallbackAdded: Boolean = false + var overrideBackAction: Boolean = true + + fun maybeAddBackCallback() { + if (!mIsBackCallbackAdded && overrideBackAction) { + fragment.activity?.onBackPressedDispatcher?.addCallback( + fragment, + mOnBackPressedCallback + ) + mIsBackCallbackAdded = true + } + } + + fun removeBackCallbackIfAdded() { + if (mIsBackCallbackAdded) { + mOnBackPressedCallback.remove() + mIsBackCallbackAdded = false + } + } +} diff --git a/android/vendored/unversioned/react-native-screens/android/src/main/java/com/swmansion/rnscreens/LifecycleHelper.kt b/android/vendored/unversioned/react-native-screens/android/src/main/java/com/swmansion/rnscreens/LifecycleHelper.kt new file mode 100644 index 0000000000000..41bfc802df603 --- /dev/null +++ b/android/vendored/unversioned/react-native-screens/android/src/main/java/com/swmansion/rnscreens/LifecycleHelper.kt @@ -0,0 +1,60 @@ +package com.swmansion.rnscreens + +import android.view.View +import androidx.fragment.app.Fragment +import androidx.lifecycle.Lifecycle +import androidx.lifecycle.LifecycleObserver + +class LifecycleHelper { + private val mViewToLifecycleMap: MutableMap = HashMap() + private val mRegisterOnLayoutChange: View.OnLayoutChangeListener = object : View.OnLayoutChangeListener { + override fun onLayoutChange( + view: View, + i: Int, + i1: Int, + i2: Int, + i3: Int, + i4: Int, + i5: Int, + i6: Int, + i7: Int + ) { + registerViewWithLifecycleOwner(view) + view.removeOnLayoutChangeListener(this) + } + } + + private fun registerViewWithLifecycleOwner(view: View) { + val parent = findNearestScreenFragmentAncestor(view) + if (parent != null && view is LifecycleObserver) { + val lifecycle = parent.lifecycle + lifecycle.addObserver((view as LifecycleObserver)) + mViewToLifecycleMap[view] = lifecycle + } + } + + fun register(view: T) where T : View, T : LifecycleObserver? { + // we need to wait until view is mounted in the hierarchy as this method is called only at the + // moment of the view creation. In order to register lifecycle observer we need to find ancestor + // of type Screen and this can only happen when the view is properly attached. We rely on + // Android's onLayout callback being triggered when the view gets added to the hierarchy and + // only then we attempt to locate lifecycle owner ancestor. + view.addOnLayoutChangeListener(mRegisterOnLayoutChange) + } + + fun unregister(view: T) where T : View, T : LifecycleObserver? { + mViewToLifecycleMap[view]?.removeObserver(view) + } + + companion object { + fun findNearestScreenFragmentAncestor(view: View): Fragment? { + var parent = view.parent + while (parent != null && parent !is Screen) { + parent = parent.parent + } + return if (parent != null) { + (parent as Screen).fragment + } else null + } + } +} diff --git a/android/vendored/unversioned/react-native-screens/android/src/main/java/com/swmansion/rnscreens/RNScreensPackage.kt b/android/vendored/unversioned/react-native-screens/android/src/main/java/com/swmansion/rnscreens/RNScreensPackage.kt new file mode 100644 index 0000000000000..371ed8cfc3c6e --- /dev/null +++ b/android/vendored/unversioned/react-native-screens/android/src/main/java/com/swmansion/rnscreens/RNScreensPackage.kt @@ -0,0 +1,21 @@ +package com.swmansion.rnscreens + +import com.facebook.react.ReactPackage +import com.facebook.react.bridge.NativeModule +import com.facebook.react.bridge.ReactApplicationContext +import com.facebook.react.uimanager.ViewManager + +class RNScreensPackage : ReactPackage { + override fun createNativeModules(reactContext: ReactApplicationContext): List = + emptyList() + + override fun createViewManagers(reactContext: ReactApplicationContext) = + listOf>( + ScreenContainerViewManager(), + ScreenViewManager(), + ScreenStackViewManager(), + ScreenStackHeaderConfigViewManager(), + ScreenStackHeaderSubviewManager(), + SearchBarManager() + ) +} diff --git a/android/vendored/unversioned/react-native-screens/android/src/main/java/com/swmansion/rnscreens/Screen.kt b/android/vendored/unversioned/react-native-screens/android/src/main/java/com/swmansion/rnscreens/Screen.kt new file mode 100644 index 0000000000000..dac3ac9013462 --- /dev/null +++ b/android/vendored/unversioned/react-native-screens/android/src/main/java/com/swmansion/rnscreens/Screen.kt @@ -0,0 +1,258 @@ +package com.swmansion.rnscreens + +import android.annotation.SuppressLint +import android.content.pm.ActivityInfo +import android.graphics.Paint +import android.os.Parcelable +import android.util.SparseArray +import android.view.ViewGroup +import android.view.WindowManager +import android.webkit.WebView +import com.facebook.react.bridge.GuardedRunnable +import com.facebook.react.bridge.ReactContext +import com.facebook.react.uimanager.UIManagerModule + +@SuppressLint("ViewConstructor") +class Screen constructor(context: ReactContext?) : FabricEnabledViewGroup(context) { + + var fragment: ScreenFragment? = null + var container: ScreenContainer<*>? = null + var activityState: ActivityState? = null + private set + private var mTransitioning = false + var stackPresentation = StackPresentation.PUSH + var replaceAnimation = ReplaceAnimation.POP + var stackAnimation = StackAnimation.DEFAULT + var isGestureEnabled = true + var screenOrientation: Int? = null + private set + private var mStatusBarStyle: String? = null + private var mStatusBarHidden: Boolean? = null + private var mStatusBarTranslucent: Boolean? = null + private var mStatusBarColor: Int? = null + private var mNavigationBarColor: Int? = null + private var mNavigationBarHidden: Boolean? = null + var isStatusBarAnimated: Boolean? = null + private var mNativeBackButtonDismissalEnabled = true + + init { + // we set layout params as WindowManager.LayoutParams to workaround the issue with TextInputs + // not displaying modal menus (e.g., copy/paste or selection). The missing menus are due to the + // fact that TextView implementation is expected to be attached to window when layout happens. + // Then, at the moment of layout it checks whether window type is in a reasonable range to tell + // whether it should enable selection controls (see Editor.java#prepareCursorControllers). + // With screens, however, the text input component can be laid out before it is attached, in + // that case TextView tries to get window type property from the oldest existing parent, which + // in this case is a Screen class, as it is the root of the screen that is about to be attached. + // Setting params this way is not the most elegant way to solve this problem but workarounds it + // for the time being + layoutParams = WindowManager.LayoutParams(WindowManager.LayoutParams.TYPE_APPLICATION) + } + + override fun dispatchSaveInstanceState(container: SparseArray) { + // do nothing, react native will keep the view hierarchy so no need to serialize/deserialize + // view's states. The side effect of restoring is that TextInput components would trigger + // set-text events which may confuse text input handling. + } + + override fun dispatchRestoreInstanceState(container: SparseArray) { + // ignore restoring instance state too as we are not saving anything anyways. + } + + override fun onLayout(changed: Boolean, l: Int, t: Int, r: Int, b: Int) { + if (changed) { + val width = r - l + val height = b - t + if (BuildConfig.IS_NEW_ARCHITECTURE_ENABLED) { + updateScreenSizeFabric(width, height) + } else { + updateScreenSizePaper(width, height) + } + } + } + + private fun updateScreenSizePaper(width: Int, height: Int) { + val reactContext = context as ReactContext + reactContext.runOnNativeModulesQueueThread( + object : GuardedRunnable(reactContext) { + override fun runGuarded() { + reactContext + .getNativeModule(UIManagerModule::class.java) + ?.updateNodeSize(id, width, height) + } + }) + } + + val headerConfig: ScreenStackHeaderConfig? + get() = getChildAt(0) as? ScreenStackHeaderConfig + + /** + * While transitioning this property allows to optimize rendering behavior on Android and provide + * a correct blending options for the animated screen. It is turned on automatically by the + * container when transitioning is detected and turned off immediately after + */ + fun setTransitioning(transitioning: Boolean) { + if (mTransitioning == transitioning) { + return + } + mTransitioning = transitioning + val isWebViewInScreen = hasWebView(this) + if (isWebViewInScreen && layerType != LAYER_TYPE_HARDWARE) { + return + } + super.setLayerType( + if (transitioning && !isWebViewInScreen) LAYER_TYPE_HARDWARE else LAYER_TYPE_NONE, + null + ) + } + + private fun hasWebView(viewGroup: ViewGroup): Boolean { + for (i in 0 until viewGroup.childCount) { + val child = viewGroup.getChildAt(i) + if (child is WebView) { + return true + } else if (child is ViewGroup) { + if (hasWebView(child)) { + return true + } + } + } + return false + } + + override fun setLayerType(layerType: Int, paint: Paint?) { + // ignore - layer type is controlled by `transitioning` prop + } + + fun setActivityState(activityState: ActivityState) { + if (activityState == this.activityState) { + return + } + this.activityState = activityState + container?.notifyChildUpdate() + } + + fun setScreenOrientation(screenOrientation: String?) { + if (screenOrientation == null) { + this.screenOrientation = null + return + } + ScreenWindowTraits.applyDidSetOrientation() + this.screenOrientation = when (screenOrientation) { + "all" -> ActivityInfo.SCREEN_ORIENTATION_FULL_SENSOR + "portrait" -> ActivityInfo.SCREEN_ORIENTATION_SENSOR_PORTRAIT + "portrait_up" -> ActivityInfo.SCREEN_ORIENTATION_PORTRAIT + "portrait_down" -> ActivityInfo.SCREEN_ORIENTATION_REVERSE_PORTRAIT + "landscape" -> ActivityInfo.SCREEN_ORIENTATION_SENSOR_LANDSCAPE + "landscape_left" -> ActivityInfo.SCREEN_ORIENTATION_REVERSE_LANDSCAPE + "landscape_right" -> ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE + else -> ActivityInfo.SCREEN_ORIENTATION_UNSPECIFIED + } + + fragment?.let { ScreenWindowTraits.setOrientation(this, it.tryGetActivity()) } + } + + // Accepts one of 4 accessibility flags + // developer.android.com/reference/android/view/View#attr_android:importantForAccessibility + fun changeAccessibilityMode(mode: Int) { + this.importantForAccessibility = mode + this.headerConfig?.toolbar?.importantForAccessibility = mode + } + + var statusBarStyle: String? + get() = mStatusBarStyle + set(statusBarStyle) { + if (statusBarStyle != null) { + ScreenWindowTraits.applyDidSetStatusBarAppearance() + } + mStatusBarStyle = statusBarStyle + fragment?.let { ScreenWindowTraits.setStyle(this, it.tryGetActivity(), it.tryGetContext()) } + } + + var isStatusBarHidden: Boolean? + get() = mStatusBarHidden + set(statusBarHidden) { + if (statusBarHidden != null) { + ScreenWindowTraits.applyDidSetStatusBarAppearance() + } + mStatusBarHidden = statusBarHidden + fragment?.let { ScreenWindowTraits.setHidden(this, it.tryGetActivity()) } + } + + var isStatusBarTranslucent: Boolean? + get() = mStatusBarTranslucent + set(statusBarTranslucent) { + if (statusBarTranslucent != null) { + ScreenWindowTraits.applyDidSetStatusBarAppearance() + } + mStatusBarTranslucent = statusBarTranslucent + fragment?.let { + ScreenWindowTraits.setTranslucent( + this, + it.tryGetActivity(), + it.tryGetContext() + ) + } + } + + var statusBarColor: Int? + get() = mStatusBarColor + set(statusBarColor) { + if (statusBarColor != null) { + ScreenWindowTraits.applyDidSetStatusBarAppearance() + } + mStatusBarColor = statusBarColor + fragment?.let { ScreenWindowTraits.setColor(this, it.tryGetActivity(), it.tryGetContext()) } + } + + var navigationBarColor: Int? + get() = mNavigationBarColor + set(navigationBarColor) { + if (navigationBarColor != null) { + ScreenWindowTraits.applyDidSetNavigationBarAppearance() + } + mNavigationBarColor = navigationBarColor + fragment?.let { ScreenWindowTraits.setNavigationBarColor(this, it.tryGetActivity()) } + } + + var isNavigationBarHidden: Boolean? + get() = mNavigationBarHidden + set(navigationBarHidden) { + if (navigationBarHidden != null) { + ScreenWindowTraits.applyDidSetNavigationBarAppearance() + } + mNavigationBarHidden = navigationBarHidden + fragment?.let { + ScreenWindowTraits.setNavigationBarHidden( + this, + it.tryGetActivity(), + ) + } + } + + var nativeBackButtonDismissalEnabled: Boolean + get() = mNativeBackButtonDismissalEnabled + set(enableNativeBackButtonDismissal) { + mNativeBackButtonDismissalEnabled = enableNativeBackButtonDismissal + } + + enum class StackPresentation { + PUSH, MODAL, TRANSPARENT_MODAL + } + + enum class StackAnimation { + DEFAULT, NONE, FADE, SLIDE_FROM_BOTTOM, SLIDE_FROM_RIGHT, SLIDE_FROM_LEFT, FADE_FROM_BOTTOM + } + + enum class ReplaceAnimation { + PUSH, POP + } + + enum class ActivityState { + INACTIVE, TRANSITIONING_OR_BELOW_TOP, ON_TOP + } + + enum class WindowTraits { + ORIENTATION, COLOR, STYLE, TRANSLUCENT, HIDDEN, ANIMATED, NAVIGATION_BAR_COLOR, NAVIGATION_BAR_HIDDEN + } +} diff --git a/android/vendored/unversioned/react-native-screens/android/src/main/java/com/swmansion/rnscreens/ScreenContainer.kt b/android/vendored/unversioned/react-native-screens/android/src/main/java/com/swmansion/rnscreens/ScreenContainer.kt new file mode 100644 index 0000000000000..39a51135478b6 --- /dev/null +++ b/android/vendored/unversioned/react-native-screens/android/src/main/java/com/swmansion/rnscreens/ScreenContainer.kt @@ -0,0 +1,373 @@ +package com.swmansion.rnscreens + +import android.content.Context +import android.content.ContextWrapper +import android.view.View +import android.view.ViewGroup +import android.view.ViewParent +import android.view.inputmethod.InputMethodManager +import androidx.fragment.app.Fragment +import androidx.fragment.app.FragmentActivity +import androidx.fragment.app.FragmentManager +import androidx.fragment.app.FragmentTransaction +import com.facebook.react.ReactRootView +import com.facebook.react.bridge.ReactContext +import com.facebook.react.modules.core.ChoreographerCompat +import com.facebook.react.modules.core.ReactChoreographer +import com.swmansion.rnscreens.Screen.ActivityState + +open class ScreenContainer(context: Context?) : ViewGroup(context) { + @JvmField + protected val mScreenFragments = ArrayList() + @JvmField + protected var mFragmentManager: FragmentManager? = null + private var mIsAttached = false + private var mNeedUpdate = false + private var mLayoutEnqueued = false + private val mLayoutCallback: ChoreographerCompat.FrameCallback = object : ChoreographerCompat.FrameCallback() { + override fun doFrame(frameTimeNanos: Long) { + mLayoutEnqueued = false + measure( + MeasureSpec.makeMeasureSpec(width, MeasureSpec.EXACTLY), + MeasureSpec.makeMeasureSpec(height, MeasureSpec.EXACTLY) + ) + layout(left, top, right, bottom) + } + } + private var mParentScreenFragment: ScreenFragment? = null + + override fun onLayout(changed: Boolean, l: Int, t: Int, r: Int, b: Int) { + var i = 0 + val size = childCount + while (i < size) { + getChildAt(i).layout(0, 0, width, height) + i++ + } + } + + override fun removeView(view: View) { + // The below block is a workaround for an issue with keyboard handling within fragments. Despite + // Android handles input focus on the fragments that leave the screen, the keyboard stays open + // in a number of cases. The issue can be best reproduced on Android 5 devices, before some + // changes in Android's InputMethodManager have been introduced (specifically around dismissing + // the keyboard in onDetachedFromWindow). However, we also noticed the keyboard issue happen + // intermittently on recent versions of Android as well. The issue hasn't been previously + // noticed as in React Native <= 0.61 there was a logic that'd trigger keyboard dismiss upon a + // blur event (the blur even gets dispatched properly, the keyboard just stays open despite + // that) – note the change in RN core here: + // https://github.com/facebook/react-native/commit/e9b4928311513d3cbbd9d875827694eab6cfa932 + // The workaround is to force-hide keyboard when the screen that has focus is dismissed (we + // detect that in removeView as super.removeView causes the input view to un focus while keeping + // the keyboard open). + if (view === focusedChild) { + (context.getSystemService(Context.INPUT_METHOD_SERVICE) as InputMethodManager) + .hideSoftInputFromWindow(windowToken, InputMethodManager.HIDE_NOT_ALWAYS) + } + super.removeView(view) + } + + override fun requestLayout() { + super.requestLayout() + @Suppress("SENSELESS_COMPARISON") // mLayoutCallback can be null here since this method can be called in init + if (!mLayoutEnqueued && mLayoutCallback != null) { + mLayoutEnqueued = true + // we use NATIVE_ANIMATED_MODULE choreographer queue because it allows us to catch the current + // looper loop instead of enqueueing the update in the next loop causing a one frame delay. + ReactChoreographer.getInstance() + .postFrameCallback( + ReactChoreographer.CallbackType.NATIVE_ANIMATED_MODULE, mLayoutCallback + ) + } + } + + val isNested: Boolean + get() = mParentScreenFragment != null + + fun notifyChildUpdate() { + performUpdatesNow() + } + + protected open fun adapt(screen: Screen): T { + @Suppress("UNCHECKED_CAST") + return ScreenFragment(screen) as T + } + + fun addScreen(screen: Screen, index: Int) { + val fragment = adapt(screen) + screen.fragment = fragment + mScreenFragments.add(index, fragment) + screen.container = this + onScreenChanged() + } + + open fun removeScreenAt(index: Int) { + mScreenFragments[index].screen.container = null + mScreenFragments.removeAt(index) + onScreenChanged() + } + + open fun removeAllScreens() { + for (screenFragment in mScreenFragments) { + screenFragment.screen.container = null + } + mScreenFragments.clear() + onScreenChanged() + } + + val screenCount: Int + get() = mScreenFragments.size + + fun getScreenAt(index: Int): Screen = mScreenFragments[index].screen + + open val topScreen: Screen? + get() = mScreenFragments.firstOrNull { getActivityState(it) === ActivityState.ON_TOP }?.screen + + private fun setFragmentManager(fm: FragmentManager) { + mFragmentManager = fm + performUpdatesNow() + } + + private fun findFragmentManagerForReactRootView(rootView: ReactRootView): FragmentManager { + var context = rootView.context + + // ReactRootView is expected to be initialized with the main React Activity as a context but + // in case of Expo the activity is wrapped in ContextWrapper and we need to unwrap it + while (context !is FragmentActivity && context is ContextWrapper) { + context = context.baseContext + } + + check(context is FragmentActivity) { + "In order to use RNScreens components your app's activity need to extend ReactActivity" + } + + // In case React Native is loaded on a Fragment (not directly in activity) we need to find + // fragment manager whose fragment's view is ReactRootView. As of now, we detect such case by + // checking whether any fragments are attached to activity which hosts ReactRootView. + // See: https://github.com/software-mansion/react-native-screens/issues/1506 on why the cases + // must be treated separately. + return if (context.supportFragmentManager.fragments.isEmpty()) { + // We are in standard React Native application w/o custom native navigation based on fragments. + context.supportFragmentManager + } else { + // We are in some custom setup & we want to use the closest fragment manager in hierarchy. + // `findFragment` method throws IllegalStateException when it fails to resolve appropriate + // fragment. It might happen when e.g. React Native is loaded directly in Activity + // but some custom fragments are still used. Such use case seems highly unlikely + // so, as for now we fallback to activity's FragmentManager in hope for the best. + try { + FragmentManager.findFragment(rootView).childFragmentManager + } catch (ex: IllegalStateException) { + context.supportFragmentManager + } + } + } + + private fun setupFragmentManager() { + var parent: ViewParent = this + // We traverse view hierarchy up until we find screen parent or a root view + while (!(parent is ReactRootView || parent is Screen) && + parent.parent != null + ) { + parent = parent.parent + } + // If parent is of type Screen it means we are inside a nested fragment structure. + // Otherwise we expect to connect directly with root view and get root fragment manager + if (parent is Screen) { + checkNotNull( + parent.fragment?.let { screenFragment -> + mParentScreenFragment = screenFragment + screenFragment.registerChildScreenContainer(this) + setFragmentManager(screenFragment.childFragmentManager) + } + ) { "Parent Screen does not have its Fragment attached" } + } else { + // we expect top level view to be of type ReactRootView, this isn't really necessary but in + // order to find root view we test if parent is null. This could potentially happen also when + // the view is detached from the hierarchy and that test would not correctly indicate the root + // view. So in order to make sure we indeed reached the root we test if it is of a correct type. + // This allows us to provide a more descriptive error message for the aforementioned case. + check(parent is ReactRootView) { "ScreenContainer is not attached under ReactRootView" } + setFragmentManager(findFragmentManagerForReactRootView(parent)) + } + } + + protected fun createTransaction(): FragmentTransaction { + return requireNotNull(mFragmentManager) { "mFragmentManager is null when creating transaction" } + .beginTransaction() + .setReorderingAllowed(true) + } + + private fun attachScreen(transaction: FragmentTransaction, screenFragment: ScreenFragment) { + transaction.add(id, screenFragment) + } + + private fun detachScreen(transaction: FragmentTransaction, screenFragment: ScreenFragment) { + transaction.remove(screenFragment) + } + + private fun getActivityState(screenFragment: ScreenFragment): ActivityState? = + screenFragment.screen.activityState + + open fun hasScreen(screenFragment: ScreenFragment?): Boolean = + mScreenFragments.contains(screenFragment) + + override fun onAttachedToWindow() { + super.onAttachedToWindow() + mIsAttached = true + setupFragmentManager() + } + + /** Removes fragments from fragment manager that are attached to this container */ + private fun removeMyFragments(fragmentManager: FragmentManager) { + val transaction = fragmentManager.beginTransaction() + var hasFragments = false + for (fragment in fragmentManager.fragments) { + if (fragment is ScreenFragment && fragment.screen.container === this) { + transaction.remove(fragment) + hasFragments = true + } + } + + if (hasFragments) { + transaction.commitNowAllowingStateLoss() + } + } + + override fun onDetachedFromWindow() { + // if there are pending transactions and this view is about to get detached we need to perform + // them here as otherwise fragment manager will crash because it won't be able to find container + // view. We also need to make sure all the fragments attached to the given container are removed + // from fragment manager as in some cases fragment manager may be reused and in such case it'd + // attempt to reattach previously registered fragments that are not removed + mFragmentManager?.let { + if (!it.isDestroyed) { + removeMyFragments(it) + it.executePendingTransactions() + } + } + + mParentScreenFragment?.unregisterChildScreenContainer(this) + mParentScreenFragment = null + + super.onDetachedFromWindow() + mIsAttached = false + // When fragment container view is detached we force all its children to be removed. + // It is because children screens are controlled by their fragments, which can often have a + // delayed lifecycle (due to transitions). As a result due to ongoing transitions the fragment + // may choose not to remove the view despite the parent container being completely detached + // from the view hierarchy until the transition is over. In such a case when the container gets + // re-attached while the transition is ongoing, the child view would still be there and we'd + // attempt to re-attach it to with a misconfigured fragment. This would result in a crash. To + // avoid it we clear all the children here as we attach all the child fragments when the + // container is reattached anyways. We don't use `removeAllViews` since it does not check if the + // children are not already detached, which may lead to calling `onDetachedFromWindow` on them + // twice. + // We also get the size earlier, because we will be removing child views in `for` loop. + for (i in childCount - 1 downTo 0) { + removeViewAt(i) + } + } + + override fun onMeasure(widthMeasureSpec: Int, heightMeasureSpec: Int) { + super.onMeasure(widthMeasureSpec, heightMeasureSpec) + for (i in 0 until childCount) { + getChildAt(i).measure(widthMeasureSpec, heightMeasureSpec) + } + } + + private fun onScreenChanged() { + // we perform update in `onBeforeLayout` of `ScreensShadowNode` by adding an UIBlock + // which is called after updating children of the ScreenContainer. + // We do it there because `onUpdate` logic requires all changes of children to be already + // made in order to provide proper animation for fragment transition for ScreenStack + // and this in turn makes nested ScreenContainers detach too early and disappear + // before transition if also not dispatched after children updates. + // The exception to this rule is `updateImmediately` which is triggered by actions + // not connected to React view hierarchy changes, but rather internal events + mNeedUpdate = true + (context as? ReactContext)?.runOnUiQueueThread { + // We schedule the update here because LayoutAnimations of `react-native-reanimated` + // sometimes attach/detach screens after the layout block of `ScreensShadowNode` has + // already run, and we want to update the container then too. In the other cases, + // this code will do nothing since it will run after the UIBlock when `mNeedUpdate` + // will already be false. + performUpdates() + } + } + + protected fun performUpdatesNow() { + // we want to update immediately when the fragment manager is set or native back button + // dismiss is dispatched or Screen's activityState changes since it is not connected to React + // view hierarchy changes and will not trigger `onBeforeLayout` method of `ScreensShadowNode` + mNeedUpdate = true + performUpdates() + } + + fun performUpdates() { + if (!mNeedUpdate || !mIsAttached || mFragmentManager == null || mFragmentManager?.isDestroyed == true) { + return + } + mNeedUpdate = false + onUpdate() + notifyContainerUpdate() + } + + open fun onUpdate() { + createTransaction().let { + // detach screens that are no longer active + val orphaned: MutableSet = HashSet( + requireNotNull(mFragmentManager) { + "mFragmentManager is null when performing update in ScreenContainer" + }.fragments + ) + for (screenFragment in mScreenFragments) { + if (getActivityState(screenFragment) === ActivityState.INACTIVE && + screenFragment.isAdded + ) { + detachScreen(it, screenFragment) + } + orphaned.remove(screenFragment) + } + if (orphaned.isNotEmpty()) { + val orphanedAry = orphaned.toTypedArray() + for (fragment in orphanedAry) { + if (fragment is ScreenFragment) { + if (fragment.screen.container == null) { + detachScreen(it, fragment) + } + } + } + } + + // if there is an "onTop" screen it means the transition has ended + val transitioning = topScreen == null + + // attach newly activated screens + var addedBefore = false + val pendingFront: ArrayList = ArrayList() + + for (screenFragment in mScreenFragments) { + val activityState = getActivityState(screenFragment) + if (activityState !== ActivityState.INACTIVE && !screenFragment.isAdded) { + addedBefore = true + attachScreen(it, screenFragment) + } else if (activityState !== ActivityState.INACTIVE && addedBefore) { + // we detach the screen and then reattach it later to make it appear on front + detachScreen(it, screenFragment) + pendingFront.add(screenFragment) + } + screenFragment.screen.setTransitioning(transitioning) + } + + for (screenFragment in pendingFront) { + attachScreen(it, screenFragment) + } + + it.commitNowAllowingStateLoss() + } + } + + protected open fun notifyContainerUpdate() { + topScreen?.fragment?.onContainerUpdate() + } +} diff --git a/android/vendored/unversioned/react-native-screens/android/src/main/java/com/swmansion/rnscreens/ScreenContainerViewManager.kt b/android/vendored/unversioned/react-native-screens/android/src/main/java/com/swmansion/rnscreens/ScreenContainerViewManager.kt new file mode 100644 index 0000000000000..4cf0a1443e27f --- /dev/null +++ b/android/vendored/unversioned/react-native-screens/android/src/main/java/com/swmansion/rnscreens/ScreenContainerViewManager.kt @@ -0,0 +1,40 @@ +package com.swmansion.rnscreens + +import android.view.View +import com.facebook.react.bridge.ReactApplicationContext +import com.facebook.react.module.annotations.ReactModule +import com.facebook.react.uimanager.LayoutShadowNode +import com.facebook.react.uimanager.ThemedReactContext +import com.facebook.react.uimanager.ViewGroupManager + +@ReactModule(name = ScreenContainerViewManager.REACT_CLASS) +class ScreenContainerViewManager : ViewGroupManager>() { + override fun getName(): String = REACT_CLASS + + override fun createViewInstance(reactContext: ThemedReactContext): ScreenContainer = ScreenContainer(reactContext) + + override fun addView(parent: ScreenContainer<*>, child: View, index: Int) { + require(child is Screen) { "Attempt attach child that is not of type RNScreens" } + parent.addScreen(child, index) + } + + override fun removeViewAt(parent: ScreenContainer<*>, index: Int) { + parent.removeScreenAt(index) + } + + override fun removeAllViews(parent: ScreenContainer<*>) { + parent.removeAllScreens() + } + + override fun getChildCount(parent: ScreenContainer<*>): Int = parent.screenCount + + override fun getChildAt(parent: ScreenContainer<*>, index: Int): View = parent.getScreenAt(index) + + override fun createShadowNodeInstance(context: ReactApplicationContext): LayoutShadowNode = ScreensShadowNode(context) + + override fun needsCustomLayoutForChildren(): Boolean = true + + companion object { + const val REACT_CLASS = "RNSScreenContainer" + } +} diff --git a/android/vendored/unversioned/react-native-screens/android/src/main/java/com/swmansion/rnscreens/ScreenFragment.kt b/android/vendored/unversioned/react-native-screens/android/src/main/java/com/swmansion/rnscreens/ScreenFragment.kt new file mode 100644 index 0000000000000..f9b1a71bba43f --- /dev/null +++ b/android/vendored/unversioned/react-native-screens/android/src/main/java/com/swmansion/rnscreens/ScreenFragment.kt @@ -0,0 +1,323 @@ +package com.swmansion.rnscreens + +import android.annotation.SuppressLint +import android.app.Activity +import android.content.Context +import android.os.Bundle +import android.view.LayoutInflater +import android.view.View +import android.view.ViewGroup +import android.view.ViewParent +import android.widget.FrameLayout +import androidx.fragment.app.Fragment +import com.facebook.react.bridge.ReactContext +import com.facebook.react.bridge.UiThreadUtil +import com.facebook.react.uimanager.UIManagerHelper +import com.facebook.react.uimanager.events.Event +import com.facebook.react.uimanager.events.EventDispatcher +import com.swmansion.rnscreens.events.HeaderBackButtonClickedEvent +import com.swmansion.rnscreens.events.ScreenAppearEvent +import com.swmansion.rnscreens.events.ScreenDisappearEvent +import com.swmansion.rnscreens.events.ScreenDismissedEvent +import com.swmansion.rnscreens.events.ScreenTransitionProgressEvent +import com.swmansion.rnscreens.events.ScreenWillAppearEvent +import com.swmansion.rnscreens.events.ScreenWillDisappearEvent +import kotlin.math.max +import kotlin.math.min + +open class ScreenFragment : Fragment { + enum class ScreenLifecycleEvent { + Appear, WillAppear, Disappear, WillDisappear + } + + // if we call empty constructor, there is no screen to be assigned so not sure why it is suggested + @Suppress("JoinDeclarationAndAssignment") + lateinit var screen: Screen + private val mChildScreenContainers: MutableList> = ArrayList() + private var shouldUpdateOnResume = false + // if we don't set it, it will be 0.0f at the beginning so the progress will not be sent + // due to progress value being already 0.0f + private var mProgress = -1f + + // those 2 vars are needed since sometimes the events would be dispatched twice in child containers + // (should only happen if parent has `NONE` animation) and we don't need too complicated logic. + // We just check if, after the event was dispatched, its "counter-event" has been also dispatched before sending the same event again. + // We do it for 'willAppear' -> 'willDisappear' and 'appear' -> 'disappear' + private var canDispatchWillAppear = true + private var canDispatchAppear = true + + // we want to know if we are currently transitioning in order not to fire lifecycle events + // in nested fragments. See more explanation in dispatchViewAnimationEvent + private var isTransitioning = false + + constructor() { + throw IllegalStateException( + "Screen fragments should never be restored. Follow instructions from https://github.com/software-mansion/react-native-screens/issues/17#issuecomment-424704067 to properly configure your main activity." + ) + } + + @SuppressLint("ValidFragment") + constructor(screenView: Screen) : super() { + screen = screenView + } + + override fun onResume() { + super.onResume() + if (shouldUpdateOnResume) { + shouldUpdateOnResume = false + ScreenWindowTraits.trySetWindowTraits(screen, tryGetActivity(), tryGetContext()) + } + } + + override fun onCreateView( + inflater: LayoutInflater, + container: ViewGroup?, + savedInstanceState: Bundle? + ): View? { + screen.layoutParams = FrameLayout.LayoutParams( + ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT + ) + val wrapper = context?.let { ScreensFrameLayout(it) }?.apply { + addView(recycleView(screen)) + } + return wrapper + } + + private class ScreensFrameLayout( + context: Context, + ) : FrameLayout(context) { + /** + * This method implements a workaround for RN's autoFocus functionality. Because of the way + * autoFocus is implemented it dismisses soft keyboard in fragment transition + * due to change of visibility of the view at the start of the transition. Here we override the + * call to `clearFocus` when the visibility of view is `INVISIBLE` since `clearFocus` triggers the + * hiding of the keyboard in `ReactEditText.java`. + */ + override fun clearFocus() { + if (visibility != INVISIBLE) { + super.clearFocus() + } + } + } + + open fun onContainerUpdate() { + updateWindowTraits() + } + + private fun updateWindowTraits() { + val activity: Activity? = activity + if (activity == null) { + shouldUpdateOnResume = true + return + } + ScreenWindowTraits.trySetWindowTraits(screen, activity, tryGetContext()) + } + + fun tryGetActivity(): Activity? { + activity?.let { return it } + val context = screen.context + if (context is ReactContext && context.currentActivity != null) { + return context.currentActivity + } + var parent: ViewParent? = screen.container + while (parent != null) { + if (parent is Screen) { + val fragment = parent.fragment + fragment?.activity?.let { return it } + } + parent = parent.parent + } + return null + } + + fun tryGetContext(): ReactContext? { + if (context is ReactContext) { + return context as ReactContext + } + if (screen.context is ReactContext) { + return screen.context as ReactContext + } + var parent: ViewParent? = screen.container + while (parent != null) { + if (parent is Screen) { + if (parent.context is ReactContext) { + return parent.context as ReactContext + } + } + parent = parent.parent + } + return null + } + + val childScreenContainers: List> + get() = mChildScreenContainers + + private fun canDispatchEvent(event: ScreenLifecycleEvent): Boolean = when (event) { + ScreenLifecycleEvent.WillAppear -> canDispatchWillAppear + ScreenLifecycleEvent.Appear -> canDispatchAppear + ScreenLifecycleEvent.WillDisappear -> !canDispatchWillAppear + ScreenLifecycleEvent.Disappear -> !canDispatchAppear + } + + private fun setLastEventDispatched(event: ScreenLifecycleEvent) { + when (event) { + ScreenLifecycleEvent.WillAppear -> canDispatchWillAppear = false + ScreenLifecycleEvent.Appear -> canDispatchAppear = false + ScreenLifecycleEvent.WillDisappear -> canDispatchWillAppear = true + ScreenLifecycleEvent.Disappear -> canDispatchAppear = true + } + } + + private fun dispatchOnWillAppear() { + dispatchEvent(ScreenLifecycleEvent.WillAppear, this) + dispatchTransitionProgress(0.0f, false) + } + + private fun dispatchOnAppear() { + dispatchEvent(ScreenLifecycleEvent.Appear, this) + dispatchTransitionProgress(1.0f, false) + } + + private fun dispatchOnWillDisappear() { + dispatchEvent(ScreenLifecycleEvent.WillDisappear, this) + dispatchTransitionProgress(0.0f, true) + } + + private fun dispatchOnDisappear() { + dispatchEvent(ScreenLifecycleEvent.Disappear, this) + dispatchTransitionProgress(1.0f, true) + } + + private fun dispatchEvent(event: ScreenLifecycleEvent, fragment: ScreenFragment) { + if (fragment is ScreenStackFragment && fragment.canDispatchEvent(event)) { + fragment.screen.let { + fragment.setLastEventDispatched(event) + val lifecycleEvent: Event<*> = when (event) { + ScreenLifecycleEvent.WillAppear -> ScreenWillAppearEvent(it.id) + ScreenLifecycleEvent.Appear -> ScreenAppearEvent(it.id) + ScreenLifecycleEvent.WillDisappear -> ScreenWillDisappearEvent(it.id) + ScreenLifecycleEvent.Disappear -> ScreenDisappearEvent(it.id) + } + val screenContext = screen.context as ReactContext + val eventDispatcher: EventDispatcher? = + UIManagerHelper.getEventDispatcherForReactTag(screenContext, screen.id) + eventDispatcher?.dispatchEvent(lifecycleEvent) + fragment.dispatchEventInChildContainers(event) + } + } + } + + private fun dispatchEventInChildContainers(event: ScreenLifecycleEvent) { + mChildScreenContainers.filter { it.screenCount > 0 }.forEach { + it.topScreen?.fragment?.let { fragment -> dispatchEvent(event, fragment) } + } + } + + fun dispatchHeaderBackButtonClickedEvent() { + val screenContext = screen.context as ReactContext + UIManagerHelper + .getEventDispatcherForReactTag(screenContext, screen.id) + ?.dispatchEvent(HeaderBackButtonClickedEvent(screen.id)) + } + + fun dispatchTransitionProgress(alpha: Float, closing: Boolean) { + if (this is ScreenStackFragment) { + if (mProgress != alpha) { + mProgress = max(0.0f, min(1.0f, alpha)) + /* We want value of 0 and 1 to be always dispatched so we base coalescing key on the progress: + - progress is 0 -> key 1 + - progress is 1 -> key 2 + - progress is between 0 and 1 -> key 3 + */ + val coalescingKey = (if (mProgress == 0.0f) 1 else if (mProgress == 1.0f) 2 else 3).toShort() + val container: ScreenContainer<*>? = screen.container + val goingForward = if (container is ScreenStack) container.goingForward else false + val screenContext = screen.context as ReactContext + UIManagerHelper + .getEventDispatcherForReactTag(screenContext, screen.id) + ?.dispatchEvent( + ScreenTransitionProgressEvent( + screen.id, mProgress, closing, goingForward, coalescingKey + ) + ) + } + } + } + + fun registerChildScreenContainer(screenContainer: ScreenContainer<*>) { + mChildScreenContainers.add(screenContainer) + } + + fun unregisterChildScreenContainer(screenContainer: ScreenContainer<*>) { + mChildScreenContainers.remove(screenContainer) + } + + fun onViewAnimationStart() { + dispatchViewAnimationEvent(false) + } + + open fun onViewAnimationEnd() { + dispatchViewAnimationEvent(true) + } + + private fun dispatchViewAnimationEvent(animationEnd: Boolean) { + isTransitioning = !animationEnd + // if parent fragment is transitioning, we do not want the events dispatched from the child, + // since we subscribe to parent's animation start/end and dispatch events in child from there + // check for `isTransitioning` should be enough since the child's animation should take only + // 20ms due to always being `StackAnimation.NONE` when nested stack being pushed + val parent = parentFragment + if (parent == null || (parent is ScreenFragment && !parent.isTransitioning)) { + // onViewAnimationStart/End is triggered from View#onAnimationStart/End method of the fragment's root + // view. We override an appropriate method of the StackFragment's + // root view in order to achieve this. + if (isResumed) { + // Android dispatches the animation start event for the fragment that is being added first + // however we want the one being dismissed first to match iOS. It also makes more sense from + // a navigation point of view to have the disappear event first. + // Since there are no explicit relationships between the fragment being added / removed the + // practical way to fix this is delaying dispatching the appear events at the end of the + // frame. + UiThreadUtil.runOnUiThread { + if (animationEnd) dispatchOnAppear() else dispatchOnWillAppear() + } + } else { + if (animationEnd) dispatchOnDisappear() else dispatchOnWillDisappear() + } + } + } + + override fun onDestroy() { + super.onDestroy() + val container = screen.container + if (container == null || !container.hasScreen(this)) { + // we only send dismissed even when the screen has been removed from its container + val screenContext = screen.context + if (screenContext is ReactContext) { + UIManagerHelper + .getEventDispatcherForReactTag(screenContext, screen.id) + ?.dispatchEvent(ScreenDismissedEvent(screen.id)) + } + } + mChildScreenContainers.clear() + } + + companion object { + @JvmStatic + protected fun recycleView(view: View): View { + // screen fragments reuse view instances instead of creating new ones. In order to reuse a given + // view it needs to be detached from the view hierarchy to allow the fragment to attach it back. + val parent = view.parent + if (parent != null) { + (parent as ViewGroup).endViewTransition(view) + parent.removeView(view) + } + + // view detached from fragment manager get their visibility changed to GONE after their state is + // dumped. Since we don't restore the state but want to reuse the view we need to change + // visibility back to VISIBLE in order for the fragment manager to animate in the view. + view.visibility = View.VISIBLE + return view + } + } +} diff --git a/android/vendored/unversioned/react-native-screens/android/src/main/java/com/swmansion/rnscreens/ScreenStack.kt b/android/vendored/unversioned/react-native-screens/android/src/main/java/com/swmansion/rnscreens/ScreenStack.kt new file mode 100644 index 0000000000000..34aed7959dc62 --- /dev/null +++ b/android/vendored/unversioned/react-native-screens/android/src/main/java/com/swmansion/rnscreens/ScreenStack.kt @@ -0,0 +1,334 @@ +package com.swmansion.rnscreens + +import android.content.Context +import android.graphics.Canvas +import android.view.View +import com.facebook.react.bridge.ReactContext +import com.facebook.react.uimanager.UIManagerHelper +import com.swmansion.rnscreens.Screen.StackAnimation +import com.swmansion.rnscreens.events.StackFinishTransitioningEvent +import java.util.Collections +import kotlin.collections.ArrayList +import kotlin.collections.HashSet + +class ScreenStack(context: Context?) : ScreenContainer(context) { + private val mStack = ArrayList() + private val mDismissed: MutableSet = HashSet() + private val drawingOpPool: MutableList = ArrayList() + private var drawingOps: MutableList = ArrayList() + private var mTopScreen: ScreenStackFragment? = null + private var mRemovalTransitionStarted = false + private var isDetachingCurrentScreen = false + private var reverseLastTwoChildren = false + private var previousChildrenCount = 0 + var goingForward = false + + fun dismiss(screenFragment: ScreenStackFragment) { + mDismissed.add(screenFragment) + performUpdatesNow() + } + + override val topScreen: Screen? + get() = mTopScreen?.screen + + val rootScreen: Screen + get() { + for (i in 0 until screenCount) { + val screen = getScreenAt(i) + if (!mDismissed.contains(screen.fragment)) { + return screen + } + } + throw IllegalStateException("Stack has no root screen set") + } + + override fun adapt(screen: Screen) = ScreenStackFragment(screen) + + override fun startViewTransition(view: View) { + super.startViewTransition(view) + mRemovalTransitionStarted = true + } + + override fun endViewTransition(view: View) { + super.endViewTransition(view) + if (mRemovalTransitionStarted) { + mRemovalTransitionStarted = false + dispatchOnFinishTransitioning() + } + } + + fun onViewAppearTransitionEnd() { + if (!mRemovalTransitionStarted) { + dispatchOnFinishTransitioning() + } + } + + private fun dispatchOnFinishTransitioning() { + UIManagerHelper + .getEventDispatcherForReactTag((context as ReactContext), id) + ?.dispatchEvent(StackFinishTransitioningEvent(id)) + } + + override fun removeScreenAt(index: Int) { + mDismissed.remove(getScreenAt(index).fragment) + super.removeScreenAt(index) + } + + override fun removeAllScreens() { + mDismissed.clear() + super.removeAllScreens() + } + + override fun hasScreen(screenFragment: ScreenFragment?): Boolean = + super.hasScreen(screenFragment) && !mDismissed.contains(screenFragment) + + override fun onUpdate() { + // When going back from a nested stack with a single screen on it, we may hit an edge case + // when all screens are dismissed and no screen is to be displayed on top. We need to gracefully + // handle the case of newTop being NULL, which happens in several places below + var newTop: ScreenStackFragment? = null // newTop is nullable, see the above comment ^ + var visibleBottom: ScreenStackFragment? = null // this is only set if newTop has TRANSPARENT_MODAL presentation mode + isDetachingCurrentScreen = false // we reset it so the previous value is not used by mistake + for (i in mScreenFragments.indices.reversed()) { + val screen = mScreenFragments[i] + if (!mDismissed.contains(screen)) { + if (newTop == null) { + newTop = screen + } else { + visibleBottom = screen + } + if (!isTransparent(screen)) { + break + } + } + } + var shouldUseOpenAnimation = true + var stackAnimation: StackAnimation? = null + if (!mStack.contains(newTop)) { + // if new top screen wasn't on stack we do "open animation" so long it is not the very first + // screen on stack + if (mTopScreen != null && newTop != null) { + // there was some other screen attached before + // if the previous top screen does not exist anymore and the new top was not on the stack + // before, probably replace or reset was called, so we play the "close animation". + // Otherwise it's open animation + val containsTopScreen = mTopScreen?.let { mScreenFragments.contains(it) } == true + val isPushReplace = newTop.screen.replaceAnimation === Screen.ReplaceAnimation.PUSH + shouldUseOpenAnimation = containsTopScreen || isPushReplace + // if the replace animation is `push`, the new top screen provides the animation, otherwise the previous one + stackAnimation = if (shouldUseOpenAnimation) newTop.screen.stackAnimation else mTopScreen?.screen?.stackAnimation + } else if (mTopScreen == null && newTop != null) { + // mTopScreen was not present before so newTop is the first screen added to a stack + // and we don't want the animation when it is entering + stackAnimation = StackAnimation.NONE + goingForward = true + } + } else if (mTopScreen != null && mTopScreen != newTop) { + // otherwise if we are performing top screen change we do "close animation" + shouldUseOpenAnimation = false + stackAnimation = mTopScreen?.screen?.stackAnimation + } + + createTransaction().let { + // animation logic start + if (stackAnimation != null) { + if (shouldUseOpenAnimation) { + when (stackAnimation) { + StackAnimation.DEFAULT -> it.setCustomAnimations(R.anim.rns_default_enter_in, R.anim.rns_default_enter_out) + StackAnimation.NONE -> it.setCustomAnimations(R.anim.rns_no_animation_20, R.anim.rns_no_animation_20) + StackAnimation.FADE -> it.setCustomAnimations(R.anim.rns_fade_in, R.anim.rns_fade_out) + StackAnimation.SLIDE_FROM_RIGHT -> it.setCustomAnimations(R.anim.rns_slide_in_from_right, R.anim.rns_slide_out_to_left) + StackAnimation.SLIDE_FROM_LEFT -> it.setCustomAnimations(R.anim.rns_slide_in_from_left, R.anim.rns_slide_out_to_right) + StackAnimation.SLIDE_FROM_BOTTOM -> it.setCustomAnimations( + R.anim.rns_slide_in_from_bottom, R.anim.rns_no_animation_medium + ) + StackAnimation.FADE_FROM_BOTTOM -> it.setCustomAnimations(R.anim.rns_fade_from_bottom, R.anim.rns_no_animation_350) + } + } else { + when (stackAnimation) { + StackAnimation.DEFAULT -> it.setCustomAnimations(R.anim.rns_default_exit_in, R.anim.rns_default_exit_out) + StackAnimation.NONE -> it.setCustomAnimations(R.anim.rns_no_animation_20, R.anim.rns_no_animation_20) + StackAnimation.FADE -> it.setCustomAnimations(R.anim.rns_fade_in, R.anim.rns_fade_out) + StackAnimation.SLIDE_FROM_RIGHT -> it.setCustomAnimations(R.anim.rns_slide_in_from_left, R.anim.rns_slide_out_to_right) + StackAnimation.SLIDE_FROM_LEFT -> it.setCustomAnimations(R.anim.rns_slide_in_from_right, R.anim.rns_slide_out_to_left) + StackAnimation.SLIDE_FROM_BOTTOM -> it.setCustomAnimations( + R.anim.rns_no_animation_medium, R.anim.rns_slide_out_to_bottom + ) + StackAnimation.FADE_FROM_BOTTOM -> it.setCustomAnimations(R.anim.rns_no_animation_250, R.anim.rns_fade_to_bottom) + } + } + } + + // animation logic end + goingForward = shouldUseOpenAnimation + + if (shouldUseOpenAnimation && + newTop != null && needsDrawReordering(newTop) && + visibleBottom == null + ) { + // When using an open animation in which two screens overlap (eg. fade_from_bottom or + // slide_from_bottom), we want to draw the previous screen under the new one, + // which is apparently not the default option. Android always draws the disappearing view + // on top of the appearing one. We then reverse the order of the views so the new screen + // appears on top of the previous one. You can read more about in the comment + // for the code we use to change that behavior: + // https://github.com/airbnb/native-navigation/blob/9cf50bf9b751b40778f473f3b19fcfe2c4d40599/lib/android/src/main/java/com/airbnb/android/react/navigation/ScreenCoordinatorLayout.java#L18 + isDetachingCurrentScreen = true + } + + // remove all screens previously on stack + for (screen in mStack) { + if (!mScreenFragments.contains(screen) || mDismissed.contains(screen)) { + it.remove(screen) + } + } + for (screen in mScreenFragments) { + // Stop detaching screens when reaching visible bottom. All screens above bottom should be + // visible. + if (screen === visibleBottom) { + break + } + // detach all screens that should not be visible + if (screen !== newTop && !mDismissed.contains(screen)) { + it.remove(screen) + } + } + + // attach screens that just became visible + if (visibleBottom != null && !visibleBottom.isAdded) { + val top = newTop + var beneathVisibleBottom = true + for (screen in mScreenFragments) { + // ignore all screens beneath the visible bottom + if (beneathVisibleBottom) { + beneathVisibleBottom = if (screen === visibleBottom) { + false + } else continue + } + // when first visible screen found, make all screens after that visible + it.add(id, screen).runOnCommit { top?.screen?.bringToFront() } + } + } else if (newTop != null && !newTop.isAdded) { + it.add(id, newTop) + } + mTopScreen = newTop + mStack.clear() + mStack.addAll(mScreenFragments) + + turnOffA11yUnderTransparentScreen(visibleBottom) + + it.commitNowAllowingStateLoss() + } + } + + // only top visible screen should be accessible + private fun turnOffA11yUnderTransparentScreen(visibleBottom: ScreenStackFragment?) { + if (mScreenFragments.size > 1 && visibleBottom != null) { + mTopScreen?.let { + if (isTransparent(it)) { + val screenFragmentsBeneathTop = mScreenFragments.slice(0 until mScreenFragments.size - 1).asReversed() + // go from the top of the stack excluding the top screen + for (screenFragment in screenFragmentsBeneathTop) { + screenFragment.screen.changeAccessibilityMode(IMPORTANT_FOR_ACCESSIBILITY_NO_HIDE_DESCENDANTS) + + // don't change a11y below non-transparent screens + if (screenFragment == visibleBottom) { + break + } + } + } + } + } + + topScreen?.changeAccessibilityMode(IMPORTANT_FOR_ACCESSIBILITY_AUTO) + } + + override fun notifyContainerUpdate() { + mStack.forEach { it.onContainerUpdate() } + } + + // below methods are taken from + // https://github.com/airbnb/native-navigation/blob/9cf50bf9b751b40778f473f3b19fcfe2c4d40599/lib/android/src/main/java/com/airbnb/android/react/navigation/ScreenCoordinatorLayout.java#L43 + // and are used to swap the order of drawing views when navigating forward with the transitions + // that are making transitioning fragments appear one on another. See more info in the comment to + // the linked class. + override fun removeView(view: View) { + // we set this property to reverse the order of drawing views + // when we want to push new fragment on top of the previous one and their animations collide. + // More information in: + // https://github.com/airbnb/native-navigation/blob/9cf50bf9b751b40778f473f3b19fcfe2c4d40599/lib/android/src/main/java/com/airbnb/android/react/navigation/ScreenCoordinatorLayout.java#L17 + if (isDetachingCurrentScreen) { + isDetachingCurrentScreen = false + reverseLastTwoChildren = true + } + super.removeView(view) + } + + private fun drawAndRelease() { + // We make a copy of the drawingOps and use it to dispatch draws in order to be sure + // that we do not modify the original list. There are cases when `op.draw` can call + // `drawChild` which would modify the list through which we are iterating. See more: + // https://github.com/software-mansion/react-native-screens/pull/1406 + val drawingOpsCopy = drawingOps + drawingOps = ArrayList() + for (op in drawingOpsCopy) { + op.draw() + drawingOpPool.add(op) + } + } + + override fun dispatchDraw(canvas: Canvas) { + super.dispatchDraw(canvas) + + // check the view removal is completed (by comparing the previous children count) + if (drawingOps.size < previousChildrenCount) { + reverseLastTwoChildren = false + } + previousChildrenCount = drawingOps.size + if (reverseLastTwoChildren && drawingOps.size >= 2) { + Collections.swap(drawingOps, drawingOps.size - 1, drawingOps.size - 2) + } + drawAndRelease() + } + + override fun drawChild(canvas: Canvas, child: View, drawingTime: Long): Boolean { + drawingOps.add(obtainDrawingOp().set(canvas, child, drawingTime)) + return true + } + + private fun performDraw(op: DrawingOp) { + super.drawChild(op.canvas, op.child, op.drawingTime) + } + + private fun obtainDrawingOp(): DrawingOp = + if (drawingOpPool.isEmpty()) DrawingOp() else drawingOpPool.removeAt(drawingOpPool.size - 1) + + private inner class DrawingOp { + var canvas: Canvas? = null + var child: View? = null + var drawingTime: Long = 0 + + operator fun set(canvas: Canvas?, child: View?, drawingTime: Long): DrawingOp { + this.canvas = canvas + this.child = child + this.drawingTime = drawingTime + return this + } + + fun draw() { + performDraw(this) + canvas = null + child = null + drawingTime = 0 + } + } + + companion object { + private fun isTransparent(fragment: ScreenStackFragment): Boolean = + fragment.screen.stackPresentation === Screen.StackPresentation.TRANSPARENT_MODAL + + private fun needsDrawReordering(fragment: ScreenStackFragment): Boolean = + fragment.screen.stackAnimation === StackAnimation.SLIDE_FROM_BOTTOM || + fragment.screen.stackAnimation === StackAnimation.FADE_FROM_BOTTOM + } +} diff --git a/android/vendored/unversioned/react-native-screens/android/src/main/java/com/swmansion/rnscreens/ScreenStackFragment.kt b/android/vendored/unversioned/react-native-screens/android/src/main/java/com/swmansion/rnscreens/ScreenStackFragment.kt new file mode 100644 index 0000000000000..86a7654805783 --- /dev/null +++ b/android/vendored/unversioned/react-native-screens/android/src/main/java/com/swmansion/rnscreens/ScreenStackFragment.kt @@ -0,0 +1,257 @@ +package com.swmansion.rnscreens + +import android.annotation.SuppressLint +import android.content.Context +import android.graphics.Color +import android.os.Bundle +import android.view.LayoutInflater +import android.view.Menu +import android.view.MenuInflater +import android.view.MenuItem +import android.view.View +import android.view.ViewGroup +import android.view.animation.Animation +import android.view.animation.AnimationSet +import android.view.animation.Transformation +import android.widget.LinearLayout +import androidx.appcompat.widget.Toolbar +import androidx.coordinatorlayout.widget.CoordinatorLayout +import com.facebook.react.uimanager.PixelUtil +import com.google.android.material.appbar.AppBarLayout +import com.google.android.material.appbar.AppBarLayout.ScrollingViewBehavior + +class ScreenStackFragment : ScreenFragment { + private var mAppBarLayout: AppBarLayout? = null + private var mToolbar: Toolbar? = null + private var mShadowHidden = false + private var mIsTranslucent = false + + var searchView: CustomSearchView? = null + var onSearchViewCreate: ((searchView: CustomSearchView) -> Unit)? = null + + @SuppressLint("ValidFragment") + constructor(screenView: Screen) : super(screenView) + + constructor() { + throw IllegalStateException( + "ScreenStack fragments should never be restored. Follow instructions from https://github.com/software-mansion/react-native-screens/issues/17#issuecomment-424704067 to properly configure your main activity." + ) + } + + fun removeToolbar() { + mAppBarLayout?.let { + mToolbar?.let { toolbar -> + if (toolbar.parent === it) { + it.removeView(toolbar) + } + } + } + mToolbar = null + } + + fun setToolbar(toolbar: Toolbar) { + mAppBarLayout?.addView(toolbar) + toolbar.layoutParams = AppBarLayout.LayoutParams( + AppBarLayout.LayoutParams.MATCH_PARENT, AppBarLayout.LayoutParams.WRAP_CONTENT + ).apply { scrollFlags = 0 } + mToolbar = toolbar + } + + fun setToolbarShadowHidden(hidden: Boolean) { + if (mShadowHidden != hidden) { + mAppBarLayout?.targetElevation = if (hidden) 0f else PixelUtil.toPixelFromDIP(4f) + mShadowHidden = hidden + } + } + + fun setToolbarTranslucent(translucent: Boolean) { + if (mIsTranslucent != translucent) { + val params = screen.layoutParams + (params as CoordinatorLayout.LayoutParams).behavior = + if (translucent) null else ScrollingViewBehavior() + mIsTranslucent = translucent + } + } + + override fun onContainerUpdate() { + screen.headerConfig?.onUpdate() + } + + override fun onViewAnimationEnd() { + super.onViewAnimationEnd() + notifyViewAppearTransitionEnd() + } + + private fun notifyViewAppearTransitionEnd() { + val screenStack = view?.parent + if (screenStack is ScreenStack) { + screenStack.onViewAppearTransitionEnd() + } + } + + override fun onCreateView( + inflater: LayoutInflater, + container: ViewGroup?, + savedInstanceState: Bundle? + ): View? { + val view: ScreensCoordinatorLayout? = + context?.let { ScreensCoordinatorLayout(it, this) } + + screen.layoutParams = CoordinatorLayout.LayoutParams( + LinearLayout.LayoutParams.MATCH_PARENT, LinearLayout.LayoutParams.MATCH_PARENT + ).apply { behavior = if (mIsTranslucent) null else ScrollingViewBehavior() } + + view?.addView(recycleView(screen)) + + mAppBarLayout = context?.let { AppBarLayout(it) }?.apply { + // By default AppBarLayout will have a background color set but since we cover the whole layout + // with toolbar (that can be semi-transparent) the bar layout background color does not pay a + // role. On top of that it breaks screens animations when alfa offscreen compositing is off + // (which is the default) + setBackgroundColor(Color.TRANSPARENT) + layoutParams = AppBarLayout.LayoutParams( + AppBarLayout.LayoutParams.MATCH_PARENT, AppBarLayout.LayoutParams.WRAP_CONTENT + ) + } + + view?.addView(mAppBarLayout) + if (mShadowHidden) { + mAppBarLayout?.targetElevation = 0f + } + mToolbar?.let { mAppBarLayout?.addView(recycleView(it)) } + setHasOptionsMenu(true) + return view + } + + override fun onPrepareOptionsMenu(menu: Menu) { + updateToolbarMenu(menu) + return super.onPrepareOptionsMenu(menu) + } + + override fun onCreateOptionsMenu(menu: Menu, inflater: MenuInflater) { + updateToolbarMenu(menu) + return super.onCreateOptionsMenu(menu, inflater) + } + + private fun shouldShowSearchBar(): Boolean { + val config = screen.headerConfig + val numberOfSubViews = config?.configSubviewsCount ?: 0 + if (config != null && numberOfSubViews > 0) { + for (i in 0 until numberOfSubViews) { + val subView = config.getConfigSubview(i) + if (subView.type == ScreenStackHeaderSubview.Type.SEARCH_BAR) { + return true + } + } + } + return false + } + + private fun updateToolbarMenu(menu: Menu) { + menu.clear() + if (shouldShowSearchBar()) { + val currentContext = context + if (searchView == null && currentContext != null) { + val newSearchView = CustomSearchView(currentContext, this) + searchView = newSearchView + onSearchViewCreate?.invoke(newSearchView) + } + menu.add("").apply { + setShowAsAction(MenuItem.SHOW_AS_ACTION_ALWAYS) + actionView = searchView + } + } + } + + fun canNavigateBack(): Boolean { + val container: ScreenContainer<*>? = screen.container + check(container is ScreenStack) { "ScreenStackFragment added into a non-stack container" } + return if (container.rootScreen == screen) { + // this screen is the root of the container, if it is nested we can check parent container + // if it is also a root or not + val parentFragment = parentFragment + if (parentFragment is ScreenStackFragment) { + parentFragment.canNavigateBack() + } else { + false + } + } else { + true + } + } + + fun dismiss() { + val container: ScreenContainer<*>? = screen.container + check(container is ScreenStack) { "ScreenStackFragment added into a non-stack container" } + container.dismiss(this) + } + + private class ScreensCoordinatorLayout( + context: Context, + private val mFragment: ScreenFragment + ) : CoordinatorLayout(context) { + private val mAnimationListener: Animation.AnimationListener = + object : Animation.AnimationListener { + override fun onAnimationStart(animation: Animation) { + mFragment.onViewAnimationStart() + } + + override fun onAnimationEnd(animation: Animation) { + mFragment.onViewAnimationEnd() + } + + override fun onAnimationRepeat(animation: Animation) {} + } + + override fun startAnimation(animation: Animation) { + // For some reason View##onAnimationEnd doesn't get called for + // exit transitions so we explicitly attach animation listener. + // We also have some animations that are an AnimationSet, so we don't wrap them + // in another set since it causes some visual glitches when going forward. + // We also set the listener only when going forward, since when going back, + // there is already a listener for dismiss action added, which would be overridden + // and also this is not necessary when going back since the lifecycle methods + // are correctly dispatched then. + // We also add fakeAnimation to the set of animations, which sends the progress of animation + val fakeAnimation = ScreensAnimation(mFragment).apply { duration = animation.duration } + + if (animation is AnimationSet && !mFragment.isRemoving) { + animation.apply { + addAnimation(fakeAnimation) + setAnimationListener(mAnimationListener) + }.also { + super.startAnimation(it) + } + } else { + AnimationSet(true).apply { + addAnimation(animation) + addAnimation(fakeAnimation) + setAnimationListener(mAnimationListener) + }.also { + super.startAnimation(it) + } + } + } + + /** + * This method implements a workaround for RN's autoFocus functionality. Because of the way + * autoFocus is implemented it dismisses soft keyboard in fragment transition + * due to change of visibility of the view at the start of the transition. Here we override the + * call to `clearFocus` when the visibility of view is `INVISIBLE` since `clearFocus` triggers the + * hiding of the keyboard in `ReactEditText.java`. + */ + override fun clearFocus() { + if (visibility != INVISIBLE) { + super.clearFocus() + } + } + } + + private class ScreensAnimation(private val mFragment: ScreenFragment) : Animation() { + override fun applyTransformation(interpolatedTime: Float, t: Transformation) { + super.applyTransformation(interpolatedTime, t) + // interpolated time should be the progress of the current transition + mFragment.dispatchTransitionProgress(interpolatedTime, !mFragment.isResumed) + } + } +} diff --git a/android/vendored/unversioned/react-native-screens/android/src/main/java/com/swmansion/rnscreens/ScreenStackHeaderConfig.kt b/android/vendored/unversioned/react-native-screens/android/src/main/java/com/swmansion/rnscreens/ScreenStackHeaderConfig.kt new file mode 100644 index 0000000000000..aa4c27c0c4dca --- /dev/null +++ b/android/vendored/unversioned/react-native-screens/android/src/main/java/com/swmansion/rnscreens/ScreenStackHeaderConfig.kt @@ -0,0 +1,398 @@ +package com.swmansion.rnscreens + +import android.content.Context +import android.graphics.PorterDuff +import android.os.Build +import android.text.TextUtils +import android.util.TypedValue +import android.view.Gravity +import android.view.View.OnClickListener +import android.view.ViewGroup +import android.widget.ImageView +import android.widget.TextView +import androidx.appcompat.app.AppCompatActivity +import androidx.appcompat.widget.Toolbar +import androidx.fragment.app.Fragment +import com.facebook.react.ReactApplication +import com.facebook.react.bridge.JSApplicationIllegalArgumentException +import com.facebook.react.bridge.ReactContext +import com.facebook.react.bridge.WritableMap +import com.facebook.react.uimanager.events.RCTEventEmitter +import com.facebook.react.views.text.ReactTypefaceUtils + +class ScreenStackHeaderConfig(context: Context) : ViewGroup(context) { + private val mConfigSubviews = ArrayList(3) + val toolbar: CustomToolbar + private var headerTopInset: Int? = null + private var mTitle: String? = null + private var mTitleColor = 0 + private var mTitleFontFamily: String? = null + private var mDirection: String? = null + private var mTitleFontSize = 0f + private var mTitleFontWeight = 0 + private var mBackgroundColor: Int? = null + private var mIsHidden = false + private var mIsBackButtonHidden = false + private var mIsShadowHidden = false + private var mDestroyed = false + private var mBackButtonInCustomView = false + private var mIsTopInsetEnabled = true + private var mIsTranslucent = false + private var mTintColor = 0 + private var mIsAttachedToWindow = false + private val mDefaultStartInset: Int + private val mDefaultStartInsetWithNavigation: Int + private val mBackClickListener = OnClickListener { + screenFragment?.let { + val stack = screenStack + if (stack != null && stack.rootScreen == it.screen) { + val parentFragment = it.parentFragment + if (parentFragment is ScreenStackFragment) { + if (parentFragment.screen.nativeBackButtonDismissalEnabled) { + parentFragment.dismiss() + } else { + parentFragment.dispatchHeaderBackButtonClickedEvent() + } + } + } else { + if (it.screen.nativeBackButtonDismissalEnabled) { + it.dismiss() + } else { + it.dispatchHeaderBackButtonClickedEvent() + } + } + } + } + + private fun sendEvent(eventName: String, eventContent: WritableMap?) { + (context as ReactContext).getJSModule(RCTEventEmitter::class.java) + ?.receiveEvent(id, eventName, eventContent) + } + + override fun onLayout(changed: Boolean, l: Int, t: Int, r: Int, b: Int) { + // no-op + } + + fun destroy() { + mDestroyed = true + } + + override fun onAttachedToWindow() { + super.onAttachedToWindow() + mIsAttachedToWindow = true + sendEvent("onAttached", null) + // we want to save the top inset before the status bar can be hidden, which would resolve in + // inset being 0 + if (headerTopInset == null) { + headerTopInset = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) + rootWindowInsets.systemWindowInsetTop + else + // Hacky fallback for old android. Before Marshmallow, the status bar height was always 25 + (25 * resources.displayMetrics.density).toInt() + } + onUpdate() + } + + override fun onDetachedFromWindow() { + super.onDetachedFromWindow() + mIsAttachedToWindow = false + sendEvent("onDetached", null) + } + + private val screen: Screen? + get() = parent as? Screen + private val screenStack: ScreenStack? + get() = screen?.container as? ScreenStack + val screenFragment: ScreenStackFragment? + get() { + val screen = parent + if (screen is Screen) { + val fragment: Fragment? = screen.fragment + if (fragment is ScreenStackFragment) { + return fragment + } + } + return null + } + + fun onUpdate() { + val stack = screenStack + val isTop = stack == null || stack.topScreen == parent + + if (!mIsAttachedToWindow || !isTop || mDestroyed) { + return + } + + val activity = screenFragment?.activity as AppCompatActivity? ?: return + if (mDirection != null) { + if (mDirection == "rtl") { + toolbar.layoutDirection = LAYOUT_DIRECTION_RTL + } else if (mDirection == "ltr") { + toolbar.layoutDirection = LAYOUT_DIRECTION_LTR + } + } + + // orientation and status bar management + screen?.let { + // we set the traits here too, not only when the prop for Screen is passed + // because sometimes we don't have the Fragment and Activity available then yet, e.g. on the + // first setting of props. Similar thing is done for Screens of ScreenContainers, but in + // `onContainerUpdate` of their Fragment + val reactContext = if (context is ReactContext) { + context as ReactContext + } else { + it.fragment?.tryGetContext() + } + ScreenWindowTraits.trySetWindowTraits(it, activity, reactContext) + } + + if (mIsHidden) { + if (toolbar.parent != null) { + screenFragment?.removeToolbar() + } + return + } + + if (toolbar.parent == null) { + screenFragment?.setToolbar(toolbar) + } + + if (mIsTopInsetEnabled) { + headerTopInset.let { + toolbar.setPadding(0, it ?: 0, 0, 0) + } + } else { + if (toolbar.paddingTop > 0) { + toolbar.setPadding(0, 0, 0, 0) + } + } + + activity.setSupportActionBar(toolbar) + // non-null toolbar is set in the line above and it is used here + val actionBar = requireNotNull(activity.supportActionBar) + + // Reset toolbar insets. By default we set symmetric inset for start and end to match iOS + // implementation where both right and left icons are offset from the edge by default. We also + // reset startWithNavigation inset which corresponds to the distance between navigation icon and + // title. If title isn't set we clear that value few lines below to give more space to custom + // center-mounted views. + toolbar.contentInsetStartWithNavigation = mDefaultStartInsetWithNavigation + toolbar.setContentInsetsRelative(mDefaultStartInset, mDefaultStartInset) + + // hide back button + actionBar.setDisplayHomeAsUpEnabled( + screenFragment?.canNavigateBack() == true && !mIsBackButtonHidden + ) + + // when setSupportActionBar is called a toolbar wrapper gets initialized that overwrites + // navigation click listener. The default behavior set in the wrapper is to call into + // menu options handlers, but we prefer the back handling logic to stay here instead. + toolbar.setNavigationOnClickListener(mBackClickListener) + + // shadow + screenFragment?.setToolbarShadowHidden(mIsShadowHidden) + + // translucent + screenFragment?.setToolbarTranslucent(mIsTranslucent) + + // title + actionBar.title = mTitle + if (TextUtils.isEmpty(mTitle)) { + // if title is empty we set start navigation inset to 0 to give more space to custom rendered + // views. When it is set to default it'd take up additional distance from the back button + // which would impact the position of custom header views rendered at the center. + toolbar.contentInsetStartWithNavigation = 0 + } + + val titleTextView = titleTextView + if (mTitleColor != 0) { + toolbar.setTitleTextColor(mTitleColor) + } + + if (titleTextView != null) { + if (mTitleFontFamily != null || mTitleFontWeight > 0) { + val titleTypeface = ReactTypefaceUtils.applyStyles( + null, 0, mTitleFontWeight, mTitleFontFamily, context.assets + ) + titleTextView.typeface = titleTypeface + } + if (mTitleFontSize > 0) { + titleTextView.textSize = mTitleFontSize + } + } + + // background + mBackgroundColor?.let { toolbar.setBackgroundColor(it) } + + // color + if (mTintColor != 0) { + toolbar.navigationIcon?.setColorFilter(mTintColor, PorterDuff.Mode.SRC_ATOP) + } + + // subviews + for (i in toolbar.childCount - 1 downTo 0) { + if (toolbar.getChildAt(i) is ScreenStackHeaderSubview) { + toolbar.removeViewAt(i) + } + } + + var i = 0 + val size = mConfigSubviews.size + while (i < size) { + val view = mConfigSubviews[i] + val type = view.type + if (type === ScreenStackHeaderSubview.Type.BACK) { + // we special case BACK button header config type as we don't add it as a view into toolbar + // but instead just copy the drawable from imageview that's added as a first child to it. + val firstChild = view.getChildAt(0) as? ImageView + ?: throw JSApplicationIllegalArgumentException( + "Back button header config view should have Image as first child" + ) + actionBar.setHomeAsUpIndicator(firstChild.drawable) + i++ + continue + } + val params = Toolbar.LayoutParams(LayoutParams.WRAP_CONTENT, LayoutParams.MATCH_PARENT) + when (type) { + ScreenStackHeaderSubview.Type.LEFT -> { + // when there is a left item we need to disable navigation icon by default + // we also hide title as there is no other way to display left side items + if (!mBackButtonInCustomView) { + toolbar.navigationIcon = null + } + toolbar.title = null + params.gravity = Gravity.START + } + ScreenStackHeaderSubview.Type.RIGHT -> params.gravity = Gravity.END + ScreenStackHeaderSubview.Type.CENTER -> { + params.width = LayoutParams.MATCH_PARENT + params.gravity = Gravity.CENTER_HORIZONTAL + toolbar.title = null + } + else -> {} + } + view.layoutParams = params + toolbar.addView(view) + i++ + } + } + + private fun maybeUpdate() { + if (parent != null && !mDestroyed) { + onUpdate() + } + } + + fun getConfigSubview(index: Int): ScreenStackHeaderSubview = mConfigSubviews[index] + + val configSubviewsCount: Int + get() = mConfigSubviews.size + + fun removeConfigSubview(index: Int) { + mConfigSubviews.removeAt(index) + maybeUpdate() + } + + fun removeAllConfigSubviews() { + mConfigSubviews.clear() + maybeUpdate() + } + + fun addConfigSubview(child: ScreenStackHeaderSubview, index: Int) { + mConfigSubviews.add(index, child) + maybeUpdate() + } + + private val titleTextView: TextView? + get() { + for (i in 0 until toolbar.childCount) { + val view = toolbar.getChildAt(i) + if (view is TextView) { + if (view.text == toolbar.title) { + return view + } + } + } + return null + } + + fun setTitle(title: String?) { + mTitle = title + } + + fun setTitleFontFamily(titleFontFamily: String?) { + mTitleFontFamily = titleFontFamily + } + + fun setTitleFontWeight(fontWeightString: String?) { + mTitleFontWeight = ReactTypefaceUtils.parseFontWeight(fontWeightString) + } + + fun setTitleFontSize(titleFontSize: Float) { + mTitleFontSize = titleFontSize + } + + fun setTitleColor(color: Int) { + mTitleColor = color + } + + fun setTintColor(color: Int) { + mTintColor = color + } + + fun setTopInsetEnabled(topInsetEnabled: Boolean) { + mIsTopInsetEnabled = topInsetEnabled + } + + fun setBackgroundColor(color: Int?) { + mBackgroundColor = color + } + + fun setHideShadow(hideShadow: Boolean) { + mIsShadowHidden = hideShadow + } + + fun setHideBackButton(hideBackButton: Boolean) { + mIsBackButtonHidden = hideBackButton + } + + fun setHidden(hidden: Boolean) { + mIsHidden = hidden + } + + fun setTranslucent(translucent: Boolean) { + mIsTranslucent = translucent + } + + fun setBackButtonInCustomView(backButtonInCustomView: Boolean) { + mBackButtonInCustomView = backButtonInCustomView + } + + fun setDirection(direction: String?) { + mDirection = direction + } + + private class DebugMenuToolbar(context: Context, config: ScreenStackHeaderConfig) : CustomToolbar(context, config) { + override fun showOverflowMenu(): Boolean { + (context.applicationContext as ReactApplication) + .reactNativeHost + .reactInstanceManager + .showDevOptionsDialog() + return true + } + } + + init { + visibility = GONE + toolbar = if (BuildConfig.DEBUG) DebugMenuToolbar(context, this) else CustomToolbar(context, this) + mDefaultStartInset = toolbar.contentInsetStart + mDefaultStartInsetWithNavigation = toolbar.contentInsetStartWithNavigation + + // set primary color as background by default + val tv = TypedValue() + if (context.theme.resolveAttribute(R.attr.colorPrimary, tv, true)) { + toolbar.setBackgroundColor(tv.data) + } + toolbar.clipChildren = false + } +} diff --git a/android/vendored/unversioned/react-native-screens/android/src/main/java/com/swmansion/rnscreens/ScreenStackHeaderConfigViewManager.kt b/android/vendored/unversioned/react-native-screens/android/src/main/java/com/swmansion/rnscreens/ScreenStackHeaderConfigViewManager.kt new file mode 100644 index 0000000000000..f17630319fb0b --- /dev/null +++ b/android/vendored/unversioned/react-native-screens/android/src/main/java/com/swmansion/rnscreens/ScreenStackHeaderConfigViewManager.kt @@ -0,0 +1,196 @@ +package com.swmansion.rnscreens + +import android.util.Log +import android.view.View +import com.facebook.react.bridge.JSApplicationCausedNativeException +import com.facebook.react.common.MapBuilder +import com.facebook.react.module.annotations.ReactModule +import com.facebook.react.uimanager.ThemedReactContext +import com.facebook.react.uimanager.ViewGroupManager +import com.facebook.react.uimanager.ViewManagerDelegate +import com.facebook.react.uimanager.annotations.ReactProp +import com.facebook.react.viewmanagers.RNSScreenStackHeaderConfigManagerDelegate +import com.facebook.react.viewmanagers.RNSScreenStackHeaderConfigManagerInterface +import javax.annotation.Nonnull + +@ReactModule(name = ScreenStackHeaderConfigViewManager.REACT_CLASS) +class ScreenStackHeaderConfigViewManager : ViewGroupManager(), RNSScreenStackHeaderConfigManagerInterface { + private val mDelegate: ViewManagerDelegate + + init { + mDelegate = RNSScreenStackHeaderConfigManagerDelegate(this) + } + + override fun getName(): String = REACT_CLASS + + override fun createViewInstance(reactContext: ThemedReactContext) = ScreenStackHeaderConfig(reactContext) + + override fun addView(parent: ScreenStackHeaderConfig, child: View, index: Int) { + if (child !is ScreenStackHeaderSubview) { + throw JSApplicationCausedNativeException( + "Config children should be of type " + ScreenStackHeaderSubviewManager.REACT_CLASS + ) + } + parent.addConfigSubview(child, index) + } + + override fun onDropViewInstance(@Nonnull view: ScreenStackHeaderConfig) { + view.destroy() + } + + override fun removeAllViews(parent: ScreenStackHeaderConfig) { + parent.removeAllConfigSubviews() + } + + override fun removeViewAt(parent: ScreenStackHeaderConfig, index: Int) { + parent.removeConfigSubview(index) + } + + override fun getChildCount(parent: ScreenStackHeaderConfig): Int = parent.configSubviewsCount + + override fun getChildAt(parent: ScreenStackHeaderConfig, index: Int): View = parent.getConfigSubview(index) + + override fun needsCustomLayoutForChildren() = true + + override fun onAfterUpdateTransaction(parent: ScreenStackHeaderConfig) { + super.onAfterUpdateTransaction(parent) + parent.onUpdate() + } + + @ReactProp(name = "title") + override fun setTitle(config: ScreenStackHeaderConfig, title: String?) { + config.setTitle(title) + } + + @ReactProp(name = "titleFontFamily") + override fun setTitleFontFamily(config: ScreenStackHeaderConfig, titleFontFamily: String?) { + config.setTitleFontFamily(titleFontFamily) + } + + @ReactProp(name = "titleFontSize") + override fun setTitleFontSize(config: ScreenStackHeaderConfig, titleFontSize: Int) { + config.setTitleFontSize(titleFontSize.toFloat()) + } + + @ReactProp(name = "titleFontWeight") + override fun setTitleFontWeight(config: ScreenStackHeaderConfig, titleFontWeight: String?) { + config.setTitleFontWeight(titleFontWeight) + } + + @ReactProp(name = "titleColor", customType = "Color") + override fun setTitleColor(config: ScreenStackHeaderConfig, titleColor: Int?) { + if (titleColor != null) { + config.setTitleColor(titleColor) + } + } + + @ReactProp(name = "backgroundColor", customType = "Color") + override fun setBackgroundColor(config: ScreenStackHeaderConfig, backgroundColor: Int?) { + config.setBackgroundColor(backgroundColor) + } + + @ReactProp(name = "hideShadow") + override fun setHideShadow(config: ScreenStackHeaderConfig, hideShadow: Boolean) { + config.setHideShadow(hideShadow) + } + + @ReactProp(name = "hideBackButton") + override fun setHideBackButton(config: ScreenStackHeaderConfig, hideBackButton: Boolean) { + config.setHideBackButton(hideBackButton) + } + + @ReactProp(name = "topInsetEnabled") + override fun setTopInsetEnabled(config: ScreenStackHeaderConfig, topInsetEnabled: Boolean) { + config.setTopInsetEnabled(topInsetEnabled) + } + + @ReactProp(name = "color", customType = "Color") + override fun setColor(config: ScreenStackHeaderConfig, color: Int?) { + config.setTintColor(color ?: 0) + } + + @ReactProp(name = "hidden") + override fun setHidden(config: ScreenStackHeaderConfig, hidden: Boolean) { + config.setHidden(hidden) + } + + @ReactProp(name = "translucent") + override fun setTranslucent(config: ScreenStackHeaderConfig, translucent: Boolean) { + config.setTranslucent(translucent) + } + + @ReactProp(name = "backButtonInCustomView") + override fun setBackButtonInCustomView( + config: ScreenStackHeaderConfig, + backButtonInCustomView: Boolean + ) { + config.setBackButtonInCustomView(backButtonInCustomView) + } + + @ReactProp(name = "direction") + override fun setDirection(config: ScreenStackHeaderConfig, direction: String?) { + config.setDirection(direction) + } + + override fun getExportedCustomDirectEventTypeConstants(): Map? { + return MapBuilder.builder() + .put("onAttached", MapBuilder.of("registrationName", "onAttached")) + .put("onDetached", MapBuilder.of("registrationName", "onDetached")) + .build() + } + + protected override fun getDelegate(): ViewManagerDelegate = mDelegate + + companion object { + const val REACT_CLASS = "RNSScreenStackHeaderConfig" + } + + // TODO: Find better way to handle platform specific props + private fun logNotAvailable(propName: String) { + Log.w("RN SCREENS", "$propName prop is not available on Android") + } + + override fun setBackTitle(view: ScreenStackHeaderConfig?, value: String?) { + logNotAvailable("backTitle") + } + + override fun setBackTitleFontFamily(view: ScreenStackHeaderConfig?, value: String?) { + logNotAvailable("backTitleFontFamily") + } + + override fun setBackTitleFontSize(view: ScreenStackHeaderConfig?, value: Int) { + logNotAvailable("backTitleFontSize") + } + + override fun setLargeTitle(view: ScreenStackHeaderConfig?, value: Boolean) { + logNotAvailable("largeTitle") + } + + override fun setLargeTitleFontFamily(view: ScreenStackHeaderConfig?, value: String?) { + logNotAvailable("largeTitleFontFamily") + } + + override fun setLargeTitleFontSize(view: ScreenStackHeaderConfig?, value: Int) { + logNotAvailable("largeTitleFontSize") + } + + override fun setLargeTitleFontWeight(view: ScreenStackHeaderConfig?, value: String?) { + logNotAvailable("largeTitleFontWeight") + } + + override fun setLargeTitleBackgroundColor(view: ScreenStackHeaderConfig?, value: Int?) { + logNotAvailable("largeTitleBackgroundColor") + } + + override fun setLargeTitleHideShadow(view: ScreenStackHeaderConfig?, value: Boolean) { + logNotAvailable("largeTitleHideShadow") + } + + override fun setLargeTitleColor(view: ScreenStackHeaderConfig?, value: Int?) { + logNotAvailable("largeTitleColor") + } + + override fun setDisableBackButtonMenu(view: ScreenStackHeaderConfig?, value: Boolean) { + logNotAvailable("disableBackButtonMenu") + } +} diff --git a/android/vendored/unversioned/react-native-screens/android/src/main/java/com/swmansion/rnscreens/ScreenStackHeaderSubview.kt b/android/vendored/unversioned/react-native-screens/android/src/main/java/com/swmansion/rnscreens/ScreenStackHeaderSubview.kt new file mode 100644 index 0000000000000..74a2c8b1239fd --- /dev/null +++ b/android/vendored/unversioned/react-native-screens/android/src/main/java/com/swmansion/rnscreens/ScreenStackHeaderSubview.kt @@ -0,0 +1,38 @@ +package com.swmansion.rnscreens + +import android.annotation.SuppressLint +import android.view.View +import com.facebook.react.bridge.ReactContext +import com.facebook.react.views.view.ReactViewGroup + +@SuppressLint("ViewConstructor") +class ScreenStackHeaderSubview(context: ReactContext?) : ReactViewGroup(context) { + private var mReactWidth = 0 + private var mReactHeight = 0 + var type = Type.RIGHT + + val config: ScreenStackHeaderConfig? + get() = (parent as? CustomToolbar)?.config + + override fun onMeasure(widthMeasureSpec: Int, heightMeasureSpec: Int) { + if (MeasureSpec.getMode(widthMeasureSpec) == MeasureSpec.EXACTLY && + MeasureSpec.getMode(heightMeasureSpec) == MeasureSpec.EXACTLY + ) { + // dimensions provided by react + mReactWidth = MeasureSpec.getSize(widthMeasureSpec) + mReactHeight = MeasureSpec.getSize(heightMeasureSpec) + val parent = parent + if (parent != null) { + forceLayout() + (parent as View).requestLayout() + } + } + setMeasuredDimension(mReactWidth, mReactHeight) + } + + override fun onLayout(changed: Boolean, left: Int, top: Int, right: Int, bottom: Int) = Unit + + enum class Type { + LEFT, CENTER, RIGHT, BACK, SEARCH_BAR + } +} diff --git a/android/vendored/unversioned/react-native-screens/android/src/main/java/com/swmansion/rnscreens/ScreenStackHeaderSubviewManager.kt b/android/vendored/unversioned/react-native-screens/android/src/main/java/com/swmansion/rnscreens/ScreenStackHeaderSubviewManager.kt new file mode 100644 index 0000000000000..b2d2505ffd9f7 --- /dev/null +++ b/android/vendored/unversioned/react-native-screens/android/src/main/java/com/swmansion/rnscreens/ScreenStackHeaderSubviewManager.kt @@ -0,0 +1,41 @@ +package com.swmansion.rnscreens + +import com.facebook.react.bridge.JSApplicationIllegalArgumentException +import com.facebook.react.module.annotations.ReactModule +import com.facebook.react.uimanager.ThemedReactContext +import com.facebook.react.uimanager.ViewGroupManager +import com.facebook.react.uimanager.ViewManagerDelegate +import com.facebook.react.uimanager.annotations.ReactProp +import com.facebook.react.viewmanagers.RNSScreenStackHeaderSubviewManagerDelegate +import com.facebook.react.viewmanagers.RNSScreenStackHeaderSubviewManagerInterface + +@ReactModule(name = ScreenStackHeaderSubviewManager.REACT_CLASS) +class ScreenStackHeaderSubviewManager : ViewGroupManager(), RNSScreenStackHeaderSubviewManagerInterface { + private val mDelegate: ViewManagerDelegate + + init { + mDelegate = RNSScreenStackHeaderSubviewManagerDelegate(this) + } + + override fun getName() = REACT_CLASS + + override fun createViewInstance(context: ThemedReactContext) = ScreenStackHeaderSubview(context) + + @ReactProp(name = "type") + override fun setType(view: ScreenStackHeaderSubview, type: String?) { + view.type = when (type) { + "left" -> ScreenStackHeaderSubview.Type.LEFT + "center" -> ScreenStackHeaderSubview.Type.CENTER + "right" -> ScreenStackHeaderSubview.Type.RIGHT + "back" -> ScreenStackHeaderSubview.Type.BACK + "searchBar" -> ScreenStackHeaderSubview.Type.SEARCH_BAR + else -> throw JSApplicationIllegalArgumentException("Unknown type $type") + } + } + + protected override fun getDelegate(): ViewManagerDelegate = mDelegate + + companion object { + const val REACT_CLASS = "RNSScreenStackHeaderSubview" + } +} diff --git a/android/vendored/unversioned/react-native-screens/android/src/main/java/com/swmansion/rnscreens/ScreenStackViewManager.kt b/android/vendored/unversioned/react-native-screens/android/src/main/java/com/swmansion/rnscreens/ScreenStackViewManager.kt new file mode 100644 index 0000000000000..d1176a4d49ad3 --- /dev/null +++ b/android/vendored/unversioned/react-native-screens/android/src/main/java/com/swmansion/rnscreens/ScreenStackViewManager.kt @@ -0,0 +1,75 @@ +package com.swmansion.rnscreens + +import android.view.View +import android.view.ViewGroup +import com.facebook.react.bridge.ReactApplicationContext +import com.facebook.react.module.annotations.ReactModule +import com.facebook.react.uimanager.LayoutShadowNode +import com.facebook.react.uimanager.ThemedReactContext +import com.facebook.react.uimanager.ViewGroupManager +import com.facebook.react.uimanager.ViewManagerDelegate +import com.facebook.react.viewmanagers.RNSScreenStackManagerDelegate +import com.facebook.react.viewmanagers.RNSScreenStackManagerInterface +import com.swmansion.rnscreens.events.StackFinishTransitioningEvent + +@ReactModule(name = ScreenStackViewManager.REACT_CLASS) +class ScreenStackViewManager : ViewGroupManager(), RNSScreenStackManagerInterface { + private val mDelegate: ViewManagerDelegate + + init { + mDelegate = RNSScreenStackManagerDelegate(this) + } + + override fun getName() = REACT_CLASS + + override fun createViewInstance(reactContext: ThemedReactContext) = ScreenStack(reactContext) + + override fun addView(parent: ScreenStack, child: View, index: Int) { + require(child is Screen) { "Attempt attach child that is not of type RNScreen" } + parent.addScreen(child, index) + } + + override fun removeViewAt(parent: ScreenStack, index: Int) { + prepareOutTransition(parent.getScreenAt(index)) + parent.removeScreenAt(index) + } + + private fun prepareOutTransition(screen: Screen?) { + startTransitionRecursive(screen) + } + + private fun startTransitionRecursive(parent: ViewGroup?) { + parent?.let { + for (i in 0 until it.childCount) { + val child = it.getChildAt(i) + child?.let { view -> it.startViewTransition(view) } + if (child is ScreenStackHeaderConfig) { + // we want to start transition on children of the toolbar too, + // which is not a child of ScreenStackHeaderConfig + startTransitionRecursive(child.toolbar) + } + if (child is ViewGroup) { + startTransitionRecursive(child) + } + } + } + } + + override fun getChildCount(parent: ScreenStack) = parent.screenCount + + override fun getChildAt(parent: ScreenStack, index: Int): View = parent.getScreenAt(index) + + override fun createShadowNodeInstance(context: ReactApplicationContext): LayoutShadowNode = ScreensShadowNode(context) + + override fun needsCustomLayoutForChildren() = true + + protected override fun getDelegate(): ViewManagerDelegate = mDelegate + + override fun getExportedCustomDirectEventTypeConstants(): MutableMap = mutableMapOf( + StackFinishTransitioningEvent.EVENT_NAME to mutableMapOf("registrationName" to "onFinishTransitioning") + ) + + companion object { + const val REACT_CLASS = "RNSScreenStack" + } +} diff --git a/android/vendored/unversioned/react-native-screens/android/src/main/java/com/swmansion/rnscreens/ScreenViewManager.kt b/android/vendored/unversioned/react-native-screens/android/src/main/java/com/swmansion/rnscreens/ScreenViewManager.kt new file mode 100644 index 0000000000000..736e06b652e4e --- /dev/null +++ b/android/vendored/unversioned/react-native-screens/android/src/main/java/com/swmansion/rnscreens/ScreenViewManager.kt @@ -0,0 +1,183 @@ +package com.swmansion.rnscreens + +import com.facebook.react.bridge.JSApplicationIllegalArgumentException +import com.facebook.react.bridge.ReadableMap +import com.facebook.react.common.MapBuilder +import com.facebook.react.module.annotations.ReactModule +import com.facebook.react.uimanager.ThemedReactContext +import com.facebook.react.uimanager.ViewGroupManager +import com.facebook.react.uimanager.ViewManagerDelegate +import com.facebook.react.uimanager.annotations.ReactProp +import com.facebook.react.viewmanagers.RNSScreenManagerDelegate +import com.facebook.react.viewmanagers.RNSScreenManagerInterface +import com.swmansion.rnscreens.events.HeaderBackButtonClickedEvent +import com.swmansion.rnscreens.events.ScreenAppearEvent +import com.swmansion.rnscreens.events.ScreenDisappearEvent +import com.swmansion.rnscreens.events.ScreenDismissedEvent +import com.swmansion.rnscreens.events.ScreenTransitionProgressEvent +import com.swmansion.rnscreens.events.ScreenWillAppearEvent +import com.swmansion.rnscreens.events.ScreenWillDisappearEvent + +@ReactModule(name = ScreenViewManager.REACT_CLASS) +class ScreenViewManager : ViewGroupManager(), RNSScreenManagerInterface { + private val mDelegate: ViewManagerDelegate + + init { + mDelegate = RNSScreenManagerDelegate(this) + } + + override fun getName() = REACT_CLASS + + override fun createViewInstance(reactContext: ThemedReactContext) = Screen(reactContext) + + override fun setActivityState(view: Screen, activityState: Float) { + setActivityState(view, activityState.toInt()) + } + + @ReactProp(name = "activityState") + fun setActivityState(view: Screen, activityState: Int) { + if (activityState == -1) { + // Null will be provided when activityState is set as an animated value and we change + // it from JS to be a plain value (non animated). + // In case when null is received, we want to ignore such value and not make + // any updates as the actual non-null value will follow immediately. + return + } + when (activityState) { + 0 -> view.setActivityState(Screen.ActivityState.INACTIVE) + 1 -> view.setActivityState(Screen.ActivityState.TRANSITIONING_OR_BELOW_TOP) + 2 -> view.setActivityState(Screen.ActivityState.ON_TOP) + } + } + + @ReactProp(name = "stackPresentation") + override fun setStackPresentation(view: Screen, presentation: String?) { + view.stackPresentation = when (presentation) { + "push" -> Screen.StackPresentation.PUSH + "modal", "containedModal", "fullScreenModal", "formSheet" -> + Screen.StackPresentation.MODAL + "transparentModal", "containedTransparentModal" -> + Screen.StackPresentation.TRANSPARENT_MODAL + else -> throw JSApplicationIllegalArgumentException("Unknown presentation type $presentation") + } + } + + @ReactProp(name = "stackAnimation") + override fun setStackAnimation(view: Screen, animation: String?) { + view.stackAnimation = when (animation) { + null, "default", "flip", "simple_push" -> Screen.StackAnimation.DEFAULT + "none" -> Screen.StackAnimation.NONE + "fade" -> Screen.StackAnimation.FADE + "slide_from_right" -> Screen.StackAnimation.SLIDE_FROM_RIGHT + "slide_from_left" -> Screen.StackAnimation.SLIDE_FROM_LEFT + "slide_from_bottom" -> Screen.StackAnimation.SLIDE_FROM_BOTTOM + "fade_from_bottom" -> Screen.StackAnimation.FADE_FROM_BOTTOM + else -> throw JSApplicationIllegalArgumentException("Unknown animation type $animation") + } + } + + @ReactProp(name = "gestureEnabled", defaultBoolean = true) + override fun setGestureEnabled(view: Screen, gestureEnabled: Boolean) { + view.isGestureEnabled = gestureEnabled + } + + @ReactProp(name = "replaceAnimation") + override fun setReplaceAnimation(view: Screen, animation: String?) { + view.replaceAnimation = when (animation) { + null, "pop" -> Screen.ReplaceAnimation.POP + "push" -> Screen.ReplaceAnimation.PUSH + else -> throw JSApplicationIllegalArgumentException("Unknown replace animation type $animation") + } + } + + @ReactProp(name = "screenOrientation") + override fun setScreenOrientation(view: Screen, screenOrientation: String?) { + view.setScreenOrientation(screenOrientation) + } + + @ReactProp(name = "statusBarAnimation") + override fun setStatusBarAnimation(view: Screen, statusBarAnimation: String?) { + val animated = statusBarAnimation != null && "none" != statusBarAnimation + view.isStatusBarAnimated = animated + } + + @ReactProp(name = "statusBarColor", customType = "Color") + override fun setStatusBarColor(view: Screen, statusBarColor: Int?) { + view.statusBarColor = statusBarColor + } + + @ReactProp(name = "statusBarStyle") + override fun setStatusBarStyle(view: Screen, statusBarStyle: String?) { + view.statusBarStyle = statusBarStyle + } + + @ReactProp(name = "statusBarTranslucent") + override fun setStatusBarTranslucent(view: Screen, statusBarTranslucent: Boolean) { + view.isStatusBarTranslucent = statusBarTranslucent + } + + @ReactProp(name = "statusBarHidden") + override fun setStatusBarHidden(view: Screen, statusBarHidden: Boolean) { + view.isStatusBarHidden = statusBarHidden + } + + @ReactProp(name = "navigationBarColor", customType = "Color") + override fun setNavigationBarColor(view: Screen, navigationBarColor: Int?) { + view.navigationBarColor = navigationBarColor + } + + @ReactProp(name = "navigationBarHidden") + override fun setNavigationBarHidden(view: Screen, navigationBarHidden: Boolean) { + view.isNavigationBarHidden = navigationBarHidden + } + + @ReactProp(name = "nativeBackButtonDismissalEnabled") + override fun setNativeBackButtonDismissalEnabled( + view: Screen, + nativeBackButtonDismissalEnabled: Boolean + ) { + view.nativeBackButtonDismissalEnabled = nativeBackButtonDismissalEnabled + } + + // these props are not available on Android, however we must override their setters + override fun setFullScreenSwipeEnabled(view: Screen?, value: Boolean) = Unit + + override fun setTransitionDuration(view: Screen?, value: Int) = Unit + + override fun setHideKeyboardOnSwipe(view: Screen?, value: Boolean) = Unit + + override fun setCustomAnimationOnSwipe(view: Screen?, value: Boolean) = Unit + + override fun setGestureResponseDistance(view: Screen?, value: ReadableMap?) = Unit + + override fun setHomeIndicatorHidden(view: Screen?, value: Boolean) = Unit + + override fun setPreventNativeDismiss(view: Screen?, value: Boolean) = Unit + + override fun setSwipeDirection(view: Screen?, value: String?) = Unit + + override fun getExportedCustomDirectEventTypeConstants(): MutableMap { + return MapBuilder.of( + ScreenDismissedEvent.EVENT_NAME, + MapBuilder.of("registrationName", "onDismissed"), + ScreenWillAppearEvent.EVENT_NAME, + MapBuilder.of("registrationName", "onWillAppear"), + ScreenAppearEvent.EVENT_NAME, + MapBuilder.of("registrationName", "onAppear"), + ScreenWillDisappearEvent.EVENT_NAME, + MapBuilder.of("registrationName", "onWillDisappear"), + ScreenDisappearEvent.EVENT_NAME, + MapBuilder.of("registrationName", "onDisappear"), + HeaderBackButtonClickedEvent.EVENT_NAME, + MapBuilder.of("registrationName", "onHeaderBackButtonClicked"), + ScreenTransitionProgressEvent.EVENT_NAME, + MapBuilder.of("registrationName", "onTransitionProgress") + ) + } + + protected override fun getDelegate(): ViewManagerDelegate = mDelegate + + companion object { + const val REACT_CLASS = "RNSScreen" + } +} diff --git a/android/vendored/unversioned/react-native-screens/android/src/main/java/com/swmansion/rnscreens/ScreenWindowTraits.kt b/android/vendored/unversioned/react-native-screens/android/src/main/java/com/swmansion/rnscreens/ScreenWindowTraits.kt new file mode 100644 index 0000000000000..56278236cd52a --- /dev/null +++ b/android/vendored/unversioned/react-native-screens/android/src/main/java/com/swmansion/rnscreens/ScreenWindowTraits.kt @@ -0,0 +1,276 @@ +package com.swmansion.rnscreens + +import android.animation.ArgbEvaluator +import android.animation.ValueAnimator +import android.annotation.SuppressLint +import android.annotation.TargetApi +import android.app.Activity +import android.content.pm.ActivityInfo +import android.graphics.Color +import android.os.Build +import android.view.ViewParent +import androidx.core.view.ViewCompat +import androidx.core.view.WindowCompat +import androidx.core.view.WindowInsetsCompat +import androidx.core.view.WindowInsetsControllerCompat +import com.facebook.react.bridge.GuardedRunnable +import com.facebook.react.bridge.ReactContext +import com.facebook.react.bridge.UiThreadUtil +import com.swmansion.rnscreens.Screen.WindowTraits + +object ScreenWindowTraits { + // Methods concerning statusBar management were taken from `react-native`'s status bar module: + // https://github.com/facebook/react-native/blob/master/ReactAndroid/src/main/java/com/facebook/react/modules/statusbar/StatusBarModule.java + private var mDidSetOrientation = false + private var mDidSetStatusBarAppearance = false + private var mDidSetNavigationBarAppearance = false + private var mDefaultStatusBarColor: Int? = null + + internal fun applyDidSetOrientation() { + mDidSetOrientation = true + } + + internal fun applyDidSetStatusBarAppearance() { + mDidSetStatusBarAppearance = true + } + + internal fun applyDidSetNavigationBarAppearance() { + mDidSetNavigationBarAppearance = true + } + + internal fun setOrientation(screen: Screen, activity: Activity?) { + if (activity == null) { + return + } + val screenForOrientation = findScreenForTrait(screen, WindowTraits.ORIENTATION) + val orientation = screenForOrientation?.screenOrientation ?: ActivityInfo.SCREEN_ORIENTATION_UNSPECIFIED + activity.requestedOrientation = orientation + } + + @SuppressLint("ObsoleteSdkInt") // to be removed when support for < 0.64 is dropped + internal fun setColor(screen: Screen, activity: Activity?, context: ReactContext?) { + if (activity == null || context == null) { + return + } + if (mDefaultStatusBarColor == null) { + mDefaultStatusBarColor = activity.window.statusBarColor + } + val screenForColor = findScreenForTrait(screen, WindowTraits.COLOR) + val screenForAnimated = findScreenForTrait(screen, WindowTraits.ANIMATED) + val color = screenForColor?.statusBarColor ?: mDefaultStatusBarColor + val animated = screenForAnimated?.isStatusBarAnimated ?: false + + UiThreadUtil.runOnUiThread( + object : GuardedRunnable(context) { + override fun runGuarded() { + val window = activity.window + val curColor: Int = window.statusBarColor + val colorAnimation = ValueAnimator.ofObject(ArgbEvaluator(), curColor, color) + colorAnimation.addUpdateListener { animator -> + window.statusBarColor = animator.animatedValue as Int + } + if (animated) { + colorAnimation.setDuration(300).startDelay = 0 + } else { + colorAnimation.setDuration(0).startDelay = 300 + } + colorAnimation.start() + } + }) + } + + internal fun setStyle(screen: Screen, activity: Activity?, context: ReactContext?) { + if (activity == null || context == null || Build.VERSION.SDK_INT < Build.VERSION_CODES.M) { + return + } + val screenForStyle = findScreenForTrait(screen, WindowTraits.STYLE) + val style = screenForStyle?.statusBarStyle ?: "light" + + UiThreadUtil.runOnUiThread { + val decorView = activity.window.decorView + val window = activity.window + val controller = WindowInsetsControllerCompat(window, decorView) + + controller.isAppearanceLightStatusBars = style == "dark" + } + } + + internal fun setTranslucent( + screen: Screen, + activity: Activity?, + context: ReactContext? + ) { + if (activity == null || context == null) { + return + } + val screenForTranslucent = findScreenForTrait(screen, WindowTraits.TRANSLUCENT) + val translucent = screenForTranslucent?.isStatusBarTranslucent ?: false + UiThreadUtil.runOnUiThread( + object : GuardedRunnable(context) { + @TargetApi(Build.VERSION_CODES.LOLLIPOP) + override fun runGuarded() { + // If the status bar is translucent hook into the window insets calculations + // and consume all the top insets so no padding will be added under the status bar. + val decorView = activity.window.decorView + if (translucent) { + ViewCompat.setOnApplyWindowInsetsListener(decorView) { v, insets -> + val defaultInsets = ViewCompat.onApplyWindowInsets(v, insets) + defaultInsets.replaceSystemWindowInsets( + defaultInsets.systemWindowInsetLeft, + 0, + defaultInsets.systemWindowInsetRight, + defaultInsets.systemWindowInsetBottom + ) + } + } else { + ViewCompat.setOnApplyWindowInsetsListener(decorView, null) + } + ViewCompat.requestApplyInsets(decorView) + } + }) + } + + internal fun setHidden(screen: Screen, activity: Activity?) { + if (activity == null) { + return + } + val screenForHidden = findScreenForTrait(screen, WindowTraits.HIDDEN) + val hidden = screenForHidden?.isStatusBarHidden ?: false + val window = activity.window + val controller = WindowInsetsControllerCompat(window, window.decorView) + + UiThreadUtil.runOnUiThread { + if (hidden) { + controller.hide(WindowInsetsCompat.Type.statusBars()) + } else { + controller.show(WindowInsetsCompat.Type.statusBars()) + } + } + } + + // Methods concerning navigationBar management were taken from `react-native-navigation`'s repo: + // https://github.com/wix/react-native-navigation/blob/9bb70d81700692141a2c505c081c2d86c7f9c66e/lib/android/app/src/main/java/com/reactnativenavigation/utils/SystemUiUtils.kt + internal fun setNavigationBarColor(screen: Screen, activity: Activity?) { + if (activity == null) { + return + } + + val window = activity.window + + val screenForNavBarColor = findScreenForTrait(screen, WindowTraits.NAVIGATION_BAR_COLOR) + val color = screenForNavBarColor?.navigationBarColor ?: window.navigationBarColor + + UiThreadUtil.runOnUiThread { + WindowInsetsControllerCompat(window, window.decorView).isAppearanceLightNavigationBars = + isColorLight(color) + } + window.navigationBarColor = color + } + + internal fun setNavigationBarHidden(screen: Screen, activity: Activity?) { + if (activity == null) { + return + } + + val window = activity.window + + val screenForNavBarHidden = findScreenForTrait(screen, WindowTraits.NAVIGATION_BAR_HIDDEN) + val hidden = screenForNavBarHidden?.isNavigationBarHidden ?: false + + WindowCompat.setDecorFitsSystemWindows(window, hidden) + if (hidden) { + WindowInsetsControllerCompat(window, window.decorView).let { controller -> + controller.hide(WindowInsetsCompat.Type.navigationBars()) + controller.systemBarsBehavior = + WindowInsetsControllerCompat.BEHAVIOR_SHOW_TRANSIENT_BARS_BY_SWIPE + } + } else { + WindowInsetsControllerCompat( + window, + window.decorView + ).show(WindowInsetsCompat.Type.navigationBars()) + } + } + + internal fun trySetWindowTraits(screen: Screen, activity: Activity?, context: ReactContext?) { + if (mDidSetOrientation) { + setOrientation(screen, activity) + } + if (mDidSetStatusBarAppearance) { + setColor(screen, activity, context) + setStyle(screen, activity, context) + setTranslucent(screen, activity, context) + setHidden(screen, activity) + } + if (mDidSetNavigationBarAppearance) { + setNavigationBarColor(screen, activity) + setNavigationBarHidden(screen, activity) + } + } + + private fun findScreenForTrait(screen: Screen, trait: WindowTraits): Screen? { + val childWithTrait = childScreenWithTraitSet(screen, trait) + if (childWithTrait != null) { + return childWithTrait + } + return if (checkTraitForScreen(screen, trait)) { + screen + } else { + // if there is no child with trait set and this screen has no trait set, we look for a parent + // that has the trait set + findParentWithTraitSet(screen, trait) + } + } + + private fun findParentWithTraitSet(screen: Screen, trait: WindowTraits): Screen? { + var parent: ViewParent? = screen.container + while (parent != null) { + if (parent is Screen) { + if (checkTraitForScreen(parent, trait)) { + return parent + } + } + parent = parent.parent + } + return null + } + + private fun childScreenWithTraitSet( + screen: Screen?, + trait: WindowTraits + ): Screen? { + screen?.fragment?.let { + for (sc in it.childScreenContainers) { + // we check only the top screen for the trait + val topScreen = sc.topScreen + val child = childScreenWithTraitSet(topScreen, trait) + if (child != null) { + return child + } + if (topScreen != null && checkTraitForScreen(topScreen, trait)) { + return topScreen + } + } + } + return null + } + + private fun checkTraitForScreen(screen: Screen, trait: WindowTraits): Boolean { + return when (trait) { + WindowTraits.ORIENTATION -> screen.screenOrientation != null + WindowTraits.COLOR -> screen.statusBarColor != null + WindowTraits.STYLE -> screen.statusBarStyle != null + WindowTraits.TRANSLUCENT -> screen.isStatusBarTranslucent != null + WindowTraits.HIDDEN -> screen.isStatusBarHidden != null + WindowTraits.ANIMATED -> screen.isStatusBarAnimated != null + WindowTraits.NAVIGATION_BAR_COLOR -> screen.navigationBarColor != null + WindowTraits.NAVIGATION_BAR_HIDDEN -> screen.isNavigationBarHidden != null + } + } + + private fun isColorLight(color: Int): Boolean { + val darkness: Double = + 1 - (0.299 * Color.red(color) + 0.587 * Color.green(color) + 0.114 * Color.blue(color)) / 255 + return darkness < 0.5 + } +} diff --git a/android/vendored/unversioned/react-native-screens/android/src/main/java/com/swmansion/rnscreens/ScreensShadowNode.kt b/android/vendored/unversioned/react-native-screens/android/src/main/java/com/swmansion/rnscreens/ScreensShadowNode.kt new file mode 100644 index 0000000000000..6893aa1a4a545 --- /dev/null +++ b/android/vendored/unversioned/react-native-screens/android/src/main/java/com/swmansion/rnscreens/ScreensShadowNode.kt @@ -0,0 +1,19 @@ +package com.swmansion.rnscreens + +import com.facebook.react.bridge.ReactContext +import com.facebook.react.uimanager.LayoutShadowNode +import com.facebook.react.uimanager.NativeViewHierarchyManager +import com.facebook.react.uimanager.NativeViewHierarchyOptimizer +import com.facebook.react.uimanager.UIManagerModule + +internal class ScreensShadowNode(private var mContext: ReactContext) : LayoutShadowNode() { + override fun onBeforeLayout(nativeViewHierarchyOptimizer: NativeViewHierarchyOptimizer) { + super.onBeforeLayout(nativeViewHierarchyOptimizer) + (mContext.getNativeModule(UIManagerModule::class.java))?.addUIBlock { nativeViewHierarchyManager: NativeViewHierarchyManager -> + val view = nativeViewHierarchyManager.resolveView(reactTag) + if (view is ScreenContainer<*>) { + view.performUpdates() + } + } + } +} diff --git a/android/vendored/unversioned/react-native-screens/android/src/main/java/com/swmansion/rnscreens/SearchBarManager.kt b/android/vendored/unversioned/react-native-screens/android/src/main/java/com/swmansion/rnscreens/SearchBarManager.kt new file mode 100644 index 0000000000000..79ef4991a6d4d --- /dev/null +++ b/android/vendored/unversioned/react-native-screens/android/src/main/java/com/swmansion/rnscreens/SearchBarManager.kt @@ -0,0 +1,107 @@ +package com.swmansion.rnscreens + +import com.facebook.react.bridge.JSApplicationIllegalArgumentException +import com.facebook.react.common.MapBuilder +import com.facebook.react.module.annotations.ReactModule +import com.facebook.react.uimanager.ThemedReactContext +import com.facebook.react.uimanager.ViewGroupManager +import com.facebook.react.uimanager.annotations.ReactProp + +@ReactModule(name = SearchBarManager.REACT_CLASS) +class SearchBarManager : ViewGroupManager() { + override fun getName(): String { + return REACT_CLASS + } + + override fun createViewInstance(context: ThemedReactContext): SearchBarView { + return SearchBarView(context) + } + + override fun onAfterUpdateTransaction(view: SearchBarView) { + super.onAfterUpdateTransaction(view) + view.onUpdate() + } + + @ReactProp(name = "autoCapitalize") + fun setAutoCapitalize(view: SearchBarView, autoCapitalize: String?) { + view.autoCapitalize = when (autoCapitalize) { + null, "none" -> SearchBarView.SearchBarAutoCapitalize.NONE + "words" -> SearchBarView.SearchBarAutoCapitalize.WORDS + "sentences" -> SearchBarView.SearchBarAutoCapitalize.SENTENCES + "characters" -> SearchBarView.SearchBarAutoCapitalize.CHARACTERS + else -> throw JSApplicationIllegalArgumentException( + "Forbidden auto capitalize value passed" + ) + } + } + + @ReactProp(name = "autoFocus") + fun setAutoFocus(view: SearchBarView, autoFocus: Boolean?) { + view.autoFocus = autoFocus ?: false + } + + @ReactProp(name = "barTintColor", customType = "Color") + fun setTintColor(view: SearchBarView, color: Int?) { + view.tintColor = color + } + + @ReactProp(name = "disableBackButtonOverride") + fun setDisableBackButtonOverride(view: SearchBarView, disableBackButtonOverride: Boolean?) { + view.shouldOverrideBackButton = disableBackButtonOverride != true + } + + @ReactProp(name = "inputType") + fun setInputType(view: SearchBarView, inputType: String?) { + view.inputType = when (inputType) { + null, "text" -> SearchBarView.SearchBarInputTypes.TEXT + "phone" -> SearchBarView.SearchBarInputTypes.PHONE + "number" -> SearchBarView.SearchBarInputTypes.NUMBER + "email" -> SearchBarView.SearchBarInputTypes.EMAIL + else -> throw JSApplicationIllegalArgumentException( + "Forbidden input type value" + ) + } + } + + @ReactProp(name = "placeholder") + fun setPlaceholder(view: SearchBarView, placeholder: String?) { + if (placeholder != null) { + view.placeholder = placeholder + } + } + + @ReactProp(name = "textColor", customType = "Color") + fun setTextColor(view: SearchBarView, color: Int?) { + view.textColor = color + } + + @ReactProp(name = "headerIconColor", customType = "Color") + fun setHeaderIconColor(view: SearchBarView, color: Int?) { + view.headerIconColor = color + } + + @ReactProp(name = "hintTextColor", customType = "Color") + fun setHintTextColor(view: SearchBarView, color: Int?) { + view.hintTextColor = color + } + + @ReactProp(name = "shouldShowHintSearchIcon") + fun setShouldShowHintSearchIcon(view: SearchBarView, shouldShowHintSearchIcon: Boolean?) { + view.shouldShowHintSearchIcon = shouldShowHintSearchIcon ?: true + } + + override fun getExportedCustomDirectEventTypeConstants(): Map? { + return MapBuilder.builder() + .put("onChangeText", MapBuilder.of("registrationName", "onChangeText")) + .put("onSearchButtonPress", MapBuilder.of("registrationName", "onSearchButtonPress")) + .put("onFocus", MapBuilder.of("registrationName", "onFocus")) + .put("onBlur", MapBuilder.of("registrationName", "onBlur")) + .put("onClose", MapBuilder.of("registrationName", "onClose")) + .put("onOpen", MapBuilder.of("registrationName", "onOpen")) + .build() + } + + companion object { + const val REACT_CLASS = "RNSSearchBar" + } +} diff --git a/android/vendored/unversioned/react-native-screens/android/src/main/java/com/swmansion/rnscreens/SearchBarView.kt b/android/vendored/unversioned/react-native-screens/android/src/main/java/com/swmansion/rnscreens/SearchBarView.kt new file mode 100644 index 0000000000000..46b8fd45eaf07 --- /dev/null +++ b/android/vendored/unversioned/react-native-screens/android/src/main/java/com/swmansion/rnscreens/SearchBarView.kt @@ -0,0 +1,155 @@ +package com.swmansion.rnscreens + +import android.annotation.SuppressLint +import android.text.InputType +import androidx.appcompat.widget.SearchView +import com.facebook.react.bridge.Arguments +import com.facebook.react.bridge.ReactContext +import com.facebook.react.bridge.WritableMap +import com.facebook.react.uimanager.events.RCTEventEmitter +import com.facebook.react.views.view.ReactViewGroup + +@SuppressLint("ViewConstructor") +class SearchBarView(reactContext: ReactContext?) : ReactViewGroup(reactContext) { + var inputType: SearchBarInputTypes = SearchBarInputTypes.TEXT + var autoCapitalize: SearchBarAutoCapitalize = SearchBarAutoCapitalize.NONE + var textColor: Int? = null + var tintColor: Int? = null + var headerIconColor: Int? = null + var hintTextColor: Int? = null + var placeholder: String = "" + var shouldOverrideBackButton: Boolean = true + var autoFocus: Boolean = false + var shouldShowHintSearchIcon: Boolean = true + + private var mSearchViewFormatter: SearchViewFormatter? = null + + private var mAreListenersSet: Boolean = false + + private val screenStackFragment: ScreenStackFragment? + get() { + val currentParent = parent + if (currentParent is ScreenStackHeaderSubview) { + return currentParent.config?.screenFragment + } + return null + } + + fun onUpdate() { + setSearchViewProps() + } + + private fun setSearchViewProps() { + val searchView = screenStackFragment?.searchView + if (searchView != null) { + if (!mAreListenersSet) { + setSearchViewListeners(searchView) + mAreListenersSet = true + } + + searchView.inputType = inputType.toAndroidInputType(autoCapitalize) + mSearchViewFormatter?.setTextColor(textColor) + mSearchViewFormatter?.setTintColor(tintColor) + mSearchViewFormatter?.setHeaderIconColor(headerIconColor) + mSearchViewFormatter?.setHintTextColor(hintTextColor) + mSearchViewFormatter?.setPlaceholder(placeholder, shouldShowHintSearchIcon) + searchView.overrideBackAction = shouldOverrideBackButton + } + } + + override fun onAttachedToWindow() { + super.onAttachedToWindow() + + screenStackFragment?.onSearchViewCreate = { newSearchView -> + if (mSearchViewFormatter == null) mSearchViewFormatter = + SearchViewFormatter(newSearchView) + setSearchViewProps() + if (autoFocus) { + screenStackFragment?.searchView?.focus() + } + } + } + + private fun setSearchViewListeners(searchView: SearchView) { + searchView.setOnQueryTextListener(object : SearchView.OnQueryTextListener { + override fun onQueryTextChange(newText: String?): Boolean { + handleTextChange(newText) + return true + } + + override fun onQueryTextSubmit(query: String?): Boolean { + handleTextSubmit(query) + return true + } + }) + searchView.setOnQueryTextFocusChangeListener { _, hasFocus -> + handleFocusChange(hasFocus) + } + searchView.setOnCloseListener { + handleClose() + false + } + searchView.setOnSearchClickListener { + handleOpen() + } + } + + private fun handleTextChange(newText: String?) { + val event = Arguments.createMap() + event.putString("text", newText) + sendEvent("onChangeText", event) + } + + private fun handleFocusChange(hasFocus: Boolean) { + sendEvent(if (hasFocus) "onFocus" else "onBlur", null) + } + + private fun handleClose() { + sendEvent("onClose", null) + } + + private fun handleOpen() { + sendEvent("onOpen", null) + } + + private fun handleTextSubmit(newText: String?) { + val event = Arguments.createMap() + event.putString("text", newText) + sendEvent("onSearchButtonPress", event) + } + + private fun sendEvent(eventName: String, eventContent: WritableMap?) { + (context as ReactContext).getJSModule(RCTEventEmitter::class.java) + ?.receiveEvent(id, eventName, eventContent) + } + + enum class SearchBarAutoCapitalize { + NONE, WORDS, SENTENCES, CHARACTERS + } + + enum class SearchBarInputTypes { + TEXT { + override fun toAndroidInputType(capitalize: SearchBarAutoCapitalize) = + when (capitalize) { + SearchBarAutoCapitalize.NONE -> InputType.TYPE_CLASS_TEXT + SearchBarAutoCapitalize.WORDS -> InputType.TYPE_TEXT_FLAG_CAP_WORDS + SearchBarAutoCapitalize.SENTENCES -> InputType.TYPE_TEXT_FLAG_CAP_SENTENCES + SearchBarAutoCapitalize.CHARACTERS -> InputType.TYPE_TEXT_FLAG_CAP_CHARACTERS + } + }, + PHONE { + override fun toAndroidInputType(capitalize: SearchBarAutoCapitalize) = + InputType.TYPE_CLASS_PHONE + }, + NUMBER { + override fun toAndroidInputType(capitalize: SearchBarAutoCapitalize) = + InputType.TYPE_CLASS_NUMBER + }, + EMAIL { + override fun toAndroidInputType(capitalize: SearchBarAutoCapitalize) = + InputType.TYPE_TEXT_VARIATION_EMAIL_ADDRESS + }; + + abstract fun toAndroidInputType(capitalize: SearchBarAutoCapitalize): Int + } +} diff --git a/android/vendored/unversioned/react-native-screens/android/src/main/java/com/swmansion/rnscreens/SearchViewFormatter.kt b/android/vendored/unversioned/react-native-screens/android/src/main/java/com/swmansion/rnscreens/SearchViewFormatter.kt new file mode 100644 index 0000000000000..8289555aaf9b6 --- /dev/null +++ b/android/vendored/unversioned/react-native-screens/android/src/main/java/com/swmansion/rnscreens/SearchViewFormatter.kt @@ -0,0 +1,67 @@ +package com.swmansion.rnscreens + +import android.graphics.drawable.Drawable +import android.view.View +import android.widget.EditText +import android.widget.ImageView +import androidx.appcompat.R +import androidx.appcompat.widget.SearchView + +class SearchViewFormatter(var searchView: SearchView) { + private var mDefaultTextColor: Int? = null + private var mDefaultTintBackground: Drawable? = null + + private val searchEditText + get() = searchView.findViewById(R.id.search_src_text) as? EditText + private val searchTextPlate + get() = searchView.findViewById(R.id.search_plate) + private val searchIcon + get() = searchView.findViewById(R.id.search_button) + private val searchCloseIcon + get() = searchView.findViewById(R.id.search_close_btn) + + fun setTextColor(textColor: Int?) { + val currentDefaultTextColor = mDefaultTextColor + if (textColor != null) { + if (mDefaultTextColor == null) { + mDefaultTextColor = searchEditText?.textColors?.defaultColor + } + searchEditText?.setTextColor(textColor) + } else if (currentDefaultTextColor != null) { + searchEditText?.setTextColor(currentDefaultTextColor) + } + } + + fun setTintColor(tintColor: Int?) { + val currentDefaultTintColor = mDefaultTintBackground + if (tintColor != null) { + if (mDefaultTintBackground == null) { + mDefaultTintBackground = searchTextPlate.background + } + searchTextPlate.setBackgroundColor(tintColor) + } else if (currentDefaultTintColor != null) { + searchTextPlate.background = currentDefaultTintColor + } + } + + fun setHeaderIconColor(headerIconColor: Int?) { + headerIconColor?.let { + searchIcon.setColorFilter(it) + searchCloseIcon.setColorFilter(it) + } + } + + fun setHintTextColor(hintTextColor: Int?) { + hintTextColor?.let { + searchEditText?.setHintTextColor(it) + } + } + + fun setPlaceholder(placeholder: String, shouldShowHintSearchIcon: Boolean) { + if (shouldShowHintSearchIcon) { + searchView.queryHint = placeholder + } else { + searchEditText?.hint = placeholder + } + } +} diff --git a/android/vendored/unversioned/react-native-screens/android/src/main/java/com/swmansion/rnscreens/events/HeaderBackButtonClickedEvent.kt b/android/vendored/unversioned/react-native-screens/android/src/main/java/com/swmansion/rnscreens/events/HeaderBackButtonClickedEvent.kt new file mode 100644 index 0000000000000..1d1ffbaf8bb2d --- /dev/null +++ b/android/vendored/unversioned/react-native-screens/android/src/main/java/com/swmansion/rnscreens/events/HeaderBackButtonClickedEvent.kt @@ -0,0 +1,24 @@ +package com.swmansion.rnscreens.events + +import com.facebook.react.bridge.Arguments +import com.facebook.react.uimanager.events.Event +import com.facebook.react.uimanager.events.RCTEventEmitter + +class HeaderBackButtonClickedEvent(viewId: Int) : Event(viewId) { + override fun getEventName(): String { + return EVENT_NAME + } + + override fun getCoalescingKey(): Short { + // All events for a given view can be coalesced. + return 0 + } + + override fun dispatch(rctEventEmitter: RCTEventEmitter) { + rctEventEmitter.receiveEvent(viewTag, eventName, Arguments.createMap()) + } + + companion object { + const val EVENT_NAME = "topHeaderBackButtonClickedEvent" + } +} diff --git a/android/vendored/unversioned/react-native-screens/android/src/main/java/com/swmansion/rnscreens/events/ScreenAppearEvent.kt b/android/vendored/unversioned/react-native-screens/android/src/main/java/com/swmansion/rnscreens/events/ScreenAppearEvent.kt new file mode 100644 index 0000000000000..2c7ef2f7967c1 --- /dev/null +++ b/android/vendored/unversioned/react-native-screens/android/src/main/java/com/swmansion/rnscreens/events/ScreenAppearEvent.kt @@ -0,0 +1,20 @@ +package com.swmansion.rnscreens.events + +import com.facebook.react.bridge.Arguments +import com.facebook.react.uimanager.events.Event +import com.facebook.react.uimanager.events.RCTEventEmitter + +class ScreenAppearEvent(viewId: Int) : Event(viewId) { + override fun getEventName() = EVENT_NAME + + // All events for a given view can be coalesced. + override fun getCoalescingKey(): Short = 0 + + override fun dispatch(rctEventEmitter: RCTEventEmitter) { + rctEventEmitter.receiveEvent(viewTag, eventName, Arguments.createMap()) + } + + companion object { + const val EVENT_NAME = "topAppear" + } +} diff --git a/android/vendored/unversioned/react-native-screens/android/src/main/java/com/swmansion/rnscreens/events/ScreenDisappearEvent.kt b/android/vendored/unversioned/react-native-screens/android/src/main/java/com/swmansion/rnscreens/events/ScreenDisappearEvent.kt new file mode 100644 index 0000000000000..a53b3096f5206 --- /dev/null +++ b/android/vendored/unversioned/react-native-screens/android/src/main/java/com/swmansion/rnscreens/events/ScreenDisappearEvent.kt @@ -0,0 +1,20 @@ +package com.swmansion.rnscreens.events + +import com.facebook.react.bridge.Arguments +import com.facebook.react.uimanager.events.Event +import com.facebook.react.uimanager.events.RCTEventEmitter + +class ScreenDisappearEvent(viewId: Int) : Event(viewId) { + override fun getEventName() = EVENT_NAME + + // All events for a given view can be coalesced. + override fun getCoalescingKey(): Short = 0 + + override fun dispatch(rctEventEmitter: RCTEventEmitter) { + rctEventEmitter.receiveEvent(viewTag, eventName, Arguments.createMap()) + } + + companion object { + const val EVENT_NAME = "topDisappear" + } +} diff --git a/android/vendored/unversioned/react-native-screens/android/src/main/java/com/swmansion/rnscreens/events/ScreenDismissedEvent.kt b/android/vendored/unversioned/react-native-screens/android/src/main/java/com/swmansion/rnscreens/events/ScreenDismissedEvent.kt new file mode 100644 index 0000000000000..533c3e4718de6 --- /dev/null +++ b/android/vendored/unversioned/react-native-screens/android/src/main/java/com/swmansion/rnscreens/events/ScreenDismissedEvent.kt @@ -0,0 +1,23 @@ +package com.swmansion.rnscreens.events + +import com.facebook.react.bridge.Arguments +import com.facebook.react.uimanager.events.Event +import com.facebook.react.uimanager.events.RCTEventEmitter + +class ScreenDismissedEvent(viewId: Int) : Event(viewId) { + override fun getEventName() = EVENT_NAME + + // All events for a given view can be coalesced. + override fun getCoalescingKey(): Short = 0 + + override fun dispatch(rctEventEmitter: RCTEventEmitter) { + val args = Arguments.createMap() + // on Android we always dismiss one screen at a time + args.putInt("dismissCount", 1) + rctEventEmitter.receiveEvent(viewTag, eventName, args) + } + + companion object { + const val EVENT_NAME = "topDismissed" + } +} diff --git a/android/vendored/unversioned/react-native-screens/android/src/main/java/com/swmansion/rnscreens/events/ScreenTransitionProgressEvent.kt b/android/vendored/unversioned/react-native-screens/android/src/main/java/com/swmansion/rnscreens/events/ScreenTransitionProgressEvent.kt new file mode 100644 index 0000000000000..b047fccb525e8 --- /dev/null +++ b/android/vendored/unversioned/react-native-screens/android/src/main/java/com/swmansion/rnscreens/events/ScreenTransitionProgressEvent.kt @@ -0,0 +1,33 @@ +package com.swmansion.rnscreens.events + +import com.facebook.react.bridge.Arguments +import com.facebook.react.uimanager.events.Event +import com.facebook.react.uimanager.events.RCTEventEmitter + +class ScreenTransitionProgressEvent( + viewId: Int, + private val mProgress: Float, + private val mClosing: Boolean, + private val mGoingForward: Boolean, + private val mCoalescingKey: Short +) : Event(viewId) { + override fun getEventName(): String { + return EVENT_NAME + } + + override fun getCoalescingKey(): Short { + return mCoalescingKey + } + + override fun dispatch(rctEventEmitter: RCTEventEmitter) { + val map = Arguments.createMap() + map.putDouble("progress", mProgress.toDouble()) + map.putInt("closing", if (mClosing) 1 else 0) + map.putInt("goingForward", if (mGoingForward) 1 else 0) + rctEventEmitter.receiveEvent(viewTag, eventName, map) + } + + companion object { + const val EVENT_NAME = "topTransitionProgress" + } +} diff --git a/android/vendored/unversioned/react-native-screens/android/src/main/java/com/swmansion/rnscreens/events/ScreenWillAppearEvent.kt b/android/vendored/unversioned/react-native-screens/android/src/main/java/com/swmansion/rnscreens/events/ScreenWillAppearEvent.kt new file mode 100644 index 0000000000000..c29abc24148d4 --- /dev/null +++ b/android/vendored/unversioned/react-native-screens/android/src/main/java/com/swmansion/rnscreens/events/ScreenWillAppearEvent.kt @@ -0,0 +1,20 @@ +package com.swmansion.rnscreens.events + +import com.facebook.react.bridge.Arguments +import com.facebook.react.uimanager.events.Event +import com.facebook.react.uimanager.events.RCTEventEmitter + +class ScreenWillAppearEvent(viewId: Int) : Event(viewId) { + override fun getEventName() = EVENT_NAME + + // All events for a given view can be coalesced. + override fun getCoalescingKey(): Short = 0 + + override fun dispatch(rctEventEmitter: RCTEventEmitter) { + rctEventEmitter.receiveEvent(viewTag, eventName, Arguments.createMap()) + } + + companion object { + const val EVENT_NAME = "topWillAppear" + } +} diff --git a/android/vendored/unversioned/react-native-screens/android/src/main/java/com/swmansion/rnscreens/events/ScreenWillDisappearEvent.kt b/android/vendored/unversioned/react-native-screens/android/src/main/java/com/swmansion/rnscreens/events/ScreenWillDisappearEvent.kt new file mode 100644 index 0000000000000..4304a9da20fd7 --- /dev/null +++ b/android/vendored/unversioned/react-native-screens/android/src/main/java/com/swmansion/rnscreens/events/ScreenWillDisappearEvent.kt @@ -0,0 +1,20 @@ +package com.swmansion.rnscreens.events + +import com.facebook.react.bridge.Arguments +import com.facebook.react.uimanager.events.Event +import com.facebook.react.uimanager.events.RCTEventEmitter + +class ScreenWillDisappearEvent(viewId: Int) : Event(viewId) { + override fun getEventName() = EVENT_NAME + + // All events for a given view can be coalesced. + override fun getCoalescingKey(): Short = 0 + + override fun dispatch(rctEventEmitter: RCTEventEmitter) { + rctEventEmitter.receiveEvent(viewTag, eventName, Arguments.createMap()) + } + + companion object { + const val EVENT_NAME = "topWillDisappear" + } +} diff --git a/android/vendored/unversioned/react-native-screens/android/src/main/java/com/swmansion/rnscreens/events/StackFinishTransitioningEvent.kt b/android/vendored/unversioned/react-native-screens/android/src/main/java/com/swmansion/rnscreens/events/StackFinishTransitioningEvent.kt new file mode 100644 index 0000000000000..af1d6de0f6fdf --- /dev/null +++ b/android/vendored/unversioned/react-native-screens/android/src/main/java/com/swmansion/rnscreens/events/StackFinishTransitioningEvent.kt @@ -0,0 +1,20 @@ +package com.swmansion.rnscreens.events + +import com.facebook.react.bridge.Arguments +import com.facebook.react.uimanager.events.Event +import com.facebook.react.uimanager.events.RCTEventEmitter + +class StackFinishTransitioningEvent(viewId: Int) : Event(viewId) { + override fun getEventName() = EVENT_NAME + + // All events for a given view can be coalesced. + override fun getCoalescingKey(): Short = 0 + + override fun dispatch(rctEventEmitter: RCTEventEmitter) { + rctEventEmitter.receiveEvent(viewTag, eventName, Arguments.createMap()) + } + + companion object { + const val EVENT_NAME = "topFinishTransitioning" + } +} diff --git a/android/vendored/unversioned/react-native-screens/android/src/main/jni/CMakeLists.txt b/android/vendored/unversioned/react-native-screens/android/src/main/jni/CMakeLists.txt new file mode 100644 index 0000000000000..3b388b4d99482 --- /dev/null +++ b/android/vendored/unversioned/react-native-screens/android/src/main/jni/CMakeLists.txt @@ -0,0 +1,72 @@ +cmake_minimum_required(VERSION 3.13) +set(CMAKE_VERBOSE_MAKEFILE ON) + +set(LIB_LITERAL rnscreens) +set(LIB_TARGET_NAME react_codegen_${LIB_LITERAL}) + +set(LIB_ANDROID_DIR ${CMAKE_CURRENT_SOURCE_DIR}/../../..) +set(LIB_COMMON_DIR ${LIB_ANDROID_DIR}/../common/cpp) +set(LIB_ANDROID_GENERATED_JNI_DIR ${LIB_ANDROID_DIR}/build/generated/source/codegen/jni) +set(LIB_ANDROID_GENERATED_COMPONENTS_DIR ${LIB_ANDROID_GENERATED_JNI_DIR}/react/renderer/components/${LIB_LITERAL}) + +add_compile_options( + -fexceptions + -frtti + -std=c++17 + -Wall + -Wpedantic + -Wno-gnu-zero-variadic-macro-arguments +) + +file(GLOB LIB_CUSTOM_SRCS CONFIGURE_DEPENDS *.cpp ${LIB_COMMON_DIR}/react/renderer/components/${LIB_LITERAL}/*.cpp) +file(GLOB LIB_CODEGEN_SRCS CONFIGURE_DEPENDS ${LIB_ANDROID_GENERATED_COMPONENTS_DIR}/*.cpp) + +add_library( + ${LIB_TARGET_NAME} + SHARED + ${LIB_CUSTOM_SRCS} + ${LIB_CODEGEN_SRCS} +) + +target_include_directories( + ${LIB_TARGET_NAME} + PUBLIC + . + ${LIB_COMMON_DIR} + ${LIB_ANDROID_GENERATED_JNI_DIR} + ${LIB_ANDROID_GENERATED_COMPONENTS_DIR} +) + +target_link_libraries( + ${LIB_TARGET_NAME} + fbjni + folly_runtime + glog + jsi + react_codegen_rncore + react_debug + react_nativemodule_core + react_render_core + react_render_debug + react_render_graphics + react_render_mapbuffer + rrc_view + turbomodulejsijni + yoga +) + +target_compile_options( + ${LIB_TARGET_NAME} + PRIVATE + -DLOG_TAG=\"ReactNative\" + -fexceptions + -frtti + -std=c++17 + -Wall +) + +target_include_directories( + ${CMAKE_PROJECT_NAME} + PUBLIC + ${CMAKE_CURRENT_SOURCE_DIR} +) diff --git a/android/vendored/unversioned/react-native-screens/android/src/main/jni/rnscreens.cpp b/android/vendored/unversioned/react-native-screens/android/src/main/jni/rnscreens.cpp new file mode 100644 index 0000000000000..ce5c15084e2e8 --- /dev/null +++ b/android/vendored/unversioned/react-native-screens/android/src/main/jni/rnscreens.cpp @@ -0,0 +1,16 @@ +/** + * This code was generated by [react-native-codegen](https://www.npmjs.com/package/react-native-codegen). + * @generated by codegen project: GenerateModuleJniCpp.js + */ +#include "rnscreens.h" + +namespace facebook { +namespace react { + + +std::shared_ptr rnscreens_ModuleProvider(const std::string &moduleName, const JavaTurboModule::InitParams ¶ms) { + return nullptr; +} + +} // namespace react +} // namespace facebook diff --git a/android/vendored/unversioned/react-native-screens/android/src/main/jni/rnscreens.h b/android/vendored/unversioned/react-native-screens/android/src/main/jni/rnscreens.h new file mode 100644 index 0000000000000..2d755a0092ad7 --- /dev/null +++ b/android/vendored/unversioned/react-native-screens/android/src/main/jni/rnscreens.h @@ -0,0 +1,28 @@ +#pragma once + +#include +#include +#include + +/** + * Note this import and that it is not present in autogenerated header file + * under android/build/generated/source/codegen/jni/rnscreens.h + * + * It is added here to make our custom symbols visible in autogenerated file with + * code responsible for registering component descriptor providers. See that rncli.cpp, + * located under /android/app/build/generated/rncli/src/main/jni/rncli.cpp, + * includes autogenerated rnscreens.h header by default. We change this behaviour + * by appropriate include path configuration so that this header file gets included. + * + * See: https://github.com/software-mansion/react-native-screens/pull/1585 + */ +#include + +namespace facebook { +namespace react { + +JSI_EXPORT +std::shared_ptr rnscreens_ModuleProvider(const std::string &moduleName, const JavaTurboModule::InitParams ¶ms); + +} // namespace react +} // namespace facebook diff --git a/android/vendored/unversioned/react-native-screens/android/src/main/res/anim/rns_default_enter_in.xml b/android/vendored/unversioned/react-native-screens/android/src/main/res/anim/rns_default_enter_in.xml new file mode 100644 index 0000000000000..4398c7efc09a2 --- /dev/null +++ b/android/vendored/unversioned/react-native-screens/android/src/main/res/anim/rns_default_enter_in.xml @@ -0,0 +1,18 @@ + + + + + diff --git a/android/vendored/unversioned/react-native-screens/android/src/main/res/anim/rns_default_enter_out.xml b/android/vendored/unversioned/react-native-screens/android/src/main/res/anim/rns_default_enter_out.xml new file mode 100644 index 0000000000000..84c91759fe65f --- /dev/null +++ b/android/vendored/unversioned/react-native-screens/android/src/main/res/anim/rns_default_enter_out.xml @@ -0,0 +1,19 @@ + + + + + + diff --git a/android/vendored/unversioned/react-native-screens/android/src/main/res/anim/rns_default_exit_in.xml b/android/vendored/unversioned/react-native-screens/android/src/main/res/anim/rns_default_exit_in.xml new file mode 100644 index 0000000000000..6d6fa02acd19e --- /dev/null +++ b/android/vendored/unversioned/react-native-screens/android/src/main/res/anim/rns_default_exit_in.xml @@ -0,0 +1,17 @@ + + + + + diff --git a/android/vendored/unversioned/react-native-screens/android/src/main/res/anim/rns_default_exit_out.xml b/android/vendored/unversioned/react-native-screens/android/src/main/res/anim/rns_default_exit_out.xml new file mode 100644 index 0000000000000..b20a184f8baeb --- /dev/null +++ b/android/vendored/unversioned/react-native-screens/android/src/main/res/anim/rns_default_exit_out.xml @@ -0,0 +1,18 @@ + + + + + diff --git a/android/vendored/unversioned/react-native-screens/android/src/main/res/anim/rns_fade_from_bottom.xml b/android/vendored/unversioned/react-native-screens/android/src/main/res/anim/rns_fade_from_bottom.xml new file mode 100644 index 0000000000000..c7a8abc07570e --- /dev/null +++ b/android/vendored/unversioned/react-native-screens/android/src/main/res/anim/rns_fade_from_bottom.xml @@ -0,0 +1,14 @@ + + + + + + + diff --git a/android/vendored/unversioned/react-native-screens/android/src/main/res/anim/rns_fade_in.xml b/android/vendored/unversioned/react-native-screens/android/src/main/res/anim/rns_fade_in.xml new file mode 100644 index 0000000000000..c78ea619df636 --- /dev/null +++ b/android/vendored/unversioned/react-native-screens/android/src/main/res/anim/rns_fade_in.xml @@ -0,0 +1,7 @@ + + + diff --git a/android/vendored/unversioned/react-native-screens/android/src/main/res/anim/rns_fade_out.xml b/android/vendored/unversioned/react-native-screens/android/src/main/res/anim/rns_fade_out.xml new file mode 100644 index 0000000000000..334e63f34ebed --- /dev/null +++ b/android/vendored/unversioned/react-native-screens/android/src/main/res/anim/rns_fade_out.xml @@ -0,0 +1,7 @@ + + + diff --git a/android/vendored/unversioned/react-native-screens/android/src/main/res/anim/rns_fade_to_bottom.xml b/android/vendored/unversioned/react-native-screens/android/src/main/res/anim/rns_fade_to_bottom.xml new file mode 100644 index 0000000000000..23345211ffcf9 --- /dev/null +++ b/android/vendored/unversioned/react-native-screens/android/src/main/res/anim/rns_fade_to_bottom.xml @@ -0,0 +1,15 @@ + + + + + + + diff --git a/android/vendored/unversioned/react-native-screens/android/src/main/res/anim/rns_no_animation_20.xml b/android/vendored/unversioned/react-native-screens/android/src/main/res/anim/rns_no_animation_20.xml new file mode 100644 index 0000000000000..5cc0d2385b1e2 --- /dev/null +++ b/android/vendored/unversioned/react-native-screens/android/src/main/res/anim/rns_no_animation_20.xml @@ -0,0 +1,6 @@ + + diff --git a/android/vendored/unversioned/react-native-screens/android/src/main/res/anim/rns_no_animation_250.xml b/android/vendored/unversioned/react-native-screens/android/src/main/res/anim/rns_no_animation_250.xml new file mode 100644 index 0000000000000..bd082a3e18e91 --- /dev/null +++ b/android/vendored/unversioned/react-native-screens/android/src/main/res/anim/rns_no_animation_250.xml @@ -0,0 +1,7 @@ + + + diff --git a/android/vendored/unversioned/react-native-screens/android/src/main/res/anim/rns_no_animation_350.xml b/android/vendored/unversioned/react-native-screens/android/src/main/res/anim/rns_no_animation_350.xml new file mode 100644 index 0000000000000..003dcd1f17d3f --- /dev/null +++ b/android/vendored/unversioned/react-native-screens/android/src/main/res/anim/rns_no_animation_350.xml @@ -0,0 +1,6 @@ + + diff --git a/android/vendored/unversioned/react-native-screens/android/src/main/res/anim/rns_no_animation_medium.xml b/android/vendored/unversioned/react-native-screens/android/src/main/res/anim/rns_no_animation_medium.xml new file mode 100644 index 0000000000000..560eda670d2b8 --- /dev/null +++ b/android/vendored/unversioned/react-native-screens/android/src/main/res/anim/rns_no_animation_medium.xml @@ -0,0 +1,7 @@ + + + diff --git a/android/vendored/unversioned/react-native-screens/android/src/main/res/anim/rns_slide_in_from_bottom.xml b/android/vendored/unversioned/react-native-screens/android/src/main/res/anim/rns_slide_in_from_bottom.xml new file mode 100644 index 0000000000000..052fe150edea6 --- /dev/null +++ b/android/vendored/unversioned/react-native-screens/android/src/main/res/anim/rns_slide_in_from_bottom.xml @@ -0,0 +1,7 @@ + + diff --git a/android/vendored/unversioned/react-native-screens/android/src/main/res/anim/rns_slide_in_from_left.xml b/android/vendored/unversioned/react-native-screens/android/src/main/res/anim/rns_slide_in_from_left.xml new file mode 100644 index 0000000000000..939110f5437ed --- /dev/null +++ b/android/vendored/unversioned/react-native-screens/android/src/main/res/anim/rns_slide_in_from_left.xml @@ -0,0 +1,5 @@ + + diff --git a/android/vendored/unversioned/react-native-screens/android/src/main/res/anim/rns_slide_in_from_right.xml b/android/vendored/unversioned/react-native-screens/android/src/main/res/anim/rns_slide_in_from_right.xml new file mode 100644 index 0000000000000..428eb9b71862e --- /dev/null +++ b/android/vendored/unversioned/react-native-screens/android/src/main/res/anim/rns_slide_in_from_right.xml @@ -0,0 +1,5 @@ + + diff --git a/android/vendored/unversioned/react-native-screens/android/src/main/res/anim/rns_slide_out_to_bottom.xml b/android/vendored/unversioned/react-native-screens/android/src/main/res/anim/rns_slide_out_to_bottom.xml new file mode 100644 index 0000000000000..6b75d2562fe62 --- /dev/null +++ b/android/vendored/unversioned/react-native-screens/android/src/main/res/anim/rns_slide_out_to_bottom.xml @@ -0,0 +1,7 @@ + + diff --git a/android/vendored/unversioned/react-native-screens/android/src/main/res/anim/rns_slide_out_to_left.xml b/android/vendored/unversioned/react-native-screens/android/src/main/res/anim/rns_slide_out_to_left.xml new file mode 100644 index 0000000000000..400a202c3bd6d --- /dev/null +++ b/android/vendored/unversioned/react-native-screens/android/src/main/res/anim/rns_slide_out_to_left.xml @@ -0,0 +1,5 @@ + + diff --git a/android/vendored/unversioned/react-native-screens/android/src/main/res/anim/rns_slide_out_to_right.xml b/android/vendored/unversioned/react-native-screens/android/src/main/res/anim/rns_slide_out_to_right.xml new file mode 100644 index 0000000000000..a00332bcdb171 --- /dev/null +++ b/android/vendored/unversioned/react-native-screens/android/src/main/res/anim/rns_slide_out_to_right.xml @@ -0,0 +1,5 @@ + + diff --git a/android/expoview/src/main/java/com/facebook/react/viewmanagers/RNSScreenManagerDelegate.java b/android/vendored/unversioned/react-native-screens/android/src/paper/java/com/facebook/react/viewmanagers/RNSScreenManagerDelegate.java similarity index 100% rename from android/expoview/src/main/java/com/facebook/react/viewmanagers/RNSScreenManagerDelegate.java rename to android/vendored/unversioned/react-native-screens/android/src/paper/java/com/facebook/react/viewmanagers/RNSScreenManagerDelegate.java diff --git a/android/expoview/src/main/java/com/facebook/react/viewmanagers/RNSScreenManagerInterface.java b/android/vendored/unversioned/react-native-screens/android/src/paper/java/com/facebook/react/viewmanagers/RNSScreenManagerInterface.java similarity index 100% rename from android/expoview/src/main/java/com/facebook/react/viewmanagers/RNSScreenManagerInterface.java rename to android/vendored/unversioned/react-native-screens/android/src/paper/java/com/facebook/react/viewmanagers/RNSScreenManagerInterface.java diff --git a/android/expoview/src/main/java/com/facebook/react/viewmanagers/RNSScreenStackHeaderConfigManagerDelegate.java b/android/vendored/unversioned/react-native-screens/android/src/paper/java/com/facebook/react/viewmanagers/RNSScreenStackHeaderConfigManagerDelegate.java similarity index 100% rename from android/expoview/src/main/java/com/facebook/react/viewmanagers/RNSScreenStackHeaderConfigManagerDelegate.java rename to android/vendored/unversioned/react-native-screens/android/src/paper/java/com/facebook/react/viewmanagers/RNSScreenStackHeaderConfigManagerDelegate.java diff --git a/android/expoview/src/main/java/com/facebook/react/viewmanagers/RNSScreenStackHeaderConfigManagerInterface.java b/android/vendored/unversioned/react-native-screens/android/src/paper/java/com/facebook/react/viewmanagers/RNSScreenStackHeaderConfigManagerInterface.java similarity index 100% rename from android/expoview/src/main/java/com/facebook/react/viewmanagers/RNSScreenStackHeaderConfigManagerInterface.java rename to android/vendored/unversioned/react-native-screens/android/src/paper/java/com/facebook/react/viewmanagers/RNSScreenStackHeaderConfigManagerInterface.java diff --git a/android/expoview/src/main/java/com/facebook/react/viewmanagers/RNSScreenStackHeaderSubviewManagerDelegate.java b/android/vendored/unversioned/react-native-screens/android/src/paper/java/com/facebook/react/viewmanagers/RNSScreenStackHeaderSubviewManagerDelegate.java similarity index 100% rename from android/expoview/src/main/java/com/facebook/react/viewmanagers/RNSScreenStackHeaderSubviewManagerDelegate.java rename to android/vendored/unversioned/react-native-screens/android/src/paper/java/com/facebook/react/viewmanagers/RNSScreenStackHeaderSubviewManagerDelegate.java diff --git a/android/expoview/src/main/java/com/facebook/react/viewmanagers/RNSScreenStackHeaderSubviewManagerInterface.java b/android/vendored/unversioned/react-native-screens/android/src/paper/java/com/facebook/react/viewmanagers/RNSScreenStackHeaderSubviewManagerInterface.java similarity index 100% rename from android/expoview/src/main/java/com/facebook/react/viewmanagers/RNSScreenStackHeaderSubviewManagerInterface.java rename to android/vendored/unversioned/react-native-screens/android/src/paper/java/com/facebook/react/viewmanagers/RNSScreenStackHeaderSubviewManagerInterface.java diff --git a/android/expoview/src/main/java/com/facebook/react/viewmanagers/RNSScreenStackManagerDelegate.java b/android/vendored/unversioned/react-native-screens/android/src/paper/java/com/facebook/react/viewmanagers/RNSScreenStackManagerDelegate.java similarity index 100% rename from android/expoview/src/main/java/com/facebook/react/viewmanagers/RNSScreenStackManagerDelegate.java rename to android/vendored/unversioned/react-native-screens/android/src/paper/java/com/facebook/react/viewmanagers/RNSScreenStackManagerDelegate.java diff --git a/android/expoview/src/main/java/com/facebook/react/viewmanagers/RNSScreenStackManagerInterface.java b/android/vendored/unversioned/react-native-screens/android/src/paper/java/com/facebook/react/viewmanagers/RNSScreenStackManagerInterface.java similarity index 100% rename from android/expoview/src/main/java/com/facebook/react/viewmanagers/RNSScreenStackManagerInterface.java rename to android/vendored/unversioned/react-native-screens/android/src/paper/java/com/facebook/react/viewmanagers/RNSScreenStackManagerInterface.java diff --git a/android/vendored/unversioned/react-native-screens/android/src/paper/java/com/swmansion/rnscreens/FabricEnabledViewGroup.kt b/android/vendored/unversioned/react-native-screens/android/src/paper/java/com/swmansion/rnscreens/FabricEnabledViewGroup.kt new file mode 100644 index 0000000000000..106638858dc9a --- /dev/null +++ b/android/vendored/unversioned/react-native-screens/android/src/paper/java/com/swmansion/rnscreens/FabricEnabledViewGroup.kt @@ -0,0 +1,10 @@ +package com.swmansion.rnscreens + +import android.view.ViewGroup +import com.facebook.react.bridge.ReactContext + +abstract class FabricEnabledViewGroup constructor(context: ReactContext?) : ViewGroup(context) { + protected fun updateScreenSizeFabric(width: Int, height: Int) { + // do nothing + } +} diff --git a/apps/bare-expo/package.json b/apps/bare-expo/package.json index 9f93a5710e0af..85bec50ab3f8b 100644 --- a/apps/bare-expo/package.json +++ b/apps/bare-expo/package.json @@ -115,7 +115,7 @@ "react-native-gesture-handler": "~2.7.0", "react-native-reanimated": "~2.10.0", "react-native-safe-area-context": "4.3.1", - "react-native-screens": "~3.15.0", + "react-native-screens": "~3.17.0", "react-native-shared-element": "0.8.4", "react-native-svg": "12.3.0", "react-native-view-shot": "3.3.0", diff --git a/apps/bare-sandbox/package.json b/apps/bare-sandbox/package.json index d618fdbabc878..9c27927240322 100644 --- a/apps/bare-sandbox/package.json +++ b/apps/bare-sandbox/package.json @@ -20,7 +20,7 @@ "react-native": "0.70.2", "react-native-gesture-handler": "~2.7.0", "react-native-reanimated": "~2.10.0", - "react-native-screens": "~3.15.0", + "react-native-screens": "~3.17.0", "react-native-web": "~0.18.9" }, "devDependencies": { diff --git a/apps/native-component-list/package.json b/apps/native-component-list/package.json index 4cbdf3c1d95fa..82b3683c4d333 100644 --- a/apps/native-component-list/package.json +++ b/apps/native-component-list/package.json @@ -152,7 +152,7 @@ "react-native-reanimated": "~2.10.0", "react-native-safe-area-context": "4.3.1", "react-native-safe-area-view": "^0.14.8", - "react-native-screens": "~3.15.0", + "react-native-screens": "~3.17.0", "react-native-shared-element": "0.8.4", "react-native-svg": "12.3.0", "react-native-view-shot": "3.3.0", diff --git a/home/package.json b/home/package.json index c5cfd11667a5e..89be7e966ab5e 100644 --- a/home/package.json +++ b/home/package.json @@ -62,7 +62,7 @@ "react-native-paper": "^4.0.1", "react-native-reanimated": "~2.10.0", "react-native-safe-area-context": "4.3.1", - "react-native-screens": "~3.15.0", + "react-native-screens": "~3.17.0", "react-redux": "^7.2.0", "react-string-replace": "^0.4.4", "reanimated-bottom-sheet": "^1.0.0-alpha.18", diff --git a/packages/expo-stories/package.json b/packages/expo-stories/package.json index 7796b72d5a03b..4d164b1bb1d40 100644 --- a/packages/expo-stories/package.json +++ b/packages/expo-stories/package.json @@ -32,7 +32,7 @@ "glob": "^7.1.7", "react-native-gesture-handler": "~2.7.0", "react-native-safe-area-context": "4.3.1", - "react-native-screens": "~3.15.0", + "react-native-screens": "~3.17.0", "react-native-svg": "12.3.0", "sane": "^5.0.1" } diff --git a/packages/expo/bundledNativeModules.json b/packages/expo/bundledNativeModules.json index 2247fa0ff7c76..0e15ba08825a8 100644 --- a/packages/expo/bundledNativeModules.json +++ b/packages/expo/bundledNativeModules.json @@ -96,7 +96,7 @@ "react-native-pager-view": "5.4.24", "react-native-reanimated": "~2.10.0", "react-native-safe-area-context": "4.3.1", - "react-native-screens": "~3.15.0", + "react-native-screens": "~3.17.0", "react-native-shared-element": "0.8.4", "react-native-svg": "12.3.0", "react-native-view-shot": "3.3.0", diff --git a/tools/src/vendoring/config/expoGoConfig.ts b/tools/src/vendoring/config/expoGoConfig.ts index f745e46e1f096..90f0993513000 100644 --- a/tools/src/vendoring/config/expoGoConfig.ts +++ b/tools/src/vendoring/config/expoGoConfig.ts @@ -109,22 +109,7 @@ const config: VendoringTargetConfig = { source: 'https://github.com/software-mansion/react-native-screens.git', semverPrefix: '~', ios: {}, - android: { - transforms: { - content: [ - { - paths: 'ScreenStack.kt', - find: /(?=^class ScreenStack\()/m, - replaceWith: `import host.exp.expoview.R\n\n`, - }, - { - paths: 'ScreenStackHeaderConfig.kt', - find: /(?=^class ScreenStackHeaderConfig\()/m, - replaceWith: `import host.exp.expoview.BuildConfig\nimport host.exp.expoview.R\n\n`, - }, - ], - }, - }, + android: {}, }, 'amazon-cognito-identity-js': { source: 'https://github.com/aws-amplify/amplify-js.git', From c1126611b6387cff692c933f4f9501a04eeb5629 Mon Sep 17 00:00:00 2001 From: Tomasz Sapeta Date: Tue, 4 Oct 2022 10:08:30 +0200 Subject: [PATCH 3/8] Update vendored code on iOS --- ios/Podfile.lock | 4 +-- .../RNScreens.podspec.json | 4 +-- .../ios/RNSFullWindowOverlay.mm | 2 +- .../react-native-screens/ios/RNSScreen.h | 1 + .../react-native-screens/ios/RNSScreen.mm | 30 +++++++++++++++---- .../ios/RNSScreenContainer.mm | 2 +- .../ios/RNSScreenNavigationContainer.mm | 2 +- .../ios/RNSScreenStack.mm | 6 ++-- .../ios/RNSScreenStackHeaderConfig.mm | 11 ++++--- .../ios/RNSScreenStackHeaderSubview.mm | 2 +- .../react-native-screens/ios/RNSSearchBar.mm | 2 +- 11 files changed, 45 insertions(+), 21 deletions(-) diff --git a/ios/Podfile.lock b/ios/Podfile.lock index 8f45fdfc8a706..dd38fc6d7b46f 100644 --- a/ios/Podfile.lock +++ b/ios/Podfile.lock @@ -2043,7 +2043,7 @@ PODS: - React-RCTText - ReactCommon/turbomodule/core - Yoga - - RNScreens (3.15.0): + - RNScreens (3.17.0): - React-Core - React-RCTImage - Stripe (22.7.1): @@ -3511,7 +3511,7 @@ SPEC CHECKSUMS: RNFlashList: 5116f2de2f543f01bfc30b22d5942d5af84b43df RNGestureHandler: 7673697e7c0e9391adefae4faa087442bc04af33 RNReanimated: 5c8c17e26787fd8984cd5accdc70fef2ca70aafd - RNScreens: 4a1af06327774490d97342c00aee0c2bafb497b7 + RNScreens: 0df01424e9e0ed7827200d6ed1087ddd06c493f9 Stripe: fb29a476e4866fec4ef22fb76207363dd32795aa stripe-react-native: 5663bf9de94bff6b3d92c16d32784433b5e94cf7 StripeApplePay: 09955cdf3f49b367af2feadd9c5b3bddb35446c0 diff --git a/ios/vendored/unversioned/react-native-screens/RNScreens.podspec.json b/ios/vendored/unversioned/react-native-screens/RNScreens.podspec.json index 6bc8166758c43..77188dae27cfd 100644 --- a/ios/vendored/unversioned/react-native-screens/RNScreens.podspec.json +++ b/ios/vendored/unversioned/react-native-screens/RNScreens.podspec.json @@ -1,6 +1,6 @@ { "name": "RNScreens", - "version": "3.15.0", + "version": "3.17.0", "summary": "Native navigation primitives for your React Native app.", "description": "RNScreens - first incomplete navigation solution for your React Native app", "homepage": "https://github.com/software-mansion/react-native-screens", @@ -14,7 +14,7 @@ }, "source": { "git": "https://github.com/software-mansion/react-native-screens.git", - "tag": "3.15.0" + "tag": "3.17.0" }, "source_files": "ios/**/*.{h,m,mm}", "requires_arc": true, diff --git a/ios/vendored/unversioned/react-native-screens/ios/RNSFullWindowOverlay.mm b/ios/vendored/unversioned/react-native-screens/ios/RNSFullWindowOverlay.mm index dff0b2fd9e437..e3fedbb24d927 100644 --- a/ios/vendored/unversioned/react-native-screens/ios/RNSFullWindowOverlay.mm +++ b/ios/vendored/unversioned/react-native-screens/ios/RNSFullWindowOverlay.mm @@ -4,11 +4,11 @@ #ifdef RN_FABRIC_ENABLED #import +#import #import #import #import #import -#import "RCTFabricComponentsPlugins.h" #else #import #endif // RN_FABRIC_ENABLED diff --git a/ios/vendored/unversioned/react-native-screens/ios/RNSScreen.h b/ios/vendored/unversioned/react-native-screens/ios/RNSScreen.h index 79a012592c758..a2195b531f4c5 100644 --- a/ios/vendored/unversioned/react-native-screens/ios/RNSScreen.h +++ b/ios/vendored/unversioned/react-native-screens/ios/RNSScreen.h @@ -103,6 +103,7 @@ NS_ASSUME_NONNULL_BEGIN #endif - (void)notifyTransitionProgress:(double)progress closing:(BOOL)closing goingForward:(BOOL)goingForward; +- (BOOL)isModal; @end diff --git a/ios/vendored/unversioned/react-native-screens/ios/RNSScreen.mm b/ios/vendored/unversioned/react-native-screens/ios/RNSScreen.mm index 066ba3ac81e51..337b32512790f 100644 --- a/ios/vendored/unversioned/react-native-screens/ios/RNSScreen.mm +++ b/ios/vendored/unversioned/react-native-screens/ios/RNSScreen.mm @@ -6,13 +6,13 @@ #ifdef RN_FABRIC_ENABLED #import +#import #import #import #import #import #import #import -#import "RCTFabricComponentsPlugins.h" #import "RNSConvert.h" #import "RNSScreenViewEvent.h" #else @@ -508,6 +508,11 @@ - (void)presentationControllerDidDismiss:(UIPresentationController *)presentatio } } +- (BOOL)isModal +{ + return self.stackPresentation != RNSScreenStackPresentationPush; +} + #pragma mark - Fabric specific #ifdef RN_FABRIC_ENABLED @@ -545,6 +550,17 @@ - (void)prepareForRecycle _dismissed = NO; _state.reset(); _touchHandler = nil; + + // We set this prop to default value here to workaround view-recycling. + // Let's assume the view has had _stackPresentation == set + // before below line was executed. Then, when instantiated again (with the same modal presentation) + // updateProps:oldProps: method would be called and setter for stack presentation would not be called. + // This is crucial as in that setter we register `self.controller` as a delegate + // (UIAdaptivePresentationControllerDelegate) to presentation controller and this leads to buggy modal behaviour as we + // rely on UIAdaptivePresentationControllerDelegate callbacks. Restoring the default value and then comparing against + // it in updateProps:oldProps: allows for setter to be called, however if there was some additional logic to execute + // when stackPresentation is set to "push" the setter would not be triggered. + _stackPresentation = RNSScreenStackPresentationPush; } - (void)updateProps:(facebook::react::Props::Shared const &)props @@ -598,9 +614,12 @@ - (void)updateProps:(facebook::react::Props::Shared const &)props } #endif - if (newScreenProps.stackPresentation != oldScreenProps.stackPresentation) { - [self - setStackPresentation:[RNSConvert RNSScreenStackPresentationFromCppEquivalent:newScreenProps.stackPresentation]]; + // Notice that we compare against _stackPresentation, not oldScreenProps.stackPresentation. + // See comment in prepareForRecycle method for explanation. + RNSScreenStackPresentation newStackPresentation = + [RNSConvert RNSScreenStackPresentationFromCppEquivalent:newScreenProps.stackPresentation]; + if (newStackPresentation != _stackPresentation) { + [self setStackPresentation:newStackPresentation]; } if (newScreenProps.stackAnimation != oldScreenProps.stackAnimation) { @@ -739,7 +758,8 @@ - (void)viewWillAppear:(BOOL)animated - (void)viewWillDisappear:(BOOL)animated { [super viewWillDisappear:animated]; - if (!self.transitionCoordinator.isInteractive) { + // self.navigationController might be null when we are dismissing a modal + if (!self.transitionCoordinator.isInteractive && self.navigationController != nil) { // user might have long pressed ios 14 back button item, // so he can go back more than one screen and we need to dismiss more screens in JS stack then. // We check it by calculating the difference between the index of currently displayed screen diff --git a/ios/vendored/unversioned/react-native-screens/ios/RNSScreenContainer.mm b/ios/vendored/unversioned/react-native-screens/ios/RNSScreenContainer.mm index fe83a272545a8..baea98c1a1f77 100644 --- a/ios/vendored/unversioned/react-native-screens/ios/RNSScreenContainer.mm +++ b/ios/vendored/unversioned/react-native-screens/ios/RNSScreenContainer.mm @@ -3,9 +3,9 @@ #ifdef RN_FABRIC_ENABLED #import +#import #import #import -#import "RCTFabricComponentsPlugins.h" #endif @implementation RNScreensViewController diff --git a/ios/vendored/unversioned/react-native-screens/ios/RNSScreenNavigationContainer.mm b/ios/vendored/unversioned/react-native-screens/ios/RNSScreenNavigationContainer.mm index c328064961554..b32546c9bb38f 100644 --- a/ios/vendored/unversioned/react-native-screens/ios/RNSScreenNavigationContainer.mm +++ b/ios/vendored/unversioned/react-native-screens/ios/RNSScreenNavigationContainer.mm @@ -3,9 +3,9 @@ #import "RNSScreenContainer.h" #ifdef RN_FABRIC_ENABLED +#import #import #import -#import "RCTFabricComponentsPlugins.h" #endif @implementation RNScreensContainerNavigationController diff --git a/ios/vendored/unversioned/react-native-screens/ios/RNSScreenStack.mm b/ios/vendored/unversioned/react-native-screens/ios/RNSScreenStack.mm index 81322b1e4aae1..8744a64f6a811 100644 --- a/ios/vendored/unversioned/react-native-screens/ios/RNSScreenStack.mm +++ b/ios/vendored/unversioned/react-native-screens/ios/RNSScreenStack.mm @@ -7,7 +7,7 @@ #import #import -#import "RCTFabricComponentsPlugins.h" +#import #else #import @@ -823,7 +823,7 @@ - (BOOL)gestureRecognizer:(UIGestureRecognizer *)gestureRecognizer shouldReceive RNSScreenView *topScreen = _reactSubviews.lastObject; if (![topScreen isKindOfClass:[RNSScreenView class]] || !topScreen.gestureEnabled || - _controller.viewControllers.count < 2) { + _controller.viewControllers.count < 2 || [topScreen isModal]) { return NO; } @@ -969,7 +969,7 @@ - (void)takeSnapshot - (void)mountingTransactionWillMount:(facebook::react::MountingTransaction const &)transaction withSurfaceTelemetry:(facebook::react::SurfaceTelemetry const &)surfaceTelemetry { - for (auto mutation : transaction.getMutations()) { + for (auto &mutation : transaction.getMutations()) { if (mutation.type == facebook::react::ShadowViewMutation::Type::Remove && mutation.parentShadowView.componentName != nil && strcmp(mutation.parentShadowView.componentName, "RNSScreenStack") == 0) { diff --git a/ios/vendored/unversioned/react-native-screens/ios/RNSScreenStackHeaderConfig.mm b/ios/vendored/unversioned/react-native-screens/ios/RNSScreenStackHeaderConfig.mm index 01f1480675d5f..7c5fa1b01a1c9 100644 --- a/ios/vendored/unversioned/react-native-screens/ios/RNSScreenStackHeaderConfig.mm +++ b/ios/vendored/unversioned/react-native-screens/ios/RNSScreenStackHeaderConfig.mm @@ -1,11 +1,11 @@ #ifdef RN_FABRIC_ENABLED #import +#import #import #import #import #import #import -#import "RCTFabricComponentsPlugins.h" #else #import #import @@ -118,7 +118,7 @@ - (void)updateViewControllerIfNeeded } // we want updates sent to the VC below modal too since it is also visible - BOOL isPresentingVC = vc.presentedViewController == nextVC; + BOOL isPresentingVC = nextVC != nil && vc.presentedViewController == nextVC; BOOL isInFullScreenModal = nav == nil && _screenView.stackPresentation == RNSScreenStackPresentationFullScreenModal; // if nav is nil, it means we can be in a fullScreen modal, so there is no nextVC, but we still want to update @@ -459,7 +459,6 @@ + (void)updateViewController:(UIViewController *)vc return; } - navitem.title = config.title; #if !TARGET_OS_TV if (config.backTitle != nil || config.backTitleFontFamily || config.backTitleFontSize || config.disableBackButtonMenu) { @@ -580,13 +579,17 @@ + (void)updateViewController:(UIViewController *)vc } case RNSScreenStackHeaderSubviewTypeBackButton: { #ifdef RN_FABRIC_ENABLED - RCTLogWarn(@"Back button subivew is not yet Fabric compatible in react-native-screens"); + RCTLogWarn(@"Back button subview is not yet Fabric compatible in react-native-screens"); #endif break; } } } + // This assignment should be done after `navitem.titleView = ...` assignment (iOS 16.0 bug). + // See: https://github.com/software-mansion/react-native-screens/issues/1570 (comments) + navitem.title = config.title; + if (animated && vc.transitionCoordinator != nil && vc.transitionCoordinator.presentationStyle == UIModalPresentationNone && !wasHidden) { // when there is an ongoing transition we may need to update navbar setting in animation block diff --git a/ios/vendored/unversioned/react-native-screens/ios/RNSScreenStackHeaderSubview.mm b/ios/vendored/unversioned/react-native-screens/ios/RNSScreenStackHeaderSubview.mm index 5d0ead1f483a6..70a430b04062d 100644 --- a/ios/vendored/unversioned/react-native-screens/ios/RNSScreenStackHeaderSubview.mm +++ b/ios/vendored/unversioned/react-native-screens/ios/RNSScreenStackHeaderSubview.mm @@ -7,7 +7,7 @@ #import #import -#import "RCTFabricComponentsPlugins.h" +#import #endif @implementation RNSScreenStackHeaderSubview diff --git a/ios/vendored/unversioned/react-native-screens/ios/RNSSearchBar.mm b/ios/vendored/unversioned/react-native-screens/ios/RNSSearchBar.mm index 12b7dc2c272d1..ebd322406d628 100644 --- a/ios/vendored/unversioned/react-native-screens/ios/RNSSearchBar.mm +++ b/ios/vendored/unversioned/react-native-screens/ios/RNSSearchBar.mm @@ -8,11 +8,11 @@ #ifdef RN_FABRIC_ENABLED #import +#import #import #import #import #import -#import "RCTFabricComponentsPlugins.h" #import "RNSConvert.h" #endif From 42d0db5a6a2ff75625c44f484e7ae20c5b1e6ce6 Mon Sep 17 00:00:00 2001 From: Tomasz Sapeta Date: Tue, 4 Oct 2022 14:42:38 +0200 Subject: [PATCH 4/8] Update to 3.18.0 --- apps/bare-expo/package.json | 2 +- apps/bare-sandbox/package.json | 2 +- apps/native-component-list/package.json | 2 +- home/package.json | 2 +- ios/Podfile.lock | 4 ++-- .../react-native-screens/RNScreens.podspec.json | 4 ++-- packages/expo-stories/package.json | 2 +- packages/expo/bundledNativeModules.json | 2 +- yarn.lock | 8 ++++---- 9 files changed, 14 insertions(+), 14 deletions(-) diff --git a/apps/bare-expo/package.json b/apps/bare-expo/package.json index 85bec50ab3f8b..d6ca9ba2a7898 100644 --- a/apps/bare-expo/package.json +++ b/apps/bare-expo/package.json @@ -115,7 +115,7 @@ "react-native-gesture-handler": "~2.7.0", "react-native-reanimated": "~2.10.0", "react-native-safe-area-context": "4.3.1", - "react-native-screens": "~3.17.0", + "react-native-screens": "~3.18.0", "react-native-shared-element": "0.8.4", "react-native-svg": "12.3.0", "react-native-view-shot": "3.3.0", diff --git a/apps/bare-sandbox/package.json b/apps/bare-sandbox/package.json index 9c27927240322..c6c8710fef1ca 100644 --- a/apps/bare-sandbox/package.json +++ b/apps/bare-sandbox/package.json @@ -20,7 +20,7 @@ "react-native": "0.70.2", "react-native-gesture-handler": "~2.7.0", "react-native-reanimated": "~2.10.0", - "react-native-screens": "~3.17.0", + "react-native-screens": "~3.18.0", "react-native-web": "~0.18.9" }, "devDependencies": { diff --git a/apps/native-component-list/package.json b/apps/native-component-list/package.json index 82b3683c4d333..b719f728955cf 100644 --- a/apps/native-component-list/package.json +++ b/apps/native-component-list/package.json @@ -152,7 +152,7 @@ "react-native-reanimated": "~2.10.0", "react-native-safe-area-context": "4.3.1", "react-native-safe-area-view": "^0.14.8", - "react-native-screens": "~3.17.0", + "react-native-screens": "~3.18.0", "react-native-shared-element": "0.8.4", "react-native-svg": "12.3.0", "react-native-view-shot": "3.3.0", diff --git a/home/package.json b/home/package.json index 89be7e966ab5e..e991d0e52d537 100644 --- a/home/package.json +++ b/home/package.json @@ -62,7 +62,7 @@ "react-native-paper": "^4.0.1", "react-native-reanimated": "~2.10.0", "react-native-safe-area-context": "4.3.1", - "react-native-screens": "~3.17.0", + "react-native-screens": "~3.18.0", "react-redux": "^7.2.0", "react-string-replace": "^0.4.4", "reanimated-bottom-sheet": "^1.0.0-alpha.18", diff --git a/ios/Podfile.lock b/ios/Podfile.lock index dd38fc6d7b46f..9e71f67959f49 100644 --- a/ios/Podfile.lock +++ b/ios/Podfile.lock @@ -2043,7 +2043,7 @@ PODS: - React-RCTText - ReactCommon/turbomodule/core - Yoga - - RNScreens (3.17.0): + - RNScreens (3.18.0): - React-Core - React-RCTImage - Stripe (22.7.1): @@ -3511,7 +3511,7 @@ SPEC CHECKSUMS: RNFlashList: 5116f2de2f543f01bfc30b22d5942d5af84b43df RNGestureHandler: 7673697e7c0e9391adefae4faa087442bc04af33 RNReanimated: 5c8c17e26787fd8984cd5accdc70fef2ca70aafd - RNScreens: 0df01424e9e0ed7827200d6ed1087ddd06c493f9 + RNScreens: f3230dd008a7d0ce5c0a8bc78ff12cf2315bda24 Stripe: fb29a476e4866fec4ef22fb76207363dd32795aa stripe-react-native: 5663bf9de94bff6b3d92c16d32784433b5e94cf7 StripeApplePay: 09955cdf3f49b367af2feadd9c5b3bddb35446c0 diff --git a/ios/vendored/unversioned/react-native-screens/RNScreens.podspec.json b/ios/vendored/unversioned/react-native-screens/RNScreens.podspec.json index 77188dae27cfd..ce966b8a9d7a3 100644 --- a/ios/vendored/unversioned/react-native-screens/RNScreens.podspec.json +++ b/ios/vendored/unversioned/react-native-screens/RNScreens.podspec.json @@ -1,6 +1,6 @@ { "name": "RNScreens", - "version": "3.17.0", + "version": "3.18.0", "summary": "Native navigation primitives for your React Native app.", "description": "RNScreens - first incomplete navigation solution for your React Native app", "homepage": "https://github.com/software-mansion/react-native-screens", @@ -14,7 +14,7 @@ }, "source": { "git": "https://github.com/software-mansion/react-native-screens.git", - "tag": "3.17.0" + "tag": "3.18.0" }, "source_files": "ios/**/*.{h,m,mm}", "requires_arc": true, diff --git a/packages/expo-stories/package.json b/packages/expo-stories/package.json index 4d164b1bb1d40..113507fbd0653 100644 --- a/packages/expo-stories/package.json +++ b/packages/expo-stories/package.json @@ -32,7 +32,7 @@ "glob": "^7.1.7", "react-native-gesture-handler": "~2.7.0", "react-native-safe-area-context": "4.3.1", - "react-native-screens": "~3.17.0", + "react-native-screens": "~3.18.0", "react-native-svg": "12.3.0", "sane": "^5.0.1" } diff --git a/packages/expo/bundledNativeModules.json b/packages/expo/bundledNativeModules.json index 0e15ba08825a8..c424e3ea16c3e 100644 --- a/packages/expo/bundledNativeModules.json +++ b/packages/expo/bundledNativeModules.json @@ -96,7 +96,7 @@ "react-native-pager-view": "5.4.24", "react-native-reanimated": "~2.10.0", "react-native-safe-area-context": "4.3.1", - "react-native-screens": "~3.17.0", + "react-native-screens": "~3.18.0", "react-native-shared-element": "0.8.4", "react-native-svg": "12.3.0", "react-native-view-shot": "3.3.0", diff --git a/yarn.lock b/yarn.lock index 938b0578c201f..b3d33289d4a3e 100644 --- a/yarn.lock +++ b/yarn.lock @@ -17279,10 +17279,10 @@ react-native-safe-modules@^1.0.3: dependencies: dedent "^0.6.0" -react-native-screens@~3.15.0: - version "3.15.0" - resolved "https://registry.yarnpkg.com/react-native-screens/-/react-native-screens-3.15.0.tgz#78e42c8df72851b1ff235ddf5434b961ae123ca5" - integrity sha512-ezC5TibsUYyqPuuHpZoM3iEl6bRzCVBMJeGaFkn7xznuOt1VwkZVub0BvafIEYR/+AQC/RjxzMSQPs1qal0+wA== +react-native-screens@~3.18.0: + version "3.18.0" + resolved "https://registry.yarnpkg.com/react-native-screens/-/react-native-screens-3.18.0.tgz#e10ec54d8b158d47f66dc6c9a81e5a042e33037d" + integrity sha512-ndnz5JPLMLq/ThCYQzAOT65x6B4bdKFH1GKckUdbxKrwINGItPRSUiAoVI7YYyOQOa4VYL4hF37rrx+AjgjtRg== dependencies: react-freeze "^1.0.0" warn-once "^0.1.0" From 05f8454035028d9e2a15ce9d6395bc610f1ae1b8 Mon Sep 17 00:00:00 2001 From: Tomasz Sapeta Date: Tue, 4 Oct 2022 14:46:07 +0200 Subject: [PATCH 5/8] Add changelog entry --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index b706b0a76e2b8..7762ef93dde9a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -12,6 +12,7 @@ Package-specific changes not released in any SDK will be added here just before - Updated `react-native-webview` from `11.23.0` to `11.23.1`. ([#19375](https://github.com/expo/expo/pull/19375) by [@aleqsio](https://github.com/aleqsio)) - Updated `react-native-gesture-handler` from `2.5.0` to `2.7.0`. ([#19362](https://github.com/expo/expo/pull/19362) by [@tsapeta](https://github.com/tsapeta)) - Updated `@react-native-picker/picker` from `2.4.2` to `2.4.6`. ([#19390](https://github.com/expo/expo/pull/19390) by [@aleqsio](https://github.com/aleqsio)) +- Updated `react-native-screens` from `3.15.0` to `3.18.0`. ([#19383](https://github.com/expo/expo/pull/19383) by [@tsapeta](https://github.com/tsapeta)) ### 🛠 Breaking changes From f7a1d23930338cf0d796dcc7883db02e0a52ba91 Mon Sep 17 00:00:00 2001 From: Tomasz Sapeta Date: Wed, 5 Oct 2022 12:17:03 +0200 Subject: [PATCH 6/8] Update Podfile.lock in bare-expo [skip ci] --- apps/bare-expo/ios/Podfile.lock | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/apps/bare-expo/ios/Podfile.lock b/apps/bare-expo/ios/Podfile.lock index dd95c364818b2..276a849973ea7 100644 --- a/apps/bare-expo/ios/Podfile.lock +++ b/apps/bare-expo/ios/Podfile.lock @@ -743,7 +743,7 @@ PODS: - React-RCTText - ReactCommon/turbomodule/core - Yoga - - RNScreens (3.15.0): + - RNScreens (3.18.0): - React-Core - React-RCTImage - RNSharedElement (0.8.4): @@ -1375,7 +1375,7 @@ SPEC CHECKSUMS: RNDateTimePicker: 30e6733efc179d1e49d6008ea5fce42cdc9aeeca RNGestureHandler: 7673697e7c0e9391adefae4faa087442bc04af33 RNReanimated: 60e291d42c77752a0f6d6f358387bdf225a87c6e - RNScreens: 4a1af06327774490d97342c00aee0c2bafb497b7 + RNScreens: f3230dd008a7d0ce5c0a8bc78ff12cf2315bda24 RNSharedElement: eb7d506733952d58634f34c82ec17e82f557e377 RNSVG: 302bfc9905bd8122f08966dc2ce2d07b7b52b9f8 SDWebImage: e5cc87bf736e60f49592f307bdf9e157189298a3 From f1c04e1e032c923ccc1dcbdea911f4c290b4557c Mon Sep 17 00:00:00 2001 From: Tomasz Sapeta Date: Wed, 5 Oct 2022 15:25:40 +0200 Subject: [PATCH 7/8] Remove unnecessary Gradle files --- .../android/gradle.properties | 23 --- .../gradle/wrapper/gradle-wrapper.properties | 5 - .../react-native-screens/android/gradlew | 164 ------------------ .../react-native-screens/android/gradlew.bat | 90 ---------- .../android/settings.gradle | 1 - .../android/spotless.gradle | 12 -- tools/src/vendoring/config/expoGoConfig.ts | 8 +- 7 files changed, 7 insertions(+), 296 deletions(-) delete mode 100644 android/vendored/unversioned/react-native-screens/android/gradle.properties delete mode 100644 android/vendored/unversioned/react-native-screens/android/gradle/wrapper/gradle-wrapper.properties delete mode 100755 android/vendored/unversioned/react-native-screens/android/gradlew delete mode 100644 android/vendored/unversioned/react-native-screens/android/gradlew.bat delete mode 100644 android/vendored/unversioned/react-native-screens/android/settings.gradle delete mode 100644 android/vendored/unversioned/react-native-screens/android/spotless.gradle diff --git a/android/vendored/unversioned/react-native-screens/android/gradle.properties b/android/vendored/unversioned/react-native-screens/android/gradle.properties deleted file mode 100644 index 7913518b0167f..0000000000000 --- a/android/vendored/unversioned/react-native-screens/android/gradle.properties +++ /dev/null @@ -1,23 +0,0 @@ -# Project-wide Gradle settings. - -# IDE (e.g. Android Studio) users: -# Gradle settings configured through the IDE *will override* -# any settings specified in this file. - -# For more details on how to configure your build environment visit -# http://www.gradle.org/docs/current/userguide/build_environment.html - -# Specifies the JVM arguments used for the daemon process. -# The setting is particularly useful for tweaking memory settings. -# Default value: -Xmx10248m -XX:MaxPermSize=256m -org.gradle.jvmargs=-Xmx2048m -XX:MaxPermSize=512m -XX:+HeapDumpOnOutOfMemoryError -Dfile.encoding=UTF-8 - -# When configured, Gradle will run in incubating parallel mode. -# This option should only be used with decoupled projects. More details, visit -# http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects -# org.gradle.parallel=true - -android.useAndroidX=true -android.enableJetifier=true - -kotlin.code.style=official \ No newline at end of file diff --git a/android/vendored/unversioned/react-native-screens/android/gradle/wrapper/gradle-wrapper.properties b/android/vendored/unversioned/react-native-screens/android/gradle/wrapper/gradle-wrapper.properties deleted file mode 100644 index 9946c0130b4b9..0000000000000 --- a/android/vendored/unversioned/react-native-screens/android/gradle/wrapper/gradle-wrapper.properties +++ /dev/null @@ -1,5 +0,0 @@ -distributionBase=GRADLE_USER_HOME -distributionPath=wrapper/dists -zipStoreBase=GRADLE_USER_HOME -zipStorePath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-6.9-all.zip \ No newline at end of file diff --git a/android/vendored/unversioned/react-native-screens/android/gradlew b/android/vendored/unversioned/react-native-screens/android/gradlew deleted file mode 100755 index 91a7e269e19df..0000000000000 --- a/android/vendored/unversioned/react-native-screens/android/gradlew +++ /dev/null @@ -1,164 +0,0 @@ -#!/usr/bin/env bash - -############################################################################## -## -## Gradle start up script for UN*X -## -############################################################################## - -# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. -DEFAULT_JVM_OPTS="" - -APP_NAME="Gradle" -APP_BASE_NAME=`basename "$0"` - -# Use the maximum available, or set MAX_FD != -1 to use that value. -MAX_FD="maximum" - -warn ( ) { - echo "$*" -} - -die ( ) { - echo - echo "$*" - echo - exit 1 -} - -# OS specific support (must be 'true' or 'false'). -cygwin=false -msys=false -darwin=false -case "`uname`" in - CYGWIN* ) - cygwin=true - ;; - Darwin* ) - darwin=true - ;; - MINGW* ) - msys=true - ;; -esac - -# For Cygwin, ensure paths are in UNIX format before anything is touched. -if $cygwin ; then - [ -n "$JAVA_HOME" ] && JAVA_HOME=`cygpath --unix "$JAVA_HOME"` -fi - -# Attempt to set APP_HOME -# Resolve links: $0 may be a link -PRG="$0" -# Need this for relative symlinks. -while [ -h "$PRG" ] ; do - ls=`ls -ld "$PRG"` - link=`expr "$ls" : '.*-> \(.*\)$'` - if expr "$link" : '/.*' > /dev/null; then - PRG="$link" - else - PRG=`dirname "$PRG"`"/$link" - fi -done -SAVED="`pwd`" -cd "`dirname \"$PRG\"`/" >&- -APP_HOME="`pwd -P`" -cd "$SAVED" >&- - -CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar - -# Determine the Java command to use to start the JVM. -if [ -n "$JAVA_HOME" ] ; then - if [ -x "$JAVA_HOME/jre/sh/java" ] ; then - # IBM's JDK on AIX uses strange locations for the executables - JAVACMD="$JAVA_HOME/jre/sh/java" - else - JAVACMD="$JAVA_HOME/bin/java" - fi - if [ ! -x "$JAVACMD" ] ; then - die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME - -Please set the JAVA_HOME variable in your environment to match the -location of your Java installation." - fi -else - JAVACMD="java" - which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. - -Please set the JAVA_HOME variable in your environment to match the -location of your Java installation." -fi - -# Increase the maximum file descriptors if we can. -if [ "$cygwin" = "false" -a "$darwin" = "false" ] ; then - MAX_FD_LIMIT=`ulimit -H -n` - if [ $? -eq 0 ] ; then - if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then - MAX_FD="$MAX_FD_LIMIT" - fi - ulimit -n $MAX_FD - if [ $? -ne 0 ] ; then - warn "Could not set maximum file descriptor limit: $MAX_FD" - fi - else - warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT" - fi -fi - -# For Darwin, add options to specify how the application appears in the dock -if $darwin; then - GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\"" -fi - -# For Cygwin, switch paths to Windows format before running java -if $cygwin ; then - APP_HOME=`cygpath --path --mixed "$APP_HOME"` - CLASSPATH=`cygpath --path --mixed "$CLASSPATH"` - - # We build the pattern for arguments to be converted via cygpath - ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null` - SEP="" - for dir in $ROOTDIRSRAW ; do - ROOTDIRS="$ROOTDIRS$SEP$dir" - SEP="|" - done - OURCYGPATTERN="(^($ROOTDIRS))" - # Add a user-defined pattern to the cygpath arguments - if [ "$GRADLE_CYGPATTERN" != "" ] ; then - OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)" - fi - # Now convert the arguments - kludge to limit ourselves to /bin/sh - i=0 - for arg in "$@" ; do - CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -` - CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option - - if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition - eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"` - else - eval `echo args$i`="\"$arg\"" - fi - i=$((i+1)) - done - case $i in - (0) set -- ;; - (1) set -- "$args0" ;; - (2) set -- "$args0" "$args1" ;; - (3) set -- "$args0" "$args1" "$args2" ;; - (4) set -- "$args0" "$args1" "$args2" "$args3" ;; - (5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;; - (6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;; - (7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;; - (8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;; - (9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;; - esac -fi - -# Split up the JVM_OPTS And GRADLE_OPTS values into an array, following the shell quoting and substitution rules -function splitJvmOpts() { - JVM_OPTS=("$@") -} -eval splitJvmOpts $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS -JVM_OPTS[${#JVM_OPTS[*]}]="-Dorg.gradle.appname=$APP_BASE_NAME" - -exec "$JAVACMD" "${JVM_OPTS[@]}" -classpath "$CLASSPATH" org.gradle.wrapper.GradleWrapperMain "$@" diff --git a/android/vendored/unversioned/react-native-screens/android/gradlew.bat b/android/vendored/unversioned/react-native-screens/android/gradlew.bat deleted file mode 100644 index aec99730b4e8f..0000000000000 --- a/android/vendored/unversioned/react-native-screens/android/gradlew.bat +++ /dev/null @@ -1,90 +0,0 @@ -@if "%DEBUG%" == "" @echo off -@rem ########################################################################## -@rem -@rem Gradle startup script for Windows -@rem -@rem ########################################################################## - -@rem Set local scope for the variables with windows NT shell -if "%OS%"=="Windows_NT" setlocal - -@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. -set DEFAULT_JVM_OPTS= - -set DIRNAME=%~dp0 -if "%DIRNAME%" == "" set DIRNAME=. -set APP_BASE_NAME=%~n0 -set APP_HOME=%DIRNAME% - -@rem Find java.exe -if defined JAVA_HOME goto findJavaFromJavaHome - -set JAVA_EXE=java.exe -%JAVA_EXE% -version >NUL 2>&1 -if "%ERRORLEVEL%" == "0" goto init - -echo. -echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. -echo. -echo Please set the JAVA_HOME variable in your environment to match the -echo location of your Java installation. - -goto fail - -:findJavaFromJavaHome -set JAVA_HOME=%JAVA_HOME:"=% -set JAVA_EXE=%JAVA_HOME%/bin/java.exe - -if exist "%JAVA_EXE%" goto init - -echo. -echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% -echo. -echo Please set the JAVA_HOME variable in your environment to match the -echo location of your Java installation. - -goto fail - -:init -@rem Get command-line arguments, handling Windowz variants - -if not "%OS%" == "Windows_NT" goto win9xME_args -if "%@eval[2+2]" == "4" goto 4NT_args - -:win9xME_args -@rem Slurp the command line arguments. -set CMD_LINE_ARGS= -set _SKIP=2 - -:win9xME_args_slurp -if "x%~1" == "x" goto execute - -set CMD_LINE_ARGS=%* -goto execute - -:4NT_args -@rem Get arguments from the 4NT Shell from JP Software -set CMD_LINE_ARGS=%$ - -:execute -@rem Setup the command line - -set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar - -@rem Execute Gradle -"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS% - -:end -@rem End local scope for the variables with windows NT shell -if "%ERRORLEVEL%"=="0" goto mainEnd - -:fail -rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of -rem the _cmd.exe /c_ return code! -if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1 -exit /b 1 - -:mainEnd -if "%OS%"=="Windows_NT" endlocal - -:omega diff --git a/android/vendored/unversioned/react-native-screens/android/settings.gradle b/android/vendored/unversioned/react-native-screens/android/settings.gradle deleted file mode 100644 index c8e23f4979eb4..0000000000000 --- a/android/vendored/unversioned/react-native-screens/android/settings.gradle +++ /dev/null @@ -1 +0,0 @@ -include 'lib' \ No newline at end of file diff --git a/android/vendored/unversioned/react-native-screens/android/spotless.gradle b/android/vendored/unversioned/react-native-screens/android/spotless.gradle deleted file mode 100644 index 7c9f86c0aa7c4..0000000000000 --- a/android/vendored/unversioned/react-native-screens/android/spotless.gradle +++ /dev/null @@ -1,12 +0,0 @@ -// formatter & linter configuration for kotlin -apply plugin: 'com.diffplug.spotless' - -spotless { - kotlin { - target 'src/**/*.kt' - ktlint("0.40.0") - trimTrailingWhitespace() - indentWithSpaces() - endWithNewline() - } -} diff --git a/tools/src/vendoring/config/expoGoConfig.ts b/tools/src/vendoring/config/expoGoConfig.ts index 90f0993513000..8139cb76fd3b4 100644 --- a/tools/src/vendoring/config/expoGoConfig.ts +++ b/tools/src/vendoring/config/expoGoConfig.ts @@ -109,7 +109,13 @@ const config: VendoringTargetConfig = { source: 'https://github.com/software-mansion/react-native-screens.git', semverPrefix: '~', ios: {}, - android: {}, + android: { + excludeFiles: [ + 'android/gradle{/**,**}', + 'android/settings.gradle', + 'android/spotless.gradle', + ], + }, }, 'amazon-cognito-identity-js': { source: 'https://github.com/aws-amplify/amplify-js.git', From 2452bebe0e8a20e983f60d38cbaa95eb66dab2ad Mon Sep 17 00:00:00 2001 From: Tomasz Sapeta Date: Wed, 5 Oct 2022 15:27:18 +0200 Subject: [PATCH 8/8] Remove res/anim from expoview --- .../main/res/anim/rns_default_enter_in.xml | 18 ------------------ .../main/res/anim/rns_default_enter_out.xml | 19 ------------------- .../src/main/res/anim/rns_default_exit_in.xml | 17 ----------------- .../main/res/anim/rns_default_exit_out.xml | 18 ------------------ .../main/res/anim/rns_fade_from_bottom.xml | 14 -------------- .../src/main/res/anim/rns_fade_in.xml | 7 ------- .../src/main/res/anim/rns_fade_out.xml | 7 ------- .../src/main/res/anim/rns_fade_to_bottom.xml | 15 --------------- .../src/main/res/anim/rns_no_animation_20.xml | 6 ------ .../main/res/anim/rns_no_animation_250.xml | 7 ------- .../main/res/anim/rns_no_animation_350.xml | 6 ------ .../main/res/anim/rns_no_animation_medium.xml | 7 ------- .../res/anim/rns_slide_in_from_bottom.xml | 7 ------- .../main/res/anim/rns_slide_in_from_left.xml | 5 ----- .../main/res/anim/rns_slide_in_from_right.xml | 5 ----- .../main/res/anim/rns_slide_out_to_bottom.xml | 7 ------- .../main/res/anim/rns_slide_out_to_left.xml | 5 ----- .../main/res/anim/rns_slide_out_to_right.xml | 5 ----- 18 files changed, 175 deletions(-) delete mode 100644 android/expoview/src/main/res/anim/rns_default_enter_in.xml delete mode 100644 android/expoview/src/main/res/anim/rns_default_enter_out.xml delete mode 100644 android/expoview/src/main/res/anim/rns_default_exit_in.xml delete mode 100644 android/expoview/src/main/res/anim/rns_default_exit_out.xml delete mode 100644 android/expoview/src/main/res/anim/rns_fade_from_bottom.xml delete mode 100644 android/expoview/src/main/res/anim/rns_fade_in.xml delete mode 100644 android/expoview/src/main/res/anim/rns_fade_out.xml delete mode 100644 android/expoview/src/main/res/anim/rns_fade_to_bottom.xml delete mode 100644 android/expoview/src/main/res/anim/rns_no_animation_20.xml delete mode 100644 android/expoview/src/main/res/anim/rns_no_animation_250.xml delete mode 100644 android/expoview/src/main/res/anim/rns_no_animation_350.xml delete mode 100644 android/expoview/src/main/res/anim/rns_no_animation_medium.xml delete mode 100644 android/expoview/src/main/res/anim/rns_slide_in_from_bottom.xml delete mode 100644 android/expoview/src/main/res/anim/rns_slide_in_from_left.xml delete mode 100644 android/expoview/src/main/res/anim/rns_slide_in_from_right.xml delete mode 100644 android/expoview/src/main/res/anim/rns_slide_out_to_bottom.xml delete mode 100644 android/expoview/src/main/res/anim/rns_slide_out_to_left.xml delete mode 100644 android/expoview/src/main/res/anim/rns_slide_out_to_right.xml diff --git a/android/expoview/src/main/res/anim/rns_default_enter_in.xml b/android/expoview/src/main/res/anim/rns_default_enter_in.xml deleted file mode 100644 index 51b17bc6d5cee..0000000000000 --- a/android/expoview/src/main/res/anim/rns_default_enter_in.xml +++ /dev/null @@ -1,18 +0,0 @@ - - - - - \ No newline at end of file diff --git a/android/expoview/src/main/res/anim/rns_default_enter_out.xml b/android/expoview/src/main/res/anim/rns_default_enter_out.xml deleted file mode 100644 index 8f0b131f8cd85..0000000000000 --- a/android/expoview/src/main/res/anim/rns_default_enter_out.xml +++ /dev/null @@ -1,19 +0,0 @@ - - - - - - \ No newline at end of file diff --git a/android/expoview/src/main/res/anim/rns_default_exit_in.xml b/android/expoview/src/main/res/anim/rns_default_exit_in.xml deleted file mode 100644 index 9fdfc1dbab044..0000000000000 --- a/android/expoview/src/main/res/anim/rns_default_exit_in.xml +++ /dev/null @@ -1,17 +0,0 @@ - - - - - \ No newline at end of file diff --git a/android/expoview/src/main/res/anim/rns_default_exit_out.xml b/android/expoview/src/main/res/anim/rns_default_exit_out.xml deleted file mode 100644 index cb1b41b9a3425..0000000000000 --- a/android/expoview/src/main/res/anim/rns_default_exit_out.xml +++ /dev/null @@ -1,18 +0,0 @@ - - - - - \ No newline at end of file diff --git a/android/expoview/src/main/res/anim/rns_fade_from_bottom.xml b/android/expoview/src/main/res/anim/rns_fade_from_bottom.xml deleted file mode 100644 index 2182290bf8c22..0000000000000 --- a/android/expoview/src/main/res/anim/rns_fade_from_bottom.xml +++ /dev/null @@ -1,14 +0,0 @@ - - - - - - - \ No newline at end of file diff --git a/android/expoview/src/main/res/anim/rns_fade_in.xml b/android/expoview/src/main/res/anim/rns_fade_in.xml deleted file mode 100644 index 78e8a972bc4a2..0000000000000 --- a/android/expoview/src/main/res/anim/rns_fade_in.xml +++ /dev/null @@ -1,7 +0,0 @@ - - - \ No newline at end of file diff --git a/android/expoview/src/main/res/anim/rns_fade_out.xml b/android/expoview/src/main/res/anim/rns_fade_out.xml deleted file mode 100644 index f1db2ad979272..0000000000000 --- a/android/expoview/src/main/res/anim/rns_fade_out.xml +++ /dev/null @@ -1,7 +0,0 @@ - - - \ No newline at end of file diff --git a/android/expoview/src/main/res/anim/rns_fade_to_bottom.xml b/android/expoview/src/main/res/anim/rns_fade_to_bottom.xml deleted file mode 100644 index c498341b3faa0..0000000000000 --- a/android/expoview/src/main/res/anim/rns_fade_to_bottom.xml +++ /dev/null @@ -1,15 +0,0 @@ - - - - - - - \ No newline at end of file diff --git a/android/expoview/src/main/res/anim/rns_no_animation_20.xml b/android/expoview/src/main/res/anim/rns_no_animation_20.xml deleted file mode 100644 index be8dc2123922c..0000000000000 --- a/android/expoview/src/main/res/anim/rns_no_animation_20.xml +++ /dev/null @@ -1,6 +0,0 @@ - - \ No newline at end of file diff --git a/android/expoview/src/main/res/anim/rns_no_animation_250.xml b/android/expoview/src/main/res/anim/rns_no_animation_250.xml deleted file mode 100644 index 2371704980d91..0000000000000 --- a/android/expoview/src/main/res/anim/rns_no_animation_250.xml +++ /dev/null @@ -1,7 +0,0 @@ - - - \ No newline at end of file diff --git a/android/expoview/src/main/res/anim/rns_no_animation_350.xml b/android/expoview/src/main/res/anim/rns_no_animation_350.xml deleted file mode 100644 index db4cdc5a18041..0000000000000 --- a/android/expoview/src/main/res/anim/rns_no_animation_350.xml +++ /dev/null @@ -1,6 +0,0 @@ - - \ No newline at end of file diff --git a/android/expoview/src/main/res/anim/rns_no_animation_medium.xml b/android/expoview/src/main/res/anim/rns_no_animation_medium.xml deleted file mode 100644 index 4e30d7099b1f7..0000000000000 --- a/android/expoview/src/main/res/anim/rns_no_animation_medium.xml +++ /dev/null @@ -1,7 +0,0 @@ - - - \ No newline at end of file diff --git a/android/expoview/src/main/res/anim/rns_slide_in_from_bottom.xml b/android/expoview/src/main/res/anim/rns_slide_in_from_bottom.xml deleted file mode 100644 index a4c293e2e1f4d..0000000000000 --- a/android/expoview/src/main/res/anim/rns_slide_in_from_bottom.xml +++ /dev/null @@ -1,7 +0,0 @@ - - \ No newline at end of file diff --git a/android/expoview/src/main/res/anim/rns_slide_in_from_left.xml b/android/expoview/src/main/res/anim/rns_slide_in_from_left.xml deleted file mode 100644 index 939110f5437ed..0000000000000 --- a/android/expoview/src/main/res/anim/rns_slide_in_from_left.xml +++ /dev/null @@ -1,5 +0,0 @@ - - diff --git a/android/expoview/src/main/res/anim/rns_slide_in_from_right.xml b/android/expoview/src/main/res/anim/rns_slide_in_from_right.xml deleted file mode 100644 index 428eb9b71862e..0000000000000 --- a/android/expoview/src/main/res/anim/rns_slide_in_from_right.xml +++ /dev/null @@ -1,5 +0,0 @@ - - diff --git a/android/expoview/src/main/res/anim/rns_slide_out_to_bottom.xml b/android/expoview/src/main/res/anim/rns_slide_out_to_bottom.xml deleted file mode 100644 index 197c27697766d..0000000000000 --- a/android/expoview/src/main/res/anim/rns_slide_out_to_bottom.xml +++ /dev/null @@ -1,7 +0,0 @@ - - \ No newline at end of file diff --git a/android/expoview/src/main/res/anim/rns_slide_out_to_left.xml b/android/expoview/src/main/res/anim/rns_slide_out_to_left.xml deleted file mode 100644 index 400a202c3bd6d..0000000000000 --- a/android/expoview/src/main/res/anim/rns_slide_out_to_left.xml +++ /dev/null @@ -1,5 +0,0 @@ - - diff --git a/android/expoview/src/main/res/anim/rns_slide_out_to_right.xml b/android/expoview/src/main/res/anim/rns_slide_out_to_right.xml deleted file mode 100644 index a00332bcdb171..0000000000000 --- a/android/expoview/src/main/res/anim/rns_slide_out_to_right.xml +++ /dev/null @@ -1,5 +0,0 @@ - -