diff --git a/android/src/main/java/com/swmansion/rnscreens/FragmentHolder.kt b/android/src/main/java/com/swmansion/rnscreens/FragmentHolder.kt new file mode 100644 index 0000000000..ed6eb5fe4f --- /dev/null +++ b/android/src/main/java/com/swmansion/rnscreens/FragmentHolder.kt @@ -0,0 +1,7 @@ +package com.swmansion.rnscreens + +import androidx.fragment.app.Fragment + +interface FragmentHolder { + val fragment: Fragment +} diff --git a/android/src/main/java/com/swmansion/rnscreens/Screen.kt b/android/src/main/java/com/swmansion/rnscreens/Screen.kt index 15301b5276..942eed688f 100644 --- a/android/src/main/java/com/swmansion/rnscreens/Screen.kt +++ b/android/src/main/java/com/swmansion/rnscreens/Screen.kt @@ -9,15 +9,18 @@ import android.view.ViewGroup import android.view.WindowManager import android.webkit.WebView import androidx.core.view.children +import androidx.fragment.app.Fragment 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) { + val fragment: Fragment? + get() = fragmentWrapper?.fragment - var fragment: ScreenFragment? = null - var container: ScreenContainer<*>? = null + var fragmentWrapper: ScreenFragmentWrapper? = null + var container: ScreenContainer? = null var activityState: ActivityState? = null private set private var mTransitioning = false @@ -150,7 +153,7 @@ class Screen constructor(context: ReactContext?) : FabricEnabledViewGroup(contex else -> ActivityInfo.SCREEN_ORIENTATION_UNSPECIFIED } - fragment?.let { ScreenWindowTraits.setOrientation(this, it.tryGetActivity()) } + fragmentWrapper?.let { ScreenWindowTraits.setOrientation(this, it.tryGetActivity()) } } // Accepts one of 4 accessibility flags @@ -167,7 +170,7 @@ class Screen constructor(context: ReactContext?) : FabricEnabledViewGroup(contex ScreenWindowTraits.applyDidSetStatusBarAppearance() } mStatusBarStyle = statusBarStyle - fragment?.let { ScreenWindowTraits.setStyle(this, it.tryGetActivity(), it.tryGetContext()) } + fragmentWrapper?.let { ScreenWindowTraits.setStyle(this, it.tryGetActivity(), it.tryGetContext()) } } var isStatusBarHidden: Boolean? @@ -177,7 +180,7 @@ class Screen constructor(context: ReactContext?) : FabricEnabledViewGroup(contex ScreenWindowTraits.applyDidSetStatusBarAppearance() } mStatusBarHidden = statusBarHidden - fragment?.let { ScreenWindowTraits.setHidden(this, it.tryGetActivity()) } + fragmentWrapper?.let { ScreenWindowTraits.setHidden(this, it.tryGetActivity()) } } var isStatusBarTranslucent: Boolean? @@ -187,7 +190,7 @@ class Screen constructor(context: ReactContext?) : FabricEnabledViewGroup(contex ScreenWindowTraits.applyDidSetStatusBarAppearance() } mStatusBarTranslucent = statusBarTranslucent - fragment?.let { + fragmentWrapper?.let { ScreenWindowTraits.setTranslucent( this, it.tryGetActivity(), @@ -203,7 +206,7 @@ class Screen constructor(context: ReactContext?) : FabricEnabledViewGroup(contex ScreenWindowTraits.applyDidSetStatusBarAppearance() } mStatusBarColor = statusBarColor - fragment?.let { ScreenWindowTraits.setColor(this, it.tryGetActivity(), it.tryGetContext()) } + fragmentWrapper?.let { ScreenWindowTraits.setColor(this, it.tryGetActivity(), it.tryGetContext()) } } var navigationBarColor: Int? @@ -213,7 +216,7 @@ class Screen constructor(context: ReactContext?) : FabricEnabledViewGroup(contex ScreenWindowTraits.applyDidSetNavigationBarAppearance() } mNavigationBarColor = navigationBarColor - fragment?.let { ScreenWindowTraits.setNavigationBarColor(this, it.tryGetActivity()) } + fragmentWrapper?.let { ScreenWindowTraits.setNavigationBarColor(this, it.tryGetActivity()) } } var isNavigationBarHidden: Boolean? @@ -223,7 +226,7 @@ class Screen constructor(context: ReactContext?) : FabricEnabledViewGroup(contex ScreenWindowTraits.applyDidSetNavigationBarAppearance() } mNavigationBarHidden = navigationBarHidden - fragment?.let { + fragmentWrapper?.let { ScreenWindowTraits.setNavigationBarHidden( this, it.tryGetActivity(), diff --git a/android/src/main/java/com/swmansion/rnscreens/ScreenContainer.kt b/android/src/main/java/com/swmansion/rnscreens/ScreenContainer.kt index 39a5113547..401d57ea59 100644 --- a/android/src/main/java/com/swmansion/rnscreens/ScreenContainer.kt +++ b/android/src/main/java/com/swmansion/rnscreens/ScreenContainer.kt @@ -16,9 +16,9 @@ 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) { +open class ScreenContainer(context: Context?) : ViewGroup(context) { @JvmField - protected val mScreenFragments = ArrayList() + protected val mScreenFragments = ArrayList() @JvmField protected var mFragmentManager: FragmentManager? = null private var mIsAttached = false @@ -34,7 +34,7 @@ open class ScreenContainer(context: Context?) : ViewGroup(co layout(left, top, right, bottom) } } - private var mParentScreenFragment: ScreenFragment? = null + private var mParentScreenFragment: ScreenFragmentWrapper? = null override fun onLayout(changed: Boolean, l: Int, t: Int, r: Int, b: Int) { var i = 0 @@ -87,14 +87,11 @@ open class ScreenContainer(context: Context?) : ViewGroup(co performUpdatesNow() } - protected open fun adapt(screen: Screen): T { - @Suppress("UNCHECKED_CAST") - return ScreenFragment(screen) as T - } + protected open fun adapt(screen: Screen): ScreenFragmentWrapper = ScreenFragment(screen) fun addScreen(screen: Screen, index: Int) { val fragment = adapt(screen) - screen.fragment = fragment + screen.fragmentWrapper = fragment mScreenFragments.add(index, fragment) screen.container = this onScreenChanged() @@ -119,6 +116,8 @@ open class ScreenContainer(context: Context?) : ViewGroup(co fun getScreenAt(index: Int): Screen = mScreenFragments[index].screen + fun getScreenFragmentWrapperAt(index: Int): ScreenFragmentWrapper = mScreenFragments[index] + open val topScreen: Screen? get() = mScreenFragments.firstOrNull { getActivityState(it) === ActivityState.ON_TOP }?.screen @@ -174,10 +173,10 @@ open class ScreenContainer(context: Context?) : ViewGroup(co // 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.fragmentWrapper?.let { fragmentWrapper -> + mParentScreenFragment = fragmentWrapper + fragmentWrapper.addChildScreenContainer(this) + setFragmentManager(fragmentWrapper.fragment.childFragmentManager) } ) { "Parent Screen does not have its Fragment attached" } } else { @@ -197,19 +196,19 @@ open class ScreenContainer(context: Context?) : ViewGroup(co .setReorderingAllowed(true) } - private fun attachScreen(transaction: FragmentTransaction, screenFragment: ScreenFragment) { - transaction.add(id, screenFragment) + private fun attachScreen(transaction: FragmentTransaction, fragment: Fragment) { + transaction.add(id, fragment) } - private fun detachScreen(transaction: FragmentTransaction, screenFragment: ScreenFragment) { - transaction.remove(screenFragment) + private fun detachScreen(transaction: FragmentTransaction, fragment: Fragment) { + transaction.remove(fragment) } - private fun getActivityState(screenFragment: ScreenFragment): ActivityState? = - screenFragment.screen.activityState + private fun getActivityState(screenFragmentWrapper: ScreenFragmentWrapper): ActivityState? = + screenFragmentWrapper.screen.activityState - open fun hasScreen(screenFragment: ScreenFragment?): Boolean = - mScreenFragments.contains(screenFragment) + open fun hasScreen(screenFragmentWrapper: ScreenFragmentWrapper?): Boolean = + mScreenFragments.contains(screenFragmentWrapper) override fun onAttachedToWindow() { super.onAttachedToWindow() @@ -246,7 +245,7 @@ open class ScreenContainer(context: Context?) : ViewGroup(co } } - mParentScreenFragment?.unregisterChildScreenContainer(this) + mParentScreenFragment?.removeChildScreenContainer(this) mParentScreenFragment = null super.onDetachedFromWindow() @@ -320,13 +319,13 @@ open class ScreenContainer(context: Context?) : ViewGroup(co "mFragmentManager is null when performing update in ScreenContainer" }.fragments ) - for (screenFragment in mScreenFragments) { - if (getActivityState(screenFragment) === ActivityState.INACTIVE && - screenFragment.isAdded + for (fragmentWrapper in mScreenFragments) { + if (getActivityState(fragmentWrapper) === ActivityState.INACTIVE && + fragmentWrapper.fragment.isAdded ) { - detachScreen(it, screenFragment) + detachScreen(it, fragmentWrapper.fragment) } - orphaned.remove(screenFragment) + orphaned.remove(fragmentWrapper.fragment) } if (orphaned.isNotEmpty()) { val orphanedAry = orphaned.toTypedArray() @@ -344,23 +343,23 @@ open class ScreenContainer(context: Context?) : ViewGroup(co // attach newly activated screens var addedBefore = false - val pendingFront: ArrayList = ArrayList() + val pendingFront: ArrayList = ArrayList() - for (screenFragment in mScreenFragments) { - val activityState = getActivityState(screenFragment) - if (activityState !== ActivityState.INACTIVE && !screenFragment.isAdded) { + for (fragmentWrapper in mScreenFragments) { + val activityState = getActivityState(fragmentWrapper) + if (activityState !== ActivityState.INACTIVE && !fragmentWrapper.fragment.isAdded) { addedBefore = true - attachScreen(it, screenFragment) + attachScreen(it, fragmentWrapper.fragment) } 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) + detachScreen(it, fragmentWrapper.fragment) + pendingFront.add(fragmentWrapper) } - screenFragment.screen.setTransitioning(transitioning) + fragmentWrapper.screen.setTransitioning(transitioning) } for (screenFragment in pendingFront) { - attachScreen(it, screenFragment) + attachScreen(it, screenFragment.fragment) } it.commitNowAllowingStateLoss() @@ -368,6 +367,6 @@ open class ScreenContainer(context: Context?) : ViewGroup(co } protected open fun notifyContainerUpdate() { - topScreen?.fragment?.onContainerUpdate() + topScreen?.fragmentWrapper?.onContainerUpdate() } } diff --git a/android/src/main/java/com/swmansion/rnscreens/ScreenContainerViewManager.kt b/android/src/main/java/com/swmansion/rnscreens/ScreenContainerViewManager.kt index 4cf0a1443e..4b181911d6 100644 --- a/android/src/main/java/com/swmansion/rnscreens/ScreenContainerViewManager.kt +++ b/android/src/main/java/com/swmansion/rnscreens/ScreenContainerViewManager.kt @@ -8,27 +8,27 @@ import com.facebook.react.uimanager.ThemedReactContext import com.facebook.react.uimanager.ViewGroupManager @ReactModule(name = ScreenContainerViewManager.REACT_CLASS) -class ScreenContainerViewManager : ViewGroupManager>() { +class ScreenContainerViewManager : ViewGroupManager() { override fun getName(): String = REACT_CLASS - override fun createViewInstance(reactContext: ThemedReactContext): ScreenContainer = ScreenContainer(reactContext) + override fun createViewInstance(reactContext: ThemedReactContext): ScreenContainer = ScreenContainer(reactContext) - override fun addView(parent: ScreenContainer<*>, child: View, index: Int) { + 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) { + override fun removeViewAt(parent: ScreenContainer, index: Int) { parent.removeScreenAt(index) } - override fun removeAllViews(parent: ScreenContainer<*>) { + override fun removeAllViews(parent: ScreenContainer) { parent.removeAllScreens() } - override fun getChildCount(parent: ScreenContainer<*>): Int = parent.screenCount + override fun getChildCount(parent: ScreenContainer): Int = parent.screenCount - override fun getChildAt(parent: ScreenContainer<*>, index: Int): View = parent.getScreenAt(index) + override fun getChildAt(parent: ScreenContainer, index: Int): View = parent.getScreenAt(index) override fun createShadowNodeInstance(context: ReactApplicationContext): LayoutShadowNode = ScreensShadowNode(context) diff --git a/android/src/main/java/com/swmansion/rnscreens/ScreenEventDispatcher.kt b/android/src/main/java/com/swmansion/rnscreens/ScreenEventDispatcher.kt new file mode 100644 index 0000000000..1c42b3e0eb --- /dev/null +++ b/android/src/main/java/com/swmansion/rnscreens/ScreenEventDispatcher.kt @@ -0,0 +1,21 @@ +package com.swmansion.rnscreens + +interface ScreenEventDispatcher { + fun canDispatchLifecycleEvent(event: ScreenFragment.ScreenLifecycleEvent): Boolean + fun updateLastEventDispatched(event: ScreenFragment.ScreenLifecycleEvent) + + /** + * Dispatches given screen lifecycle event to JS using screen from given fragment `fragmentWrapper` + */ + fun dispatchLifecycleEvent(event: ScreenFragment.ScreenLifecycleEvent, fragmentWrapper: ScreenFragmentWrapper) + + /** + * Dispatches given screen lifecycle event from all non-empty child containers to JS + */ + fun dispatchLifecycleEventInChildContainers(event: ScreenFragment.ScreenLifecycleEvent) + + fun dispatchHeaderBackButtonClickedEvent() + fun dispatchTransitionProgressEvent(alpha: Float, closing: Boolean) + + // Concrete dispatchers +} diff --git a/android/src/main/java/com/swmansion/rnscreens/ScreenFragment.kt b/android/src/main/java/com/swmansion/rnscreens/ScreenFragment.kt index c52fdbf374..a5967056fb 100644 --- a/android/src/main/java/com/swmansion/rnscreens/ScreenFragment.kt +++ b/android/src/main/java/com/swmansion/rnscreens/ScreenFragment.kt @@ -25,15 +25,18 @@ import com.swmansion.rnscreens.events.ScreenWillDisappearEvent import kotlin.math.max import kotlin.math.min -open class ScreenFragment : Fragment { +open class ScreenFragment : Fragment, ScreenFragmentWrapper { enum class ScreenLifecycleEvent { Appear, WillAppear, Disappear, WillDisappear } + override val fragment: Fragment + get() = this + // 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() + override 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 @@ -100,7 +103,7 @@ open class ScreenFragment : Fragment { } } - open fun onContainerUpdate() { + override fun onContainerUpdate() { updateWindowTraits() } @@ -113,7 +116,7 @@ open class ScreenFragment : Fragment { ScreenWindowTraits.trySetWindowTraits(screen, activity, tryGetContext()) } - fun tryGetActivity(): Activity? { + override fun tryGetActivity(): Activity? { activity?.let { return it } val context = screen.context if (context is ReactContext && context.currentActivity != null) { @@ -130,7 +133,7 @@ open class ScreenFragment : Fragment { return null } - fun tryGetContext(): ReactContext? { + override fun tryGetContext(): ReactContext? { if (context is ReactContext) { return context as ReactContext } @@ -149,17 +152,17 @@ open class ScreenFragment : Fragment { return null } - val childScreenContainers: List> + override val childScreenContainers: List get() = mChildScreenContainers - private fun canDispatchEvent(event: ScreenLifecycleEvent): Boolean = when (event) { + override fun canDispatchLifecycleEvent(event: ScreenLifecycleEvent): Boolean = when (event) { ScreenLifecycleEvent.WillAppear -> canDispatchWillAppear ScreenLifecycleEvent.Appear -> canDispatchAppear ScreenLifecycleEvent.WillDisappear -> !canDispatchWillAppear ScreenLifecycleEvent.Disappear -> !canDispatchAppear } - private fun setLastEventDispatched(event: ScreenLifecycleEvent) { + override fun updateLastEventDispatched(event: ScreenLifecycleEvent) { when (event) { ScreenLifecycleEvent.WillAppear -> canDispatchWillAppear = false ScreenLifecycleEvent.Appear -> canDispatchAppear = false @@ -169,29 +172,30 @@ open class ScreenFragment : Fragment { } private fun dispatchOnWillAppear() { - dispatchEvent(ScreenLifecycleEvent.WillAppear, this) - dispatchTransitionProgress(0.0f, false) + dispatchLifecycleEvent(ScreenLifecycleEvent.WillAppear, this) + dispatchTransitionProgressEvent(0.0f, false) } private fun dispatchOnAppear() { - dispatchEvent(ScreenLifecycleEvent.Appear, this) - dispatchTransitionProgress(1.0f, false) + dispatchLifecycleEvent(ScreenLifecycleEvent.Appear, this) + dispatchTransitionProgressEvent(1.0f, false) } private fun dispatchOnWillDisappear() { - dispatchEvent(ScreenLifecycleEvent.WillDisappear, this) - dispatchTransitionProgress(0.0f, true) + dispatchLifecycleEvent(ScreenLifecycleEvent.WillDisappear, this) + dispatchTransitionProgressEvent(0.0f, true) } private fun dispatchOnDisappear() { - dispatchEvent(ScreenLifecycleEvent.Disappear, this) - dispatchTransitionProgress(1.0f, true) + dispatchLifecycleEvent(ScreenLifecycleEvent.Disappear, this) + dispatchTransitionProgressEvent(1.0f, true) } - private fun dispatchEvent(event: ScreenLifecycleEvent, fragment: ScreenFragment) { - if (fragment is ScreenStackFragment && fragment.canDispatchEvent(event)) { + override fun dispatchLifecycleEvent(event: ScreenLifecycleEvent, fragmentWrapper: ScreenFragmentWrapper) { + val fragment = fragmentWrapper.fragment + if (fragment is ScreenStackFragment && fragment.canDispatchLifecycleEvent(event)) { fragment.screen.let { - fragment.setLastEventDispatched(event) + fragmentWrapper.updateLastEventDispatched(event) val surfaceId = UIManagerHelper.getSurfaceId(it) val lifecycleEvent: Event<*> = when (event) { ScreenLifecycleEvent.WillAppear -> ScreenWillAppearEvent(surfaceId, it.id) @@ -203,18 +207,18 @@ open class ScreenFragment : Fragment { val eventDispatcher: EventDispatcher? = UIManagerHelper.getEventDispatcherForReactTag(screenContext, screen.id) eventDispatcher?.dispatchEvent(lifecycleEvent) - fragment.dispatchEventInChildContainers(event) + fragmentWrapper.dispatchLifecycleEventInChildContainers(event) } } } - private fun dispatchEventInChildContainers(event: ScreenLifecycleEvent) { + override fun dispatchLifecycleEventInChildContainers(event: ScreenLifecycleEvent) { mChildScreenContainers.filter { it.screenCount > 0 }.forEach { - it.topScreen?.fragment?.let { fragment -> dispatchEvent(event, fragment) } + it.topScreen?.fragmentWrapper?.let { fragment -> dispatchLifecycleEvent(event, fragment) } } } - fun dispatchHeaderBackButtonClickedEvent() { + override fun dispatchHeaderBackButtonClickedEvent() { val screenContext = screen.context as ReactContext val surfaceId = UIManagerHelper.getSurfaceId(screenContext) UIManagerHelper @@ -222,7 +226,7 @@ open class ScreenFragment : Fragment { ?.dispatchEvent(HeaderBackButtonClickedEvent(surfaceId, screen.id)) } - fun dispatchTransitionProgress(alpha: Float, closing: Boolean) { + override fun dispatchTransitionProgressEvent(alpha: Float, closing: Boolean) { if (this is ScreenStackFragment) { if (mProgress != alpha) { mProgress = max(0.0f, min(1.0f, alpha)) @@ -232,7 +236,7 @@ open class ScreenFragment : Fragment { - 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 container: ScreenContainer? = screen.container val goingForward = if (container is ScreenStack) container.goingForward else false val screenContext = screen.context as ReactContext UIManagerHelper @@ -247,19 +251,19 @@ open class ScreenFragment : Fragment { } } - fun registerChildScreenContainer(screenContainer: ScreenContainer<*>) { - mChildScreenContainers.add(screenContainer) + override fun addChildScreenContainer(container: ScreenContainer) { + mChildScreenContainers.add(container) } - fun unregisterChildScreenContainer(screenContainer: ScreenContainer<*>) { - mChildScreenContainers.remove(screenContainer) + override fun removeChildScreenContainer(container: ScreenContainer) { + mChildScreenContainers.remove(container) } - fun onViewAnimationStart() { + override fun onViewAnimationStart() { dispatchViewAnimationEvent(false) } - open fun onViewAnimationEnd() { + override fun onViewAnimationEnd() { dispatchViewAnimationEvent(true) } diff --git a/android/src/main/java/com/swmansion/rnscreens/ScreenFragmentWrapper.kt b/android/src/main/java/com/swmansion/rnscreens/ScreenFragmentWrapper.kt new file mode 100644 index 0000000000..4d507149b9 --- /dev/null +++ b/android/src/main/java/com/swmansion/rnscreens/ScreenFragmentWrapper.kt @@ -0,0 +1,22 @@ +package com.swmansion.rnscreens + +import android.app.Activity +import com.facebook.react.bridge.ReactContext + +interface ScreenFragmentWrapper : FragmentHolder, ScreenEventDispatcher { + var screen: Screen + + // Communication with container + val childScreenContainers: List + fun addChildScreenContainer(container: ScreenContainer) + fun removeChildScreenContainer(container: ScreenContainer) + fun onContainerUpdate() + + // Animation phase callbacks + fun onViewAnimationStart() + fun onViewAnimationEnd() + + // Helpers + fun tryGetActivity(): Activity? + fun tryGetContext(): ReactContext? +} diff --git a/android/src/main/java/com/swmansion/rnscreens/ScreenStack.kt b/android/src/main/java/com/swmansion/rnscreens/ScreenStack.kt index 6b4a0bceee..b1815e5bb3 100644 --- a/android/src/main/java/com/swmansion/rnscreens/ScreenStack.kt +++ b/android/src/main/java/com/swmansion/rnscreens/ScreenStack.kt @@ -11,19 +11,19 @@ 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() +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 mTopScreen: ScreenStackFragmentWrapper? = null private var mRemovalTransitionStarted = false private var isDetachingCurrentScreen = false private var reverseLastTwoChildren = false private var previousChildrenCount = 0 var goingForward = false - fun dismiss(screenFragment: ScreenStackFragment) { + fun dismiss(screenFragment: ScreenStackFragmentWrapper) { mDismissed.add(screenFragment) performUpdatesNow() } @@ -34,9 +34,9 @@ class ScreenStack(context: Context?) : ScreenContainer(cont val rootScreen: Screen get() { for (i in 0 until screenCount) { - val screen = getScreenAt(i) - if (!mDismissed.contains(screen.fragment)) { - return screen + val screenWrapper = getScreenFragmentWrapperAt(i) + if (!mDismissed.contains(screenWrapper)) { + return screenWrapper.screen } } throw IllegalStateException("Stack has no root screen set") @@ -71,7 +71,7 @@ class ScreenStack(context: Context?) : ScreenContainer(cont } override fun removeScreenAt(index: Int) { - mDismissed.remove(getScreenAt(index).fragment) + mDismissed.remove(getScreenFragmentWrapperAt(index)) super.removeScreenAt(index) } @@ -80,18 +80,18 @@ class ScreenStack(context: Context?) : ScreenContainer(cont super.removeAllScreens() } - override fun hasScreen(screenFragment: ScreenFragment?): Boolean = - super.hasScreen(screenFragment) && !mDismissed.contains(screenFragment) + override fun hasScreen(screenFragmentWrapper: ScreenFragmentWrapper?): Boolean = + super.hasScreen(screenFragmentWrapper) && !mDismissed.contains(screenFragmentWrapper) 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 + var newTop: ScreenFragmentWrapper? = null // newTop is nullable, see the above comment ^ + var visibleBottom: ScreenFragmentWrapper? = 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] + val screen = getScreenFragmentWrapperAt(i) if (!mDismissed.contains(screen)) { if (newTop == null) { newTop = screen @@ -178,43 +178,43 @@ class ScreenStack(context: Context?) : ScreenContainer(cont } // remove all screens previously on stack - for (screen in mStack) { - if (!mScreenFragments.contains(screen) || mDismissed.contains(screen)) { - it.remove(screen) + for (fragmentWrapper in mStack) { + if (!mScreenFragments.contains(fragmentWrapper) || mDismissed.contains(fragmentWrapper)) { + it.remove(fragmentWrapper.fragment) } } - for (screen in mScreenFragments) { + for (fragmentWrapper in mScreenFragments) { // Stop detaching screens when reaching visible bottom. All screens above bottom should be // visible. - if (screen === visibleBottom) { + if (fragmentWrapper === visibleBottom) { break } // detach all screens that should not be visible - if (screen !== newTop && !mDismissed.contains(screen)) { - it.remove(screen) + if (fragmentWrapper !== newTop && !mDismissed.contains(fragmentWrapper)) { + it.remove(fragmentWrapper.fragment) } } // attach screens that just became visible - if (visibleBottom != null && !visibleBottom.isAdded) { + if (visibleBottom != null && !visibleBottom.fragment.isAdded) { val top = newTop var beneathVisibleBottom = true - for (screen in mScreenFragments) { + for (fragmentWrapper in mScreenFragments) { // ignore all screens beneath the visible bottom if (beneathVisibleBottom) { - beneathVisibleBottom = if (screen === visibleBottom) { + beneathVisibleBottom = if (fragmentWrapper === visibleBottom) { false } else continue } // when first visible screen found, make all screens after that visible - it.add(id, screen).runOnCommit { top?.screen?.bringToFront() } + it.add(id, fragmentWrapper.fragment).runOnCommit { top?.screen?.bringToFront() } } - } else if (newTop != null && !newTop.isAdded) { - it.add(id, newTop) + } else if (newTop != null && !newTop.fragment.isAdded) { + it.add(id, newTop.fragment) } - mTopScreen = newTop + mTopScreen = newTop as? ScreenStackFragmentWrapper mStack.clear() - mStack.addAll(mScreenFragments) + mStack.addAll(mScreenFragments.map { it as ScreenStackFragmentWrapper }) turnOffA11yUnderTransparentScreen(visibleBottom) @@ -223,17 +223,17 @@ class ScreenStack(context: Context?) : ScreenContainer(cont } // only top visible screen should be accessible - private fun turnOffA11yUnderTransparentScreen(visibleBottom: ScreenStackFragment?) { + private fun turnOffA11yUnderTransparentScreen(visibleBottom: ScreenFragmentWrapper?) { 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) + for (fragmentWrapper in screenFragmentsBeneathTop) { + fragmentWrapper.screen.changeAccessibilityMode(IMPORTANT_FOR_ACCESSIBILITY_NO_HIDE_DESCENDANTS) // don't change a11y below non-transparent screens - if (screenFragment == visibleBottom) { + if (fragmentWrapper == visibleBottom) { break } } @@ -326,11 +326,11 @@ class ScreenStack(context: Context?) : ScreenContainer(cont } companion object { - private fun isTransparent(fragment: ScreenStackFragment): Boolean = - fragment.screen.stackPresentation === Screen.StackPresentation.TRANSPARENT_MODAL + private fun isTransparent(fragmentWrapper: ScreenFragmentWrapper): Boolean = + fragmentWrapper.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 + private fun needsDrawReordering(fragmentWrapper: ScreenFragmentWrapper): Boolean = + fragmentWrapper.screen.stackAnimation === StackAnimation.SLIDE_FROM_BOTTOM || + fragmentWrapper.screen.stackAnimation === StackAnimation.FADE_FROM_BOTTOM } } diff --git a/android/src/main/java/com/swmansion/rnscreens/ScreenStackFragment.kt b/android/src/main/java/com/swmansion/rnscreens/ScreenStackFragment.kt index 86a7654805..725bcf879a 100644 --- a/android/src/main/java/com/swmansion/rnscreens/ScreenStackFragment.kt +++ b/android/src/main/java/com/swmansion/rnscreens/ScreenStackFragment.kt @@ -20,7 +20,7 @@ import com.facebook.react.uimanager.PixelUtil import com.google.android.material.appbar.AppBarLayout import com.google.android.material.appbar.AppBarLayout.ScrollingViewBehavior -class ScreenStackFragment : ScreenFragment { +class ScreenStackFragment : ScreenFragment, ScreenStackFragmentWrapper { private var mAppBarLayout: AppBarLayout? = null private var mToolbar: Toolbar? = null private var mShadowHidden = false @@ -38,7 +38,7 @@ class ScreenStackFragment : ScreenFragment { ) } - fun removeToolbar() { + override fun removeToolbar() { mAppBarLayout?.let { mToolbar?.let { toolbar -> if (toolbar.parent === it) { @@ -49,7 +49,7 @@ class ScreenStackFragment : ScreenFragment { mToolbar = null } - fun setToolbar(toolbar: Toolbar) { + override fun setToolbar(toolbar: Toolbar) { mAppBarLayout?.addView(toolbar) toolbar.layoutParams = AppBarLayout.LayoutParams( AppBarLayout.LayoutParams.MATCH_PARENT, AppBarLayout.LayoutParams.WRAP_CONTENT @@ -57,14 +57,14 @@ class ScreenStackFragment : ScreenFragment { mToolbar = toolbar } - fun setToolbarShadowHidden(hidden: Boolean) { + override fun setToolbarShadowHidden(hidden: Boolean) { if (mShadowHidden != hidden) { mAppBarLayout?.targetElevation = if (hidden) 0f else PixelUtil.toPixelFromDIP(4f) mShadowHidden = hidden } } - fun setToolbarTranslucent(translucent: Boolean) { + override fun setToolbarTranslucent(translucent: Boolean) { if (mIsTranslucent != translucent) { val params = screen.layoutParams (params as CoordinatorLayout.LayoutParams).behavior = @@ -163,8 +163,8 @@ class ScreenStackFragment : ScreenFragment { } } - fun canNavigateBack(): Boolean { - val container: ScreenContainer<*>? = screen.container + override 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 @@ -180,8 +180,8 @@ class ScreenStackFragment : ScreenFragment { } } - fun dismiss() { - val container: ScreenContainer<*>? = screen.container + override fun dismiss() { + val container: ScreenContainer? = screen.container check(container is ScreenStack) { "ScreenStackFragment added into a non-stack container" } container.dismiss(this) } @@ -251,7 +251,7 @@ class ScreenStackFragment : ScreenFragment { 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) + mFragment.dispatchTransitionProgressEvent(interpolatedTime, !mFragment.isResumed) } } } diff --git a/android/src/main/java/com/swmansion/rnscreens/ScreenStackFragmentWrapper.kt b/android/src/main/java/com/swmansion/rnscreens/ScreenStackFragmentWrapper.kt new file mode 100644 index 0000000000..ec9a71ab55 --- /dev/null +++ b/android/src/main/java/com/swmansion/rnscreens/ScreenStackFragmentWrapper.kt @@ -0,0 +1,15 @@ +package com.swmansion.rnscreens + +import androidx.appcompat.widget.Toolbar + +interface ScreenStackFragmentWrapper : ScreenFragmentWrapper { + // Toolbar management + fun removeToolbar() + fun setToolbar(toolbar: Toolbar) + fun setToolbarShadowHidden(hidden: Boolean) + fun setToolbarTranslucent(translucent: Boolean) + + // Navigation + fun canNavigateBack(): Boolean + fun dismiss() +} diff --git a/android/src/main/java/com/swmansion/rnscreens/ScreenStackHeaderConfig.kt b/android/src/main/java/com/swmansion/rnscreens/ScreenStackHeaderConfig.kt index 1d44fa555c..ed1fd58196 100644 --- a/android/src/main/java/com/swmansion/rnscreens/ScreenStackHeaderConfig.kt +++ b/android/src/main/java/com/swmansion/rnscreens/ScreenStackHeaderConfig.kt @@ -141,7 +141,7 @@ class ScreenStackHeaderConfig(context: Context) : ViewGroup(context) { val reactContext = if (context is ReactContext) { context as ReactContext } else { - it.fragment?.tryGetContext() + it.fragmentWrapper?.tryGetContext() } ScreenWindowTraits.trySetWindowTraits(it, activity, reactContext) } diff --git a/android/src/main/java/com/swmansion/rnscreens/ScreenWindowTraits.kt b/android/src/main/java/com/swmansion/rnscreens/ScreenWindowTraits.kt index bdb232e64e..e9928a8f4f 100644 --- a/android/src/main/java/com/swmansion/rnscreens/ScreenWindowTraits.kt +++ b/android/src/main/java/com/swmansion/rnscreens/ScreenWindowTraits.kt @@ -259,7 +259,7 @@ object ScreenWindowTraits { screen: Screen?, trait: WindowTraits ): Screen? { - screen?.fragment?.let { + screen?.fragmentWrapper?.let { for (sc in it.childScreenContainers) { // we check only the top screen for the trait val topScreen = sc.topScreen diff --git a/android/src/main/java/com/swmansion/rnscreens/ScreensShadowNode.kt b/android/src/main/java/com/swmansion/rnscreens/ScreensShadowNode.kt index 6893aa1a4a..21531b3811 100644 --- a/android/src/main/java/com/swmansion/rnscreens/ScreensShadowNode.kt +++ b/android/src/main/java/com/swmansion/rnscreens/ScreensShadowNode.kt @@ -11,7 +11,7 @@ internal class ScreensShadowNode(private var mContext: ReactContext) : LayoutSha super.onBeforeLayout(nativeViewHierarchyOptimizer) (mContext.getNativeModule(UIManagerModule::class.java))?.addUIBlock { nativeViewHierarchyManager: NativeViewHierarchyManager -> val view = nativeViewHierarchyManager.resolveView(reactTag) - if (view is ScreenContainer<*>) { + if (view is ScreenContainer) { view.performUpdates() } }