React Native New Arch (#10980)

This commit is contained in:
Oleksii Bulenok
2026-07-23 17:25:40 +02:00
committed by GitHub
parent b80d09000f
commit 8d64ab9d4b
72 changed files with 2776 additions and 1596 deletions
+4 -2
View File
@@ -39,9 +39,9 @@ export const SplashScreen = ({
logoFill,
logoShadow: isDarkMode
? [
t.atoms.shadow_md,
{
shadowColor: logoFill,
shadowRadius: 8,
shadowOpacity: 0.5,
shadowOffset: {
width: 0,
@@ -93,13 +93,15 @@ export const SplashScreen = ({
size="large"
color={isDarkMode ? 'secondary_inverted' : 'secondary'}
style={[
t.atoms.shadow_md,
{
shadowColor: t.palette.black,
shadowRadius: 8,
shadowOpacity: 0.1,
shadowOffset: {
width: 0,
height: 5,
},
elevation: 16,
},
]}>
<ButtonText>
+3 -2
View File
@@ -25,6 +25,7 @@ import {KeyboardAvoidingView} from 'react-native-keyboard-controller'
import ProgressCircle from 'react-native-progress/Circle'
import Animated, {
type AnimatedRef,
type AnimatedStyle,
Easing,
FadeIn,
FadeOut,
@@ -1794,7 +1795,7 @@ function ComposerTopBar({
isEditingDraft: boolean
canSaveDraft: boolean
textLength: number
topBarAnimatedStyle: StyleProp<ViewStyle>
topBarAnimatedStyle: AnimatedStyle<ViewStyle>
children?: React.ReactNode
}) {
const t = useTheme()
@@ -2029,7 +2030,7 @@ function ComposerPills({
thread: ThreadDraft
post: PostDraft
dispatch: (action: ComposerAction) => void
bottomBarAnimatedStyle: StyleProp<ViewStyle>
bottomBarAnimatedStyle: AnimatedStyle<ViewStyle>
}) {
const t = useTheme()
const media = post.embed.media
@@ -227,7 +227,6 @@ export function TextInput({
allowFontScaling
multiline
scrollEnabled={false}
numberOfLines={2}
// Note: should be the default value, but as of v1.104
// it switched to "none" on Android
autoCapitalize="sentences"
+27 -13
View File
@@ -1,4 +1,12 @@
import {memo, useCallback, useEffect, useMemo, useRef, useState} from 'react'
import {
memo,
useCallback,
useEffect,
useImperativeHandle,
useMemo,
useRef,
useState,
} from 'react'
import {
ActivityIndicator,
AppState,
@@ -195,6 +203,10 @@ export function getItemsForFeedback(feedRow: FeedRow): {
}
}
export type PostFeedRef = {
refreshFeed: () => Promise<void>
}
// DISABLED need to check if this is causing random feed refreshes -prf
// const REFRESH_AFTER = STALE.HOURS.ONE
const CHECK_LATEST_AFTER = STALE.SECONDS.THIRTY
@@ -222,6 +234,7 @@ let PostFeed = ({
savedFeedConfig,
initialNumToRender: initialNumToRenderOverride,
isVideoFeed = false,
ref,
}: {
feed: FeedDescriptor
description?: RichTextType
@@ -246,6 +259,7 @@ let PostFeed = ({
initialNumToRender?: number
isVideoFeed?: boolean
lastFetchDate?: () => number
ref?: React.Ref<PostFeedRef>
}): React.ReactNode => {
const ax = useAnalytics()
const t = useTheme()
@@ -725,8 +739,9 @@ let PostFeed = ({
// events
// =
//
const onRefresh = useCallback(async () => {
const refreshFeed = async () => {
if (!enabled) return
ax.metric('feed:refresh', {
@@ -734,24 +749,23 @@ let PostFeed = ({
feedUrl: feed,
reason: 'pull-to-refresh',
})
setIsPTRing(true)
try {
await truncateAndInvalidate(queryClient, RQKEY(feed, feedParams))
onHasNew?.(false)
} catch (err) {
logger.error('Failed to refresh posts feed', {message: err})
}
}
const onRefresh = async () => {
setIsPTRing(true)
await refreshFeed()
setIsPTRing(false)
}, [
ax,
queryClient,
setIsPTRing,
onHasNew,
feed,
feedParams,
feedType,
enabled,
])
}
useImperativeHandle(ref, () => ({
refreshFeed,
}))
const onEndReached = useCallback(async () => {
if (isFetching || !hasNextPage || isError) return
+2
View File
@@ -3,6 +3,7 @@ import {type NativeScrollEvent} from 'react-native'
import {
clamp,
interpolate,
Reanimated3DefaultSpringConfig,
type SharedValue,
useAnimatedStyle,
useSharedValue,
@@ -105,6 +106,7 @@ export function MainScrollProvider({children}: {children: React.ReactNode}) {
'worklet'
headerMode.set(() =>
withSpring(v ? 1 : 0, {
...Reanimated3DefaultSpringConfig,
overshootClamping: true,
}),
)
+10 -2
View File
@@ -1,6 +1,9 @@
import {useCallback, useEffect, useLayoutEffect, useMemo, useRef} from 'react'
import {ActivityIndicator, StyleSheet} from 'react-native'
import {withSpring} from 'react-native-reanimated'
import {
Reanimated3DefaultSpringConfig,
withSpring,
} from 'react-native-reanimated'
import {useFocusEffect} from '@react-navigation/native'
import {PROD_DEFAULT_FEED} from '#/lib/constants'
@@ -147,7 +150,12 @@ function HomeScreenReady({
const headerMode = useHomeHeaderMode()
const showHeader = useCallback(() => {
'worklet'
headerMode.set(() => withSpring(0, {overshootClamping: true}))
headerMode.set(() =>
withSpring(0, {
...Reanimated3DefaultSpringConfig,
overshootClamping: true,
}),
)
}, [headerMode])
useFocusEffect(
+17 -3
View File
@@ -1,6 +1,7 @@
import {useCallback, useEffect, useMemo, useRef, useState} from 'react'
import {StyleSheet} from 'react-native'
import {SafeAreaView} from 'react-native-safe-area-context'
import {ScrollForwarderView} from 'react-native-scroll-forwarder/src'
import {
type AppBskyActorDefs,
moderateProfile,
@@ -36,6 +37,7 @@ import {useAgent, useSession} from '#/state/session'
import {ProfileFeedgens} from '#/view/com/feeds/ProfileFeedgens'
import {ProfileLists} from '#/view/com/lists/ProfileLists'
import {PagerWithHeader} from '#/view/com/pager/PagerWithHeader'
import {type PostFeedRef} from '#/view/com/posts/PostFeed'
import {ErrorScreen} from '#/view/com/util/error/ErrorScreen'
import {FAB} from '#/view/com/util/fab/FAB'
import {type ListRef} from '#/view/com/util/List'
@@ -53,7 +55,6 @@ import * as Layout from '#/components/Layout'
import {ScreenHider} from '#/components/moderation/ScreenHider'
import {ProfileStarterPacks} from '#/components/StarterPack/ProfileStarterPacks'
import {navigate} from '#/Navigation'
import {ExpoScrollForwarderView} from '../../../modules/expo-scroll-forwarder'
interface SectionRef {
scrollToTop: () => void
@@ -187,6 +188,7 @@ function ProfileScreenLoaded({
enabled: !!profile.associated?.labeler,
})
const [currentPage, setCurrentPage] = useState(0)
const [isRefreshing, setIsRefreshing] = useState(false)
const {_} = useLingui()
const [scrollViewTag, setScrollViewTag] = useState<number | null>(null)
@@ -353,6 +355,14 @@ function ProfileScreenLoaded({
],
})
const postFeedRef = useRef<PostFeedRef>(null)
const onRefresh = async () => {
setIsRefreshing(true)
await postFeedRef.current?.refreshFeed()
setIsRefreshing(false)
}
// rendering
// =
@@ -362,7 +372,10 @@ function ProfileScreenLoaded({
setMinimumHeight: (height: number) => void
}) => {
return (
<ExpoScrollForwarderView scrollViewTag={scrollViewTag}>
<ScrollForwarderView
scrollViewTag={scrollViewTag}
refreshing={isRefreshing}
onRefresh={onRefresh}>
<ProfileHeader
profile={profile}
labeler={labelerInfo}
@@ -372,7 +385,7 @@ function ProfileScreenLoaded({
isPlaceholderProfile={showPlaceholder}
setMinimumHeight={setMinimumHeight}
/>
</ExpoScrollForwarderView>
</ScrollForwarderView>
)
}
@@ -440,6 +453,7 @@ function ProfileScreenLoaded({
}
: undefined
}
postFeedRef={postFeedRef}
/>
)
: null}
+5
View File
@@ -2,6 +2,11 @@ import {useContext} from 'react'
import {DrawerGestureContext} from 'react-native-drawer-layout'
import {Gesture, GestureDetector} from 'react-native-gesture-handler'
/**
* BlockDrawerGesture must wrap the ScrollView directly - Gesture.Native()
* only works when attached to the natively scrollable view. On the new
* arch (Android), attaching it to a wrapper view breaks scrolling.
*/
export function BlockDrawerGesture({children}: {children: React.ReactNode}) {
const drawerGesture = useContext(DrawerGestureContext) ?? Gesture.Native() // noop for web
let scrollGesture = Gesture.Native()