-
-
Notifications
You must be signed in to change notification settings - Fork 618
feat: add ScrollViewMarker experimental component
#3674
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Changes from all commits
61ee732
32d2b41
7400317
c410eb8
5062401
162d9eb
69ea950
d8fd359
68e8e46
a84419e
f99faa4
e970c97
75a6133
9b3ec59
7d1553b
724b376
0386139
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,88 @@ | ||
| package com.swmansion.rnscreens.gamma.scrollviewmarker | ||
|
|
||
| import android.annotation.SuppressLint | ||
| import android.view.ViewGroup | ||
| import android.widget.ScrollView | ||
| import androidx.core.view.children | ||
| import androidx.core.widget.NestedScrollView | ||
| import com.facebook.react.bridge.UIManager | ||
| import com.facebook.react.bridge.UIManagerListener | ||
| import com.facebook.react.common.annotations.UnstableReactNativeAPI | ||
| import com.facebook.react.uimanager.ThemedReactContext | ||
| import com.facebook.react.uimanager.UIManagerHelper | ||
| import com.facebook.react.uimanager.common.UIManagerType | ||
| import com.facebook.react.views.view.ReactViewGroup | ||
|
|
||
| @OptIn(UnstableReactNativeAPI::class) | ||
| @SuppressLint("ViewConstructor") // Should never be inflated / restored | ||
| class ScrollViewMarker( | ||
| private val reactContext: ThemedReactContext, | ||
| ) : ReactViewGroup(reactContext), | ||
| UIManagerListener { | ||
| init { | ||
| // We're adding ourselves during a batch, therefore we expect to receive its finalization callbacks | ||
| val uiManager = | ||
| checkNotNull(UIManagerHelper.getUIManager(reactContext, UIManagerType.FABRIC)) { | ||
| "[RNScreens] UIManager must not be null." | ||
| } | ||
| uiManager.addUIManagerEventListener(this) | ||
| } | ||
|
|
||
| private var hasAttemptedRegistration: Boolean = false | ||
|
|
||
| /** | ||
| * Currently we discover only ScrollView or NestedScrollView. | ||
| * It'll crash in case scroll view detection fails. | ||
| * | ||
| * Call it only after the children have been already attached and not yet detached. | ||
| */ | ||
| private fun findScrollView(): ViewGroup { | ||
| val childScrollView = | ||
| checkNotNull(children.find { childView -> childView is ScrollView || childView is NestedScrollView }) { | ||
| "[RNScreens] Failed to find supported type of ScrollView in children of ScrollViewMarker" | ||
| } | ||
|
|
||
| return childScrollView as ViewGroup | ||
| } | ||
|
|
||
| private fun findSeekingParent(): ScrollViewSeeking? { | ||
| var currentView = parent | ||
|
|
||
| while (currentView != null) { | ||
| if (currentView is ScrollViewSeeking) { | ||
| return currentView | ||
| } | ||
| currentView = currentView.parent | ||
| } | ||
|
|
||
| return null | ||
| } | ||
|
|
||
| private fun registerWithSeekingAncestor() { | ||
| val scrollView = findScrollView() | ||
| findSeekingParent()?.registerScrollView(this, scrollView) | ||
| } | ||
|
|
||
| private fun maybeRegisterWithSeekingAncestor() { | ||
| if (hasAttemptedRegistration) { | ||
| return | ||
| } | ||
|
|
||
| registerWithSeekingAncestor() | ||
| hasAttemptedRegistration = true | ||
| } | ||
|
|
||
| // UIManagerListener | ||
|
|
||
| override fun didMountItems(uiManager: UIManager) { | ||
| maybeRegisterWithSeekingAncestor() | ||
| } | ||
|
|
||
| override fun willDispatchViewUpdates(uiManager: UIManager) = Unit | ||
|
|
||
| override fun willMountItems(uiManager: UIManager) = Unit | ||
|
|
||
| override fun didDispatchMountItems(uiManager: UIManager) = Unit | ||
|
|
||
| override fun didScheduleMountItems(uiManager: UIManager) = Unit | ||
| } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,50 @@ | ||
| package com.swmansion.rnscreens.gamma.scrollviewmarker | ||
|
|
||
| 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.viewmanagers.RNSScrollViewMarkerManagerDelegate | ||
| import com.facebook.react.viewmanagers.RNSScrollViewMarkerManagerInterface | ||
|
|
||
| @ReactModule(name = ScrollViewMarkerViewManager.REACT_CLASS) | ||
| class ScrollViewMarkerViewManager : | ||
| ViewGroupManager<ScrollViewMarker>(), | ||
| RNSScrollViewMarkerManagerInterface<ScrollViewMarker> { | ||
| private val delegate: ViewManagerDelegate<ScrollViewMarker> = | ||
| RNSScrollViewMarkerManagerDelegate<ScrollViewMarker, ScrollViewMarkerViewManager>(this) | ||
|
|
||
| override fun getName() = REACT_CLASS | ||
|
|
||
| override fun getDelegate() = delegate | ||
|
|
||
| override fun createViewInstance(reactContext: ThemedReactContext): ScrollViewMarker = ScrollViewMarker(reactContext) | ||
|
|
||
| // iOS only | ||
| override fun setLeftScrollEdgeEffect( | ||
| view: ScrollViewMarker?, | ||
| value: String?, | ||
| ) = Unit | ||
|
|
||
| // iOS only | ||
| override fun setTopScrollEdgeEffect( | ||
| view: ScrollViewMarker?, | ||
| value: String?, | ||
| ) = Unit | ||
|
|
||
| // iOS only | ||
| override fun setRightScrollEdgeEffect( | ||
| view: ScrollViewMarker?, | ||
| value: String?, | ||
| ) = Unit | ||
|
|
||
| // iOS only | ||
| override fun setBottomScrollEdgeEffect( | ||
| view: ScrollViewMarker?, | ||
| value: String?, | ||
| ) = Unit | ||
|
|
||
| companion object { | ||
| const val REACT_CLASS = "RNSScrollViewMarker" | ||
| } | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,13 @@ | ||
| package com.swmansion.rnscreens.gamma.scrollviewmarker | ||
|
|
||
| import android.view.ViewGroup | ||
|
|
||
| interface ScrollViewSeeking { | ||
| // scrollView is a ViewGroup, because there is no universal ScrollView component on Android. | ||
| // It might a be ScrollView, NestedScrollView (different inheritance hierarchies) or any other | ||
| // ViewGroup that implements appropriate scrolling interfaces. | ||
| fun registerScrollView( | ||
| maker: ScrollViewMarker, | ||
| scrollView: ViewGroup, | ||
| ) | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,21 @@ | ||
| import Colors from '../styling/Colors'; | ||
|
|
||
| let GLOBAL_NEXT_COLOR_ID = 0; | ||
|
|
||
| export function generateNextColor() { | ||
| const colors = [ | ||
| Colors.BlueDark100, | ||
| Colors.GreenDark100, | ||
| Colors.RedDark100, | ||
| Colors.YellowDark100, | ||
| Colors.PurpleDark100, | ||
| Colors.BlueLight100, | ||
| Colors.GreenLight100, | ||
| Colors.RedLight100, | ||
| Colors.YellowLight100, | ||
| Colors.PurpleLight100, | ||
| ]; | ||
| const index = GLOBAL_NEXT_COLOR_ID; | ||
| GLOBAL_NEXT_COLOR_ID += 1; | ||
| return colors[index % colors.length]; | ||
| } |
| Original file line number | Diff line number | Diff line change | ||||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| @@ -0,0 +1,10 @@ | ||||||||||||||||||||||||||
| import { ScenarioGroup } from '../../shared/helpers'; | ||||||||||||||||||||||||||
| import TestSvmDetectsScrollView from './test-svm-configures-scroll-view'; | ||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||
| const ScrollViewMarkerScenarioGroup: ScenarioGroup = { | ||||||||||||||||||||||||||
| name: 'ScrollViewMarker scenarios', | ||||||||||||||||||||||||||
| details: 'Scenarios related to ScrollViewMarker component', | ||||||||||||||||||||||||||
| scenarios: [TestSvmDetectsScrollView], | ||||||||||||||||||||||||||
|
Comment on lines
+2
to
+7
|
||||||||||||||||||||||||||
| import TestSvmDetectsScrollView from './test-svm-configures-scroll-view'; | |
| const ScrollViewMarkerScenarioGroup: ScenarioGroup = { | |
| name: 'ScrollViewMarker scenarios', | |
| details: 'Scenarios related to ScrollViewMarker component', | |
| scenarios: [TestSvmDetectsScrollView], | |
| import TestSvmConfiguresScrollView from './test-svm-configures-scroll-view'; | |
| const ScrollViewMarkerScenarioGroup: ScenarioGroup = { | |
| name: 'ScrollViewMarker scenarios', | |
| details: 'Scenarios related to ScrollViewMarker component', | |
| scenarios: [TestSvmConfiguresScrollView], |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I see we're adding self as a listener, but we never remove, is this intentional?