Bluesky app fork with some witchin' additions 💫
0
fork

Configure Feed

Select the types of activity you want to include in your feed.

[Nicer Tabs] Fork TabBar, simplify Pager (#6762)

* Fork TabBar.web.tsx

* Trim dead code from both forks

* Remove onPageSelecting event

It's difficult to tell what exactly it's supposed to represent, and in practice it's not really used aside from logging. Let's rip it out for now to keep other changes simpler.

* Remove early onPageSelected call

It was added to try to do some work eagerly when we're sure which way the scroll is snapping. This is not necessarily a good idea though. It schedules a potentially expensive re-render right during the deceleration animation, which is not great. Whatever we're optimizing there, we should optimize smarter (e.g. prewarm just the network call). The other thing it used to help with is triggering the pager header autoscroll earlier. But we're going to rewrite that part differently anyway so that's not relevant either.

* Prune more dead code from the native version

We'll have to revisit this when adding tablet support but for now I'd prefer to remove a codepath that is not being tested or ever run.

* Use regular ScrollView on native

The Draggable thing was needed for web-only behavior so we can drop it in the native fork.

authored by

dan and committed by
GitHub
5a313c2d 996871d8

+216 -199
-6
src/lib/statsig/events.ts
··· 80 80 feedUrl: string 81 81 feedType: string 82 82 index: number 83 - reason: 84 - | 'focus' 85 - | 'tabbar-click' 86 - | 'pager-swipe' 87 - | 'desktop-sidebar-click' 88 - | 'starter-pack-initial-feed' 89 83 } 90 84 'feed:endReached': { 91 85 feedUrl: string
+5 -65
src/view/com/pager/Pager.tsx
··· 1 1 import React, {forwardRef} from 'react' 2 2 import {View} from 'react-native' 3 3 import PagerView, { 4 - PagerViewOnPageScrollEvent, 5 4 PagerViewOnPageSelectedEvent, 6 5 PageScrollStateChangedNativeEvent, 7 6 } from 'react-native-pager-view' 8 7 9 - import {LogEvents} from '#/lib/statsig/events' 10 8 import {atoms as a, native} from '#/alf' 11 9 12 10 export type PageSelectedEvent = PagerViewOnPageSelectedEvent 13 11 14 12 export interface PagerRef { 15 - setPage: ( 16 - index: number, 17 - reason: LogEvents['home:feedDisplayed']['reason'], 18 - ) => void 13 + setPage: (index: number) => void 19 14 } 20 15 21 16 export interface RenderTabBarFnProps { ··· 29 24 initialPage?: number 30 25 renderTabBar: RenderTabBarFn 31 26 onPageSelected?: (index: number) => void 32 - onPageSelecting?: ( 33 - index: number, 34 - reason: LogEvents['home:feedDisplayed']['reason'], 35 - ) => void 36 27 onPageScrollStateChanged?: ( 37 28 scrollState: 'idle' | 'dragging' | 'settling', 38 29 ) => void ··· 46 37 renderTabBar, 47 38 onPageScrollStateChanged, 48 39 onPageSelected, 49 - onPageSelecting, 50 40 testID, 51 41 }: React.PropsWithChildren<Props>, 52 42 ref, 53 43 ) { 54 44 const [selectedPage, setSelectedPage] = React.useState(0) 55 - const lastOffset = React.useRef(0) 56 - const lastDirection = React.useRef(0) 57 - const scrollState = React.useRef('') 58 45 const pagerView = React.useRef<PagerView>(null) 59 46 60 47 React.useImperativeHandle(ref, () => ({ 61 - setPage: ( 62 - index: number, 63 - reason: LogEvents['home:feedDisplayed']['reason'], 64 - ) => { 48 + setPage: (index: number) => { 65 49 pagerView.current?.setPage(index) 66 - onPageSelecting?.(index, reason) 67 50 }, 68 51 })) 69 52 ··· 75 58 [setSelectedPage, onPageSelected], 76 59 ) 77 60 78 - const onPageScroll = React.useCallback( 79 - (e: PagerViewOnPageScrollEvent) => { 80 - const {position, offset} = e.nativeEvent 81 - if (offset === 0) { 82 - // offset hits 0 in some awkward spots so we ignore it 83 - return 84 - } 85 - // NOTE 86 - // we want to call `onPageSelecting` as soon as the scroll-gesture 87 - // enters the "settling" phase, which means the user has released it 88 - // we can't infer directionality from the scroll information, so we 89 - // track the offset changes. if the offset delta is consistent with 90 - // the existing direction during the settling phase, we can say for 91 - // certain where it's going and can fire 92 - // -prf 93 - if (scrollState.current === 'settling') { 94 - if (lastDirection.current === -1 && offset < lastOffset.current) { 95 - onPageSelecting?.(position, 'pager-swipe') 96 - setSelectedPage(position) 97 - lastDirection.current = 0 98 - } else if ( 99 - lastDirection.current === 1 && 100 - offset > lastOffset.current 101 - ) { 102 - onPageSelecting?.(position + 1, 'pager-swipe') 103 - setSelectedPage(position + 1) 104 - lastDirection.current = 0 105 - } 106 - } else { 107 - if (offset < lastOffset.current) { 108 - lastDirection.current = -1 109 - } else if (offset > lastOffset.current) { 110 - lastDirection.current = 1 111 - } 112 - } 113 - lastOffset.current = offset 114 - }, 115 - [lastOffset, lastDirection, onPageSelecting], 116 - ) 117 - 118 61 const handlePageScrollStateChanged = React.useCallback( 119 62 (e: PageScrollStateChangedNativeEvent) => { 120 - scrollState.current = e.nativeEvent.pageScrollState 121 63 onPageScrollStateChanged?.(e.nativeEvent.pageScrollState) 122 64 }, 123 - [scrollState, onPageScrollStateChanged], 65 + [onPageScrollStateChanged], 124 66 ) 125 67 126 68 const onTabBarSelect = React.useCallback( 127 69 (index: number) => { 128 70 pagerView.current?.setPage(index) 129 - onPageSelecting?.(index, 'tabbar-click') 130 71 }, 131 - [pagerView, onPageSelecting], 72 + [pagerView], 132 73 ) 133 74 134 75 return ( ··· 142 83 style={[a.flex_1]} 143 84 initialPage={initialPage} 144 85 onPageScrollStateChanged={handlePageScrollStateChanged} 145 - onPageSelected={onPageSelectedInner} 146 - onPageScroll={onPageScroll}> 86 + onPageSelected={onPageSelectedInner}> 147 87 {children} 148 88 </PagerView> 149 89 </View>
+5 -15
src/view/com/pager/Pager.web.tsx
··· 2 2 import {View} from 'react-native' 3 3 import {flushSync} from 'react-dom' 4 4 5 - import {LogEvents} from '#/lib/statsig/events' 6 5 import {s} from '#/lib/styles' 7 6 8 7 export interface RenderTabBarFnProps { ··· 16 15 initialPage?: number 17 16 renderTabBar: RenderTabBarFn 18 17 onPageSelected?: (index: number) => void 19 - onPageSelecting?: ( 20 - index: number, 21 - reason: LogEvents['home:feedDisplayed']['reason'], 22 - ) => void 23 18 } 24 19 export const Pager = React.forwardRef(function PagerImpl( 25 20 { ··· 27 22 initialPage = 0, 28 23 renderTabBar, 29 24 onPageSelected, 30 - onPageSelecting, 31 25 }: React.PropsWithChildren<Props>, 32 26 ref, 33 27 ) { ··· 36 30 const anchorRef = React.useRef(null) 37 31 38 32 React.useImperativeHandle(ref, () => ({ 39 - setPage: ( 40 - index: number, 41 - reason: LogEvents['home:feedDisplayed']['reason'], 42 - ) => { 43 - onTabBarSelect(index, reason) 33 + setPage: (index: number) => { 34 + onTabBarSelect(index) 44 35 }, 45 36 })) 46 37 47 38 const onTabBarSelect = React.useCallback( 48 - (index: number, reason: LogEvents['home:feedDisplayed']['reason']) => { 39 + (index: number) => { 49 40 const scrollY = window.scrollY 50 41 // We want to determine if the tabbar is already "sticking" at the top (in which 51 42 // case we should preserve and restore scroll), or if it is somewhere below in the ··· 64 55 flushSync(() => { 65 56 setSelectedPage(index) 66 57 onPageSelected?.(index) 67 - onPageSelecting?.(index, reason) 68 58 }) 69 59 if (isSticking) { 70 60 const restoredScrollY = scrollYs.current[index] ··· 75 65 } 76 66 } 77 67 }, 78 - [selectedPage, setSelectedPage, onPageSelected, onPageSelecting], 68 + [selectedPage, setSelectedPage, onPageSelected], 79 69 ) 80 70 81 71 return ( ··· 83 73 {renderTabBar({ 84 74 selectedPage, 85 75 tabBarAnchor: <View ref={anchorRef} />, 86 - onSelect: e => onTabBarSelect(e, 'tabbar-click'), 76 + onSelect: e => onTabBarSelect(e), 87 77 })} 88 78 {React.Children.map(children, (child, i) => ( 89 79 <View style={selectedPage === i ? s.flex1 : s.hidden} key={`page-${i}`}>
-5
src/view/com/pager/PagerWithHeader.tsx
··· 182 182 [onPageSelected, setCurrentPage], 183 183 ) 184 184 185 - const onPageSelecting = React.useCallback((index: number) => { 186 - setCurrentPage(index) 187 - }, []) 188 - 189 185 return ( 190 186 <Pager 191 187 ref={ref} 192 188 testID={testID} 193 189 initialPage={initialPage} 194 190 onPageSelected={onPageSelectedInner} 195 - onPageSelecting={onPageSelecting} 196 191 renderTabBar={renderTabBar}> 197 192 {toArray(children) 198 193 .filter(Boolean)
-5
src/view/com/pager/PagerWithHeader.web.tsx
··· 75 75 [onPageSelected, setCurrentPage], 76 76 ) 77 77 78 - const onPageSelecting = React.useCallback((index: number) => { 79 - setCurrentPage(index) 80 - }, []) 81 - 82 78 return ( 83 79 <Pager 84 80 ref={ref} 85 81 testID={testID} 86 82 initialPage={initialPage} 87 83 onPageSelected={onPageSelectedInner} 88 - onPageSelecting={onPageSelecting} 89 84 renderTabBar={renderTabBar}> 90 85 {toArray(children) 91 86 .filter(Boolean)
+11 -91
src/view/com/pager/TabBar.tsx
··· 2 2 import {LayoutChangeEvent, ScrollView, StyleSheet, View} from 'react-native' 3 3 4 4 import {usePalette} from '#/lib/hooks/usePalette' 5 - import {useWebMediaQueries} from '#/lib/hooks/useWebMediaQueries' 6 - import {isNative} from '#/platform/detection' 7 5 import {PressableWithHover} from '../util/PressableWithHover' 8 6 import {Text} from '../util/text/Text' 9 - import {DraggableScrollView} from './DraggableScrollView' 10 7 11 8 export interface TabBarProps { 12 9 testID?: string ··· 31 28 }: TabBarProps) { 32 29 const pal = usePalette('default') 33 30 const scrollElRef = useRef<ScrollView>(null) 34 - const itemRefs = useRef<Array<Element>>([]) 35 31 const [itemXs, setItemXs] = useState<number[]>([]) 36 32 const indicatorStyle = useMemo( 37 33 () => ({borderBottomColor: indicatorColor || pal.colors.link}), 38 34 [indicatorColor, pal], 39 35 ) 40 - const {isDesktop, isTablet} = useWebMediaQueries() 41 - const styles = isDesktop || isTablet ? desktopStyles : mobileStyles 42 36 43 37 useEffect(() => { 44 - if (isNative) { 45 - // On native, the primary interaction is swiping. 46 - // We adjust the scroll little by little on every tab change. 47 - // Scroll into view but keep the end of the previous item visible. 48 - let x = itemXs[selectedPage] || 0 49 - x = Math.max(0, x - OFFSCREEN_ITEM_WIDTH) 50 - scrollElRef.current?.scrollTo({x}) 51 - } else { 52 - // On the web, the primary interaction is tapping. 53 - // Scrolling under tap feels disorienting so only adjust the scroll offset 54 - // when tapping on an item out of view--and we adjust by almost an entire page. 55 - const parent = scrollElRef?.current?.getScrollableNode?.() 56 - if (!parent) { 57 - return 58 - } 59 - const parentRect = parent.getBoundingClientRect() 60 - if (!parentRect) { 61 - return 62 - } 63 - const { 64 - left: parentLeft, 65 - right: parentRight, 66 - width: parentWidth, 67 - } = parentRect 68 - const child = itemRefs.current[selectedPage] 69 - if (!child) { 70 - return 71 - } 72 - const childRect = child.getBoundingClientRect?.() 73 - if (!childRect) { 74 - return 75 - } 76 - const {left: childLeft, right: childRight, width: childWidth} = childRect 77 - let dx = 0 78 - if (childRight >= parentRight) { 79 - dx += childRight - parentRight 80 - dx += parentWidth - childWidth - OFFSCREEN_ITEM_WIDTH 81 - } else if (childLeft <= parentLeft) { 82 - dx -= parentLeft - childLeft 83 - dx -= parentWidth - childWidth - OFFSCREEN_ITEM_WIDTH 84 - } 85 - let x = parent.scrollLeft + dx 86 - x = Math.max(0, x) 87 - x = Math.min(x, parent.scrollWidth - parentWidth) 88 - if (dx !== 0) { 89 - parent.scroll({ 90 - left: x, 91 - behavior: 'smooth', 92 - }) 93 - } 94 - } 95 - }, [scrollElRef, itemXs, selectedPage, styles]) 38 + // On native, the primary interaction is swiping. 39 + // We adjust the scroll little by little on every tab change. 40 + // Scroll into view but keep the end of the previous item visible. 41 + let x = itemXs[selectedPage] || 0 42 + x = Math.max(0, x - OFFSCREEN_ITEM_WIDTH) 43 + scrollElRef.current?.scrollTo({x}) 44 + }, [scrollElRef, itemXs, selectedPage]) 96 45 97 46 const onPressItem = useCallback( 98 47 (index: number) => { ··· 122 71 testID={testID} 123 72 style={[pal.view, styles.outer]} 124 73 accessibilityRole="tablist"> 125 - <DraggableScrollView 74 + <ScrollView 126 75 testID={`${testID}-selector`} 127 76 horizontal={true} 128 77 showsHorizontalScrollIndicator={false} ··· 134 83 <PressableWithHover 135 84 testID={`${testID}-selector-${i}`} 136 85 key={`${item}-${i}`} 137 - ref={node => (itemRefs.current[i] = node as any)} 138 86 onLayout={e => onItemLayout(e, i)} 139 87 style={styles.item} 140 88 hoverStyle={pal.viewLight} ··· 143 91 <View style={[styles.itemInner, selected && indicatorStyle]}> 144 92 <Text 145 93 emoji 146 - type={isDesktop || isTablet ? 'xl-bold' : 'lg-bold'} 94 + type="lg-bold" 147 95 testID={testID ? `${testID}-${item}` : undefined} 148 96 style={[ 149 97 selected ? pal.text : pal.textLight, ··· 155 103 </PressableWithHover> 156 104 ) 157 105 })} 158 - </DraggableScrollView> 106 + </ScrollView> 159 107 <View style={[pal.border, styles.outerBottomBorder]} /> 160 108 </View> 161 109 ) 162 110 } 163 111 164 - const desktopStyles = StyleSheet.create({ 165 - outer: { 166 - flexDirection: 'row', 167 - width: 598, 168 - }, 169 - contentContainer: { 170 - paddingHorizontal: 0, 171 - backgroundColor: 'transparent', 172 - }, 173 - item: { 174 - paddingTop: 14, 175 - paddingHorizontal: 14, 176 - justifyContent: 'center', 177 - }, 178 - itemInner: { 179 - paddingBottom: 12, 180 - borderBottomWidth: 3, 181 - borderBottomColor: 'transparent', 182 - }, 183 - outerBottomBorder: { 184 - position: 'absolute', 185 - left: 0, 186 - right: 0, 187 - top: '100%', 188 - borderBottomWidth: StyleSheet.hairlineWidth, 189 - }, 190 - }) 191 - 192 - const mobileStyles = StyleSheet.create({ 112 + const styles = StyleSheet.create({ 193 113 outer: { 194 114 flexDirection: 'row', 195 115 },
+192
src/view/com/pager/TabBar.web.tsx
··· 1 + import {useCallback, useEffect, useMemo, useRef} from 'react' 2 + import {ScrollView, StyleSheet, View} from 'react-native' 3 + 4 + import {usePalette} from '#/lib/hooks/usePalette' 5 + import {useWebMediaQueries} from '#/lib/hooks/useWebMediaQueries' 6 + import {PressableWithHover} from '../util/PressableWithHover' 7 + import {Text} from '../util/text/Text' 8 + import {DraggableScrollView} from './DraggableScrollView' 9 + 10 + export interface TabBarProps { 11 + testID?: string 12 + selectedPage: number 13 + items: string[] 14 + indicatorColor?: string 15 + onSelect?: (index: number) => void 16 + onPressSelected?: (index: number) => void 17 + } 18 + 19 + // How much of the previous/next item we're showing 20 + // to give the user a hint there's more to scroll. 21 + const OFFSCREEN_ITEM_WIDTH = 20 22 + 23 + export function TabBar({ 24 + testID, 25 + selectedPage, 26 + items, 27 + indicatorColor, 28 + onSelect, 29 + onPressSelected, 30 + }: TabBarProps) { 31 + const pal = usePalette('default') 32 + const scrollElRef = useRef<ScrollView>(null) 33 + const itemRefs = useRef<Array<Element>>([]) 34 + const indicatorStyle = useMemo( 35 + () => ({borderBottomColor: indicatorColor || pal.colors.link}), 36 + [indicatorColor, pal], 37 + ) 38 + const {isDesktop, isTablet} = useWebMediaQueries() 39 + const styles = isDesktop || isTablet ? desktopStyles : mobileStyles 40 + 41 + useEffect(() => { 42 + // On the web, the primary interaction is tapping. 43 + // Scrolling under tap feels disorienting so only adjust the scroll offset 44 + // when tapping on an item out of view--and we adjust by almost an entire page. 45 + const parent = scrollElRef?.current?.getScrollableNode?.() 46 + if (!parent) { 47 + return 48 + } 49 + const parentRect = parent.getBoundingClientRect() 50 + if (!parentRect) { 51 + return 52 + } 53 + const { 54 + left: parentLeft, 55 + right: parentRight, 56 + width: parentWidth, 57 + } = parentRect 58 + const child = itemRefs.current[selectedPage] 59 + if (!child) { 60 + return 61 + } 62 + const childRect = child.getBoundingClientRect?.() 63 + if (!childRect) { 64 + return 65 + } 66 + const {left: childLeft, right: childRight, width: childWidth} = childRect 67 + let dx = 0 68 + if (childRight >= parentRight) { 69 + dx += childRight - parentRight 70 + dx += parentWidth - childWidth - OFFSCREEN_ITEM_WIDTH 71 + } else if (childLeft <= parentLeft) { 72 + dx -= parentLeft - childLeft 73 + dx -= parentWidth - childWidth - OFFSCREEN_ITEM_WIDTH 74 + } 75 + let x = parent.scrollLeft + dx 76 + x = Math.max(0, x) 77 + x = Math.min(x, parent.scrollWidth - parentWidth) 78 + if (dx !== 0) { 79 + parent.scroll({ 80 + left: x, 81 + behavior: 'smooth', 82 + }) 83 + } 84 + }, [scrollElRef, selectedPage, styles]) 85 + 86 + const onPressItem = useCallback( 87 + (index: number) => { 88 + onSelect?.(index) 89 + if (index === selectedPage) { 90 + onPressSelected?.(index) 91 + } 92 + }, 93 + [onSelect, selectedPage, onPressSelected], 94 + ) 95 + 96 + return ( 97 + <View 98 + testID={testID} 99 + style={[pal.view, styles.outer]} 100 + accessibilityRole="tablist"> 101 + <DraggableScrollView 102 + testID={`${testID}-selector`} 103 + horizontal={true} 104 + showsHorizontalScrollIndicator={false} 105 + ref={scrollElRef} 106 + contentContainerStyle={styles.contentContainer}> 107 + {items.map((item, i) => { 108 + const selected = i === selectedPage 109 + return ( 110 + <PressableWithHover 111 + testID={`${testID}-selector-${i}`} 112 + key={`${item}-${i}`} 113 + ref={node => (itemRefs.current[i] = node as any)} 114 + style={styles.item} 115 + hoverStyle={pal.viewLight} 116 + onPress={() => onPressItem(i)} 117 + accessibilityRole="tab"> 118 + <View style={[styles.itemInner, selected && indicatorStyle]}> 119 + <Text 120 + emoji 121 + type={isDesktop || isTablet ? 'xl-bold' : 'lg-bold'} 122 + testID={testID ? `${testID}-${item}` : undefined} 123 + style={[ 124 + selected ? pal.text : pal.textLight, 125 + {lineHeight: 20}, 126 + ]}> 127 + {item} 128 + </Text> 129 + </View> 130 + </PressableWithHover> 131 + ) 132 + })} 133 + </DraggableScrollView> 134 + <View style={[pal.border, styles.outerBottomBorder]} /> 135 + </View> 136 + ) 137 + } 138 + 139 + const desktopStyles = StyleSheet.create({ 140 + outer: { 141 + flexDirection: 'row', 142 + width: 598, 143 + }, 144 + contentContainer: { 145 + paddingHorizontal: 0, 146 + backgroundColor: 'transparent', 147 + }, 148 + item: { 149 + paddingTop: 14, 150 + paddingHorizontal: 14, 151 + justifyContent: 'center', 152 + }, 153 + itemInner: { 154 + paddingBottom: 12, 155 + borderBottomWidth: 3, 156 + borderBottomColor: 'transparent', 157 + }, 158 + outerBottomBorder: { 159 + position: 'absolute', 160 + left: 0, 161 + right: 0, 162 + top: '100%', 163 + borderBottomWidth: StyleSheet.hairlineWidth, 164 + }, 165 + }) 166 + 167 + const mobileStyles = StyleSheet.create({ 168 + outer: { 169 + flexDirection: 'row', 170 + }, 171 + contentContainer: { 172 + backgroundColor: 'transparent', 173 + paddingHorizontal: 6, 174 + }, 175 + item: { 176 + paddingTop: 10, 177 + paddingHorizontal: 10, 178 + justifyContent: 'center', 179 + }, 180 + itemInner: { 181 + paddingBottom: 10, 182 + borderBottomWidth: 3, 183 + borderBottomColor: 'transparent', 184 + }, 185 + outerBottomBorder: { 186 + position: 'absolute', 187 + left: 0, 188 + right: 0, 189 + top: '100%', 190 + borderBottomWidth: StyleSheet.hairlineWidth, 191 + }, 192 + })
+3 -12
src/view/screens/Home.tsx
··· 11 11 HomeTabNavigatorParams, 12 12 NativeStackScreenProps, 13 13 } from '#/lib/routes/types' 14 - import {logEvent, LogEvents} from '#/lib/statsig/statsig' 14 + import {logEvent} from '#/lib/statsig/statsig' 15 15 import {isWeb} from '#/platform/detection' 16 16 import {emitSoftReset} from '#/state/events' 17 17 import {SavedFeedSourceInfo, usePinnedFeedsInfos} from '#/state/queries/feed' ··· 121 121 // This is supposed to only happen on the web when you use the right nav. 122 122 if (selectedIndex !== lastPagerReportedIndexRef.current) { 123 123 lastPagerReportedIndexRef.current = selectedIndex 124 - pagerRef.current?.setPage(selectedIndex, 'desktop-sidebar-click') 124 + pagerRef.current?.setPage(selectedIndex) 125 125 } 126 126 }, [selectedIndex]) 127 127 ··· 158 158 const feed = allFeeds[index] 159 159 setSelectedFeed(feed) 160 160 lastPagerReportedIndexRef.current = index 161 - }, 162 - [setDrawerSwipeDisabled, setSelectedFeed, setMinimalShellMode, allFeeds], 163 - ) 164 - 165 - const onPageSelecting = React.useCallback( 166 - (index: number, reason: LogEvents['home:feedDisplayed']['reason']) => { 167 - const feed = allFeeds[index] 168 161 logEvent('home:feedDisplayed', { 169 162 index, 170 163 feedType: feed.split('|')[0], 171 164 feedUrl: feed, 172 - reason, 173 165 }) 174 166 }, 175 - [allFeeds], 167 + [setDrawerSwipeDisabled, setSelectedFeed, setMinimalShellMode, allFeeds], 176 168 ) 177 169 178 170 const onPressSelected = React.useCallback(() => { ··· 228 220 ref={pagerRef} 229 221 testID="homeScreen" 230 222 initialPage={selectedIndex} 231 - onPageSelecting={onPageSelecting} 232 223 onPageSelected={onPageSelected} 233 224 onPageScrollStateChanged={onPageScrollStateChanged} 234 225 renderTabBar={renderTabBar}>