Merge branch 'bluesky-social:main' into zh

zio/stable
Frudrax Cheng 2024-06-18 09:11:53 +08:00 committed by GitHub
commit 7548c23f19
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
12 changed files with 121 additions and 66 deletions

View File

@ -1,3 +1,29 @@
diff --git a/node_modules/react-native/Libraries/Text/TextInput/RCTBaseTextInputView.mm b/node_modules/react-native/Libraries/Text/TextInput/RCTBaseTextInputView.mm
index b0d71dc..9974932 100644
--- a/node_modules/react-native/Libraries/Text/TextInput/RCTBaseTextInputView.mm
+++ b/node_modules/react-native/Libraries/Text/TextInput/RCTBaseTextInputView.mm
@@ -377,10 +377,6 @@ - (void)textInputDidBeginEditing
self.backedTextInputView.attributedText = [NSAttributedString new];
}
- if (_selectTextOnFocus) {
- [self.backedTextInputView selectAll:nil];
- }
-
[_eventDispatcher sendTextEventWithType:RCTTextEventTypeFocus
reactTag:self.reactTag
text:[self.backedTextInputView.attributedText.string copy]
@@ -611,6 +607,10 @@ - (UIView *)reactAccessibilityElement
- (void)reactFocus
{
[self.backedTextInputView reactFocus];
+
+ if (_selectTextOnFocus) {
+ [self.backedTextInputView selectAll:nil];
+ }
}
- (void)reactBlur
diff --git a/node_modules/react-native/React/Views/RefreshControl/RCTRefreshControl.h b/node_modules/react-native/React/Views/RefreshControl/RCTRefreshControl.h diff --git a/node_modules/react-native/React/Views/RefreshControl/RCTRefreshControl.h b/node_modules/react-native/React/Views/RefreshControl/RCTRefreshControl.h
index e9b330f..1ecdf0a 100644 index e9b330f..1ecdf0a 100644
--- a/node_modules/react-native/React/Views/RefreshControl/RCTRefreshControl.h --- a/node_modules/react-native/React/Views/RefreshControl/RCTRefreshControl.h
@ -5,7 +31,7 @@ index e9b330f..1ecdf0a 100644
@@ -16,4 +16,6 @@ @@ -16,4 +16,6 @@
@property (nonatomic, copy) RCTDirectEventBlock onRefresh; @property (nonatomic, copy) RCTDirectEventBlock onRefresh;
@property (nonatomic, weak) UIScrollView *scrollView; @property (nonatomic, weak) UIScrollView *scrollView;
+- (void)forwarderBeginRefreshing; +- (void)forwarderBeginRefreshing;
+ +
@end @end
@ -16,7 +42,7 @@ index b09e653..4c32b31 100644
@@ -198,9 +198,53 @@ - (void)refreshControlValueChanged @@ -198,9 +198,53 @@ - (void)refreshControlValueChanged
[self setCurrentRefreshingState:super.refreshing]; [self setCurrentRefreshingState:super.refreshing];
_refreshingProgrammatically = NO; _refreshingProgrammatically = NO;
+ if (@available(iOS 17.4, *)) { + if (@available(iOS 17.4, *)) {
+ if (_currentRefreshingState) { + if (_currentRefreshingState) {
+ UIImpactFeedbackGenerator *feedbackGenerator = [[UIImpactFeedbackGenerator alloc] initWithStyle:UIImpactFeedbackStyleLight]; + UIImpactFeedbackGenerator *feedbackGenerator = [[UIImpactFeedbackGenerator alloc] initWithStyle:UIImpactFeedbackStyleLight];
@ -29,7 +55,7 @@ index b09e653..4c32b31 100644
_onRefresh(nil); _onRefresh(nil);
} }
} }
+/* +/*
+ This method is used by Bluesky's ExpoScrollForwarder. This allows other React Native + This method is used by Bluesky's ExpoScrollForwarder. This allows other React Native
+ libraries to perform a refresh of a scrollview and access the refresh control's onRefresh + libraries to perform a refresh of a scrollview and access the refresh control's onRefresh
@ -38,15 +64,15 @@ index b09e653..4c32b31 100644
+- (void)forwarderBeginRefreshing +- (void)forwarderBeginRefreshing
+{ +{
+ _refreshingProgrammatically = NO; + _refreshingProgrammatically = NO;
+ +
+ [self sizeToFit]; + [self sizeToFit];
+ +
+ if (!self.scrollView) { + if (!self.scrollView) {
+ return; + return;
+ } + }
+ +
+ UIScrollView *scrollView = (UIScrollView *)self.scrollView; + UIScrollView *scrollView = (UIScrollView *)self.scrollView;
+ +
+ [UIView animateWithDuration:0.3 + [UIView animateWithDuration:0.3
+ delay:0 + delay:0
+ options:UIViewAnimationOptionBeginFromCurrentState + options:UIViewAnimationOptionBeginFromCurrentState
@ -58,7 +84,7 @@ index b09e653..4c32b31 100644
+ completion:^(__unused BOOL finished) { + completion:^(__unused BOOL finished) {
+ [super beginRefreshing]; + [super beginRefreshing];
+ [self setCurrentRefreshingState:super.refreshing]; + [self setCurrentRefreshingState:super.refreshing];
+ +
+ if (self->_onRefresh) { + if (self->_onRefresh) {
+ self->_onRefresh(nil); + self->_onRefresh(nil);
+ } + }
@ -73,7 +99,7 @@ index 5f5e1ab..aac00b6 100644
+++ b/node_modules/react-native/ReactAndroid/src/main/java/com/facebook/react/modules/core/JavaTimerManager.java +++ b/node_modules/react-native/ReactAndroid/src/main/java/com/facebook/react/modules/core/JavaTimerManager.java
@@ -99,8 +99,9 @@ public class JavaTimerManager { @@ -99,8 +99,9 @@ public class JavaTimerManager {
} }
// If the JS thread is busy for multiple frames we cancel any other pending runnable. // If the JS thread is busy for multiple frames we cancel any other pending runnable.
- if (mCurrentIdleCallbackRunnable != null) { - if (mCurrentIdleCallbackRunnable != null) {
- mCurrentIdleCallbackRunnable.cancel(); - mCurrentIdleCallbackRunnable.cancel();
@ -81,5 +107,5 @@ index 5f5e1ab..aac00b6 100644
+ if (currentRunnable != null) { + if (currentRunnable != null) {
+ currentRunnable.cancel(); + currentRunnable.cancel();
} }
mCurrentIdleCallbackRunnable = new IdleCallbackRunnable(frameTimeNanos); mCurrentIdleCallbackRunnable = new IdleCallbackRunnable(frameTimeNanos);

View File

@ -11,3 +11,10 @@ in the RN repo: https://github.com/facebook/react-native/issues/43388
Patching `RCTRefreshControl.m` and `RCTRefreshControl.h` to add a new `forwarderBeginRefreshing` method to the class. Patching `RCTRefreshControl.m` and `RCTRefreshControl.h` to add a new `forwarderBeginRefreshing` method to the class.
This method is used by `ExpoScrollForwarder` to initiate a refresh of the underlying `UIScrollView` from inside that This method is used by `ExpoScrollForwarder` to initiate a refresh of the underlying `UIScrollView` from inside that
module. module.
## TextInput Patch - `selectTextOnFocus` fix
Patching `RCTBaseTextInputView.m` to fix an issue where `selectTextOnFocus` does not work as expected on iOS 17. This
patch _only_ fixes the Paper version of `TextInput`. If we migrate to Fabric and the fix has not been made upstream,
we can apply the same fix. See https://github.com/facebook/react-native/pull/44307.

View File

@ -14,7 +14,11 @@ import * as SplashScreen from 'expo-splash-screen'
import {msg} from '@lingui/macro' import {msg} from '@lingui/macro'
import {useLingui} from '@lingui/react' import {useLingui} from '@lingui/react'
import {Provider as StatsigProvider} from '#/lib/statsig/statsig' import {
initialize,
Provider as StatsigProvider,
tryFetchGates,
} from '#/lib/statsig/statsig'
import {logger} from '#/logger' import {logger} from '#/logger'
import {MessagesProvider} from '#/state/messages' import {MessagesProvider} from '#/state/messages'
import {init as initPersistedState} from '#/state/persisted' import {init as initPersistedState} from '#/state/persisted'
@ -69,6 +73,9 @@ function InnerApp() {
try { try {
if (account) { if (account) {
await resumeSession(account) await resumeSession(account)
} else {
await initialize()
await tryFetchGates(undefined, 'prefer-fresh-gates')
} }
} catch (e) { } catch (e) {
logger.error(`session: resume failed`, {message: e}) logger.error(`session: resume failed`, {message: e})

View File

@ -11,6 +11,7 @@ import {
useRemoveFeedMutation, useRemoveFeedMutation,
} from '#/state/queries/preferences' } from '#/state/queries/preferences'
import {sanitizeHandle} from 'lib/strings/handles' import {sanitizeHandle} from 'lib/strings/handles'
import {useSession} from 'state/session'
import {UserAvatar} from '#/view/com/util/UserAvatar' import {UserAvatar} from '#/view/com/util/UserAvatar'
import * as Toast from 'view/com/util/Toast' import * as Toast from 'view/com/util/Toast'
import {useTheme} from '#/alf' import {useTheme} from '#/alf'
@ -116,6 +117,12 @@ export function Likes({count}: {count: number}) {
} }
export function Action({uri, pin}: {uri: string; pin?: boolean}) { export function Action({uri, pin}: {uri: string; pin?: boolean}) {
const {hasSession} = useSession()
if (!hasSession) return null
return <ActionInner uri={uri} pin={pin} />
}
function ActionInner({uri, pin}: {uri: string; pin?: boolean}) {
const {_} = useLingui() const {_} = useLingui()
const {data: preferences} = usePreferencesQuery() const {data: preferences} = usePreferencesQuery()
const {isPending: isAddSavedFeedPending, mutateAsync: saveFeeds} = const {isPending: isAddSavedFeedPending, mutateAsync: saveFeeds} =

View File

@ -1,5 +1,6 @@
export type Gate = export type Gate =
// Keep this alphabetic please. // Keep this alphabetic please.
| 'native_pwi_disabled'
| 'request_notifications_permission_after_onboarding_v2' | 'request_notifications_permission_after_onboarding_v2'
| 'show_avi_follow_button' | 'show_avi_follow_button'
| 'show_follow_back_label_v2' | 'show_follow_back_label_v2'

View File

@ -14,6 +14,8 @@ import {useNonReactiveCallback} from '../hooks/useNonReactiveCallback'
import {LogEvents} from './events' import {LogEvents} from './events'
import {Gate} from './gates' import {Gate} from './gates'
const SDK_KEY = 'client-SXJakO39w9vIhl3D44u8UupyzFl4oZ2qPIkjwcvuPsV'
type StatsigUser = { type StatsigUser = {
userID: string | undefined userID: string | undefined
// TODO: Remove when enough users have custom.platform: // TODO: Remove when enough users have custom.platform:
@ -251,7 +253,7 @@ AppState.addEventListener('change', (state: AppStateStatus) => {
}) })
export async function tryFetchGates( export async function tryFetchGates(
did: string, did: string | undefined,
strategy: 'prefer-low-latency' | 'prefer-fresh-gates', strategy: 'prefer-low-latency' | 'prefer-fresh-gates',
) { ) {
try { try {
@ -275,6 +277,10 @@ export async function tryFetchGates(
} }
} }
export function initialize() {
return Statsig.initialize(SDK_KEY, null, createStatsigOptions([]))
}
export function Provider({children}: {children: React.ReactNode}) { export function Provider({children}: {children: React.ReactNode}) {
const {currentAccount, accounts} = useSession() const {currentAccount, accounts} = useSession()
const did = currentAccount?.did const did = currentAccount?.did
@ -320,7 +326,7 @@ export function Provider({children}: {children: React.ReactNode}) {
<GateCache.Provider value={gateCache}> <GateCache.Provider value={gateCache}>
<StatsigProvider <StatsigProvider
key={did} key={did}
sdkKey="client-SXJakO39w9vIhl3D44u8UupyzFl4oZ2qPIkjwcvuPsV" sdkKey={SDK_KEY}
mountKey={currentStatsigUser.userID} mountKey={currentStatsigUser.userID}
user={currentStatsigUser} user={currentStatsigUser}
// This isn't really blocking due to short initTimeoutMs above. // This isn't really blocking due to short initTimeoutMs above.

View File

@ -152,7 +152,7 @@ export function Layout({children}: React.PropsWithChildren<{}>) {
{children} {children}
</View> </View>
<View style={{height: 200}} /> <View style={{height: 400}} />
</View> </View>
</View> </View>
</ScrollView> </ScrollView>

View File

@ -5,7 +5,7 @@ import ViewShot from 'react-native-view-shot'
import {useAvatar} from '#/screens/Onboarding/StepProfile/index' import {useAvatar} from '#/screens/Onboarding/StepProfile/index'
import {atoms as a} from '#/alf' import {atoms as a} from '#/alf'
const SIZE_MULTIPLIER = 1.5 const SIZE_MULTIPLIER = 5
export interface PlaceholderCanvasRef { export interface PlaceholderCanvasRef {
capture: () => Promise<string> capture: () => Promise<string>

View File

@ -234,26 +234,28 @@ export function useGetPopularFeedsQuery(options?: GetPopularFeedsOptions) {
data: InfiniteData<AppBskyUnspeccedGetPopularFeedGenerators.OutputSchema>, data: InfiniteData<AppBskyUnspeccedGetPopularFeedGenerators.OutputSchema>,
) => { ) => {
const {savedFeeds, hasSession: hasSessionInner} = selectArgs const {savedFeeds, hasSession: hasSessionInner} = selectArgs
data?.pages.map(page => { return {
page.feeds = page.feeds.filter(feed => { ...data,
if ( pages: data.pages.map(page => {
!hasSessionInner && return {
KNOWN_AUTHED_ONLY_FEEDS.includes(feed.uri) ...page,
) { feeds: page.feeds.filter(feed => {
return false if (
} !hasSessionInner &&
const alreadySaved = Boolean( KNOWN_AUTHED_ONLY_FEEDS.includes(feed.uri)
savedFeeds?.find(f => { ) {
return f.value === feed.uri return false
}
const alreadySaved = Boolean(
savedFeeds?.find(f => {
return f.value === feed.uri
}),
)
return !alreadySaved
}), }),
) }
return !alreadySaved }),
}) }
return page
})
return data
}, },
[selectArgs /* Don't change. Everything needs to go into selectArgs. */], [selectArgs /* Don't change. Everything needs to go into selectArgs. */],
), ),

View File

@ -16,18 +16,17 @@ import {useModerationOpts} from '#/state/preferences/moderation-opts'
import {useGetPopularFeedsQuery} from '#/state/queries/feed' import {useGetPopularFeedsQuery} from '#/state/queries/feed'
import {usePreferencesQuery} from '#/state/queries/preferences' import {usePreferencesQuery} from '#/state/queries/preferences'
import {useSuggestedFollowsQuery} from '#/state/queries/suggested-follows' import {useSuggestedFollowsQuery} from '#/state/queries/suggested-follows'
import {useSession} from '#/state/session'
import {cleanError} from 'lib/strings/errors' import {cleanError} from 'lib/strings/errors'
import {ProfileCardWithFollowBtn} from '#/view/com/profile/ProfileCard' import {ProfileCardWithFollowBtn} from '#/view/com/profile/ProfileCard'
import {List} from '#/view/com/util/List' import {List} from '#/view/com/util/List'
import {UserAvatar} from '#/view/com/util/UserAvatar' import {UserAvatar} from '#/view/com/util/UserAvatar'
import {FeedSourceCard} from 'view/com/feeds/FeedSourceCard'
import { import {
FeedFeedLoadingPlaceholder, FeedFeedLoadingPlaceholder,
ProfileCardFeedLoadingPlaceholder, ProfileCardFeedLoadingPlaceholder,
} from 'view/com/util/LoadingPlaceholder' } from 'view/com/util/LoadingPlaceholder'
import {atoms as a, useTheme, ViewStyleProp} from '#/alf' import {atoms as a, useTheme, ViewStyleProp} from '#/alf'
import {Button} from '#/components/Button' import {Button} from '#/components/Button'
import * as FeedCard from '#/components/FeedCard'
import {ArrowBottom_Stroke2_Corner0_Rounded as ArrowBottom} from '#/components/icons/Arrow' import {ArrowBottom_Stroke2_Corner0_Rounded as ArrowBottom} from '#/components/icons/Arrow'
import {CircleInfo_Stroke2_Corner0_Rounded as CircleInfo} from '#/components/icons/CircleInfo' import {CircleInfo_Stroke2_Corner0_Rounded as CircleInfo} from '#/components/icons/CircleInfo'
import {Props as SVGIconProps} from '#/components/icons/common' import {Props as SVGIconProps} from '#/components/icons/common'
@ -271,7 +270,6 @@ type ExploreScreenItems =
export function Explore() { export function Explore() {
const {_} = useLingui() const {_} = useLingui()
const t = useTheme() const t = useTheme()
const {hasSession} = useSession()
const {data: preferences, error: preferencesError} = usePreferencesQuery() const {data: preferences, error: preferencesError} = usePreferencesQuery()
const moderationOpts = useModerationOpts() const moderationOpts = useModerationOpts()
const { const {
@ -480,15 +478,14 @@ export function Explore() {
} }
case 'feed': { case 'feed': {
return ( return (
<View style={[a.border_b, t.atoms.border_contrast_low]}> <View
<FeedSourceCard style={[
feedUri={item.feed.uri} a.border_b,
showSaveBtn={hasSession} t.atoms.border_contrast_low,
showDescription a.px_lg,
showLikes a.py_lg,
pinOnSave ]}>
hideTopBorder <FeedCard.Default feed={item.feed} />
/>
</View> </View>
) )
} }
@ -538,7 +535,7 @@ export function Explore() {
} }
} }
}, },
[t, hasSession, moderationOpts], [t, moderationOpts],
) )
return ( return (

View File

@ -30,7 +30,7 @@ import {makeProfileLink} from '#/lib/routes/links'
import {NavigationProp} from '#/lib/routes/types' import {NavigationProp} from '#/lib/routes/types'
import {augmentSearchQuery} from '#/lib/strings/helpers' import {augmentSearchQuery} from '#/lib/strings/helpers'
import {logger} from '#/logger' import {logger} from '#/logger'
import {isIOS, isNative, isWeb} from '#/platform/detection' import {isNative, isWeb} from '#/platform/detection'
import {listenSoftReset} from '#/state/events' import {listenSoftReset} from '#/state/events'
import {useModerationOpts} from '#/state/preferences/moderation-opts' import {useModerationOpts} from '#/state/preferences/moderation-opts'
import {useActorAutocompleteQuery} from '#/state/queries/actor-autocomplete' import {useActorAutocompleteQuery} from '#/state/queries/actor-autocomplete'
@ -57,8 +57,8 @@ import {Text} from '#/view/com/util/text/Text'
import {CenteredView, ScrollView} from '#/view/com/util/Views' import {CenteredView, ScrollView} from '#/view/com/util/Views'
import {Explore} from '#/view/screens/Search/Explore' import {Explore} from '#/view/screens/Search/Explore'
import {SearchLinkCard, SearchProfileCard} from '#/view/shell/desktop/Search' import {SearchLinkCard, SearchProfileCard} from '#/view/shell/desktop/Search'
import {FeedSourceCard} from 'view/com/feeds/FeedSourceCard' import {atoms as a, useTheme as useThemeNew} from '#/alf'
import {atoms as a} from '#/alf' import * as FeedCard from '#/components/FeedCard'
import {Menu_Stroke2_Corner0_Rounded as Menu} from '#/components/icons/Menu' import {Menu_Stroke2_Corner0_Rounded as Menu} from '#/components/icons/Menu'
function Loader() { function Loader() {
@ -285,8 +285,8 @@ let SearchScreenFeedsResults = ({
query: string query: string
active: boolean active: boolean
}): React.ReactNode => { }): React.ReactNode => {
const t = useThemeNew()
const {_} = useLingui() const {_} = useLingui()
const {hasSession} = useSession()
const {data: results, isFetched} = usePopularFeedsSearch({ const {data: results, isFetched} = usePopularFeedsSearch({
query, query,
@ -299,13 +299,15 @@ let SearchScreenFeedsResults = ({
<List <List
data={results} data={results}
renderItem={({item}) => ( renderItem={({item}) => (
<FeedSourceCard <View
feedUri={item.uri} style={[
showSaveBtn={hasSession} a.border_b,
showDescription t.atoms.border_contrast_low,
showLikes a.px_lg,
pinOnSave a.py_lg,
/> ]}>
<FeedCard.Default feed={item} />
</View>
)} )}
keyExtractor={item => item.uri} keyExtractor={item => item.uri}
// @ts-ignore web only -prf // @ts-ignore web only -prf
@ -802,12 +804,6 @@ let SearchInputBox = ({
}) })
} else { } else {
setShowAutocomplete(true) setShowAutocomplete(true)
if (isIOS) {
// We rely on selectTextOnFocus, but it's broken on iOS:
// https://github.com/facebook/react-native/issues/41988
textInput.current?.setSelection(0, searchText.length)
// We still rely on selectTextOnFocus for it to be instant on Android.
}
} }
}} }}
onChangeText={onChangeText} onChangeText={onChangeText}

View File

@ -29,7 +29,8 @@ import {
useLoggedOutView, useLoggedOutView,
useLoggedOutViewControls, useLoggedOutViewControls,
} from '#/state/shell/logged-out' } from '#/state/shell/logged-out'
import {isWeb} from 'platform/detection' import {useGate} from 'lib/statsig/statsig'
import {isNative, isWeb} from 'platform/detection'
import {Deactivated} from '#/screens/Deactivated' import {Deactivated} from '#/screens/Deactivated'
import {Onboarding} from '#/screens/Onboarding' import {Onboarding} from '#/screens/Onboarding'
import {SignupQueued} from '#/screens/SignupQueued' import {SignupQueued} from '#/screens/SignupQueued'
@ -50,6 +51,7 @@ function NativeStackNavigator({
screenOptions, screenOptions,
...rest ...rest
}: NativeStackNavigatorProps) { }: NativeStackNavigatorProps) {
const gate = useGate()
// --- this is copy and pasted from the original native stack navigator --- // --- this is copy and pasted from the original native stack navigator ---
const {state, descriptors, navigation, NavigationContent} = const {state, descriptors, navigation, NavigationContent} =
useNavigationBuilder< useNavigationBuilder<
@ -100,7 +102,11 @@ function NativeStackNavigator({
const {showLoggedOut} = useLoggedOutView() const {showLoggedOut} = useLoggedOutView()
const {setShowLoggedOut} = useLoggedOutViewControls() const {setShowLoggedOut} = useLoggedOutViewControls()
const {isMobile, isTabletOrMobile} = useWebMediaQueries() const {isMobile, isTabletOrMobile} = useWebMediaQueries()
if ((!PWI_ENABLED || activeRouteRequiresAuth) && !hasSession) { const isNativePWIDisabled = isNative && gate('native_pwi_disabled')
if (
(!PWI_ENABLED || isNativePWIDisabled || activeRouteRequiresAuth) &&
!hasSession
) {
return <LoggedOut /> return <LoggedOut />
} }
if (hasSession && currentAccount?.signupQueued) { if (hasSession && currentAccount?.signupQueued) {