Merge remote-tracking branch 'origin/explore-page-trending-and-starterpacks' into hailey/add-topic-screen
This commit is contained in:
@@ -105,3 +105,19 @@ jest.mock('expo-modules-core', () => ({
|
||||
return () => null
|
||||
}),
|
||||
}))
|
||||
|
||||
jest.mock('expo-localization', () => ({
|
||||
getLocales: () => [],
|
||||
}))
|
||||
|
||||
jest.mock('statsig-react-native-expo', () => ({
|
||||
Statsig: {
|
||||
initialize() {},
|
||||
initializeCalled() {
|
||||
return false
|
||||
},
|
||||
},
|
||||
}))
|
||||
|
||||
jest.mock('../src/lib/bitdrift', () => ({}))
|
||||
jest.mock('../src/lib/statsig/statsig', () => ({}))
|
||||
|
||||
+3
-4
@@ -120,7 +120,7 @@
|
||||
"emoji-mart": "^5.5.2",
|
||||
"emoji-regex": "^10.4.0",
|
||||
"eventemitter3": "^5.0.1",
|
||||
"expo": "~52.0.17",
|
||||
"expo": "~52.0.19",
|
||||
"expo-application": "^6.0.1",
|
||||
"expo-blur": "^14.0.1",
|
||||
"expo-build-properties": "^0.13.1",
|
||||
@@ -128,7 +128,7 @@
|
||||
"expo-clipboard": "^7.0.0",
|
||||
"expo-dev-client": "^5.0.4",
|
||||
"expo-device": "~7.0.1",
|
||||
"expo-file-system": "^18.0.4",
|
||||
"expo-file-system": "^18.0.6",
|
||||
"expo-font": "~13.0.1",
|
||||
"expo-haptics": "^14.0.0",
|
||||
"expo-image": "~2.0.3",
|
||||
@@ -141,7 +141,7 @@
|
||||
"expo-navigation-bar": "~4.0.4",
|
||||
"expo-notifications": "~0.29.11",
|
||||
"expo-sharing": "^13.0.0",
|
||||
"expo-splash-screen": "~0.29.16",
|
||||
"expo-splash-screen": "~0.29.18",
|
||||
"expo-status-bar": "~2.0.0",
|
||||
"expo-system-ui": "^4.0.4",
|
||||
"expo-task-manager": "~12.0.3",
|
||||
@@ -213,7 +213,6 @@
|
||||
"@babel/runtime": "^7.26.0",
|
||||
"@did-plc/server": "^0.0.1",
|
||||
"@expo/config-plugins": "9.0.10",
|
||||
"@expo/prebuild-config": "8.0.22",
|
||||
"@lingui/cli": "^4.14.1",
|
||||
"@lingui/macro": "^4.14.1",
|
||||
"@pmmmwh/react-refresh-webpack-plugin": "^0.5.15",
|
||||
|
||||
@@ -1,35 +0,0 @@
|
||||
diff --git a/node_modules/expo-modules-core/android/src/main/java/expo/modules/kotlin/devtools/ExpoNetworkInspectOkHttpInterceptors.kt b/node_modules/expo-modules-core/android/src/main/java/expo/modules/kotlin/devtools/ExpoNetworkInspectOkHttpInterceptors.kt
|
||||
index 47c4d15..afe138d 100644
|
||||
--- a/node_modules/expo-modules-core/android/src/main/java/expo/modules/kotlin/devtools/ExpoNetworkInspectOkHttpInterceptors.kt
|
||||
+++ b/node_modules/expo-modules-core/android/src/main/java/expo/modules/kotlin/devtools/ExpoNetworkInspectOkHttpInterceptors.kt
|
||||
@@ -125,6 +125,10 @@ internal fun peekResponseBody(
|
||||
}
|
||||
|
||||
internal fun shouldParseBody(response: Response): Boolean {
|
||||
+ if (response.request.url.encodedPath == "/bitdrift_public.protobuf.client.v1.ApiService/Mux") {
|
||||
+ return false
|
||||
+ }
|
||||
+
|
||||
// Check for Content-Type
|
||||
val skipContentTypes = listOf(
|
||||
"text/event-stream", // Server Sent Events
|
||||
diff --git a/node_modules/expo-modules-core/src/uuid/uuid.ts b/node_modules/expo-modules-core/src/uuid/uuid.ts
|
||||
index 148beac..fabdff5 100644
|
||||
--- a/node_modules/expo-modules-core/src/uuid/uuid.ts
|
||||
+++ b/node_modules/expo-modules-core/src/uuid/uuid.ts
|
||||
@@ -5,6 +5,7 @@ const nativeUuidv4 = globalThis?.expo?.uuidv4;
|
||||
const nativeUuidv5 = globalThis?.expo?.uuidv5;
|
||||
|
||||
function uuidv4(): string {
|
||||
+ const nativeUuidv4 = globalThis?.expo?.uuidv4;
|
||||
if (!nativeUuidv4) {
|
||||
throw Error(
|
||||
"Native UUID version 4 generator implementation wasn't found in `expo-modules-core`"
|
||||
@@ -23,6 +24,7 @@ function uuidv5(name: string, namespace: string | number[]) {
|
||||
throw new Error('`namespace` must be a valid UUID string or an Array of 16 byte values');
|
||||
}
|
||||
|
||||
+ const nativeUuidv5 = globalThis?.expo?.uuidv5;
|
||||
if (!nativeUuidv5) {
|
||||
throw Error("Native UUID type 5 generator implementation wasn't found in `expo-modules-core`");
|
||||
}
|
||||
@@ -0,0 +1,15 @@
|
||||
diff --git a/node_modules/expo-modules-core/android/src/main/java/expo/modules/kotlin/devtools/ExpoNetworkInspectOkHttpInterceptors.kt b/node_modules/expo-modules-core/android/src/main/java/expo/modules/kotlin/devtools/ExpoNetworkInspectOkHttpInterceptors.kt
|
||||
index 47c4d15..afe138d 100644
|
||||
--- a/node_modules/expo-modules-core/android/src/main/java/expo/modules/kotlin/devtools/ExpoNetworkInspectOkHttpInterceptors.kt
|
||||
+++ b/node_modules/expo-modules-core/android/src/main/java/expo/modules/kotlin/devtools/ExpoNetworkInspectOkHttpInterceptors.kt
|
||||
@@ -125,6 +125,10 @@ internal fun peekResponseBody(
|
||||
}
|
||||
|
||||
internal fun shouldParseBody(response: Response): Boolean {
|
||||
+ if (response.request.url.encodedPath == "/bitdrift_public.protobuf.client.v1.ApiService/Mux") {
|
||||
+ return false
|
||||
+ }
|
||||
+
|
||||
// Check for Content-Type
|
||||
val skipContentTypes = listOf(
|
||||
"text/event-stream", // Server Sent Events
|
||||
@@ -222,7 +222,7 @@ index 40aaf9c..1c60164 100644
|
||||
{
|
||||
[self.bridge.uiManager addUIBlock:^(RCTUIManager *uiManager, NSDictionary<NSNumber *, UIView *> *viewRegistry) {
|
||||
diff --git a/node_modules/react-native/React/Views/ScrollView/RCTScrollView.m b/node_modules/react-native/React/Views/ScrollView/RCTScrollView.m
|
||||
index e9ce48c..ccd9ad6 100644
|
||||
index e9ce48c..84a6fca 100644
|
||||
--- a/node_modules/react-native/React/Views/ScrollView/RCTScrollView.m
|
||||
+++ b/node_modules/react-native/React/Views/ScrollView/RCTScrollView.m
|
||||
@@ -159,26 +159,8 @@ - (BOOL)touchesShouldCancelInContentView:(__unused UIView *)view
|
||||
@@ -329,3 +329,38 @@ index e9ce48c..ccd9ad6 100644
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1055,6 +1082,22 @@ -(type)getter \
|
||||
RCT_SET_AND_PRESERVE_OFFSET(setShowsVerticalScrollIndicator, showsVerticalScrollIndicator, BOOL)
|
||||
RCT_SET_AND_PRESERVE_OFFSET(setZoomScale, zoomScale, CGFloat);
|
||||
|
||||
+- (void)setScrollIndicatorInsets:(UIEdgeInsets)value
|
||||
+{
|
||||
+ [_scrollView setScrollIndicatorInsets:value];
|
||||
+}
|
||||
+
|
||||
+- (UIEdgeInsets)scrollIndicatorInsets
|
||||
+{
|
||||
+ UIEdgeInsets verticalScrollIndicatorInsets = [_scrollView verticalScrollIndicatorInsets];
|
||||
+ UIEdgeInsets horizontalScrollIndicatorInsets = [_scrollView horizontalScrollIndicatorInsets];
|
||||
+ return UIEdgeInsetsMake(
|
||||
+ verticalScrollIndicatorInsets.top,
|
||||
+ horizontalScrollIndicatorInsets.left,
|
||||
+ verticalScrollIndicatorInsets.bottom,
|
||||
+ horizontalScrollIndicatorInsets.right);
|
||||
+}
|
||||
+
|
||||
- (void)setAutomaticallyAdjustsScrollIndicatorInsets:(BOOL)automaticallyAdjusts API_AVAILABLE(ios(13.0))
|
||||
{
|
||||
// `automaticallyAdjustsScrollIndicatorInsets` is available since iOS 13.
|
||||
diff --git a/node_modules/react-native/React/Views/ScrollView/RCTScrollViewManager.m b/node_modules/react-native/React/Views/ScrollView/RCTScrollViewManager.m
|
||||
index cd1e7eb..c1d0172 100644
|
||||
--- a/node_modules/react-native/React/Views/ScrollView/RCTScrollViewManager.m
|
||||
+++ b/node_modules/react-native/React/Views/ScrollView/RCTScrollViewManager.m
|
||||
@@ -83,6 +83,7 @@ - (UIView *)view
|
||||
RCT_EXPORT_VIEW_PROPERTY(scrollEventThrottle, NSTimeInterval)
|
||||
RCT_EXPORT_VIEW_PROPERTY(zoomScale, CGFloat)
|
||||
RCT_EXPORT_VIEW_PROPERTY(contentInset, UIEdgeInsets)
|
||||
+RCT_EXPORT_VIEW_PROPERTY(scrollIndicatorInsets, UIEdgeInsets)
|
||||
RCT_EXPORT_VIEW_PROPERTY(verticalScrollIndicatorInsets, UIEdgeInsets)
|
||||
RCT_EXPORT_VIEW_PROPERTY(scrollToOverflowEnabled, BOOL)
|
||||
RCT_EXPORT_VIEW_PROPERTY(snapToInterval, int)
|
||||
|
||||
@@ -302,6 +302,18 @@ export const atoms = {
|
||||
border_0: {
|
||||
borderWidth: 0,
|
||||
},
|
||||
border_t_0: {
|
||||
borderTopWidth: 0,
|
||||
},
|
||||
border_b_0: {
|
||||
borderBottomWidth: 0,
|
||||
},
|
||||
border_l_0: {
|
||||
borderLeftWidth: 0,
|
||||
},
|
||||
border_r_0: {
|
||||
borderRightWidth: 0,
|
||||
},
|
||||
border: {
|
||||
borderWidth: StyleSheet.hairlineWidth,
|
||||
},
|
||||
|
||||
@@ -280,8 +280,8 @@ export function ProfileGrid({
|
||||
profile={profile}
|
||||
moderationOpts={moderationOpts}
|
||||
logContext="FeedInterstitial"
|
||||
color="secondary_inverted"
|
||||
shape="round"
|
||||
colorInverted
|
||||
/>
|
||||
</ProfileCard.Header>
|
||||
<ProfileCard.Description profile={profile} numberOfLines={2} />
|
||||
|
||||
@@ -285,6 +285,7 @@ export type FollowButtonProps = {
|
||||
moderationOpts: ModerationOpts
|
||||
logContext: LogEvents['profile:follow']['logContext'] &
|
||||
LogEvents['profile:unfollow']['logContext']
|
||||
colorInverted?: boolean
|
||||
} & Partial<ButtonProps>
|
||||
|
||||
export function FollowButton(props: FollowButtonProps) {
|
||||
@@ -297,6 +298,8 @@ export function FollowButtonInner({
|
||||
profile: profileUnshadowed,
|
||||
moderationOpts,
|
||||
logContext,
|
||||
onPress: onPressProp,
|
||||
colorInverted,
|
||||
...rest
|
||||
}: FollowButtonProps) {
|
||||
const {_} = useLingui()
|
||||
@@ -321,6 +324,7 @@ export function FollowButtonInner({
|
||||
)}`,
|
||||
),
|
||||
)
|
||||
onPressProp?.(e)
|
||||
} catch (err: any) {
|
||||
if (err?.name !== 'AbortError') {
|
||||
Toast.show(_(msg`An issue occurred, please try again.`), 'xmark')
|
||||
@@ -341,6 +345,7 @@ export function FollowButtonInner({
|
||||
)}`,
|
||||
),
|
||||
)
|
||||
onPressProp?.(e)
|
||||
} catch (err: any) {
|
||||
if (err?.name !== 'AbortError') {
|
||||
Toast.show(_(msg`An issue occurred, please try again.`), 'xmark')
|
||||
@@ -387,7 +392,7 @@ export function FollowButtonInner({
|
||||
label={followLabel}
|
||||
size="small"
|
||||
variant="solid"
|
||||
color="primary"
|
||||
color={colorInverted ? 'secondary_inverted' : 'primary'}
|
||||
{...rest}
|
||||
onPress={onPressFollow}>
|
||||
<ButtonIcon icon={Plus} position={isRound ? undefined : 'left'} />
|
||||
|
||||
@@ -0,0 +1,829 @@
|
||||
import {memo, useCallback, useEffect, useMemo, useRef, useState} from 'react'
|
||||
import {ScrollView, TextInput, useWindowDimensions, View} from 'react-native'
|
||||
import Animated, {
|
||||
LayoutAnimationConfig,
|
||||
LinearTransition,
|
||||
ZoomInEasyDown,
|
||||
} from 'react-native-reanimated'
|
||||
import {AppBskyActorDefs, ModerationOpts} from '@atproto/api'
|
||||
import {msg, Trans} from '@lingui/macro'
|
||||
import {useLingui} from '@lingui/react'
|
||||
|
||||
import {useNonReactiveCallback} from '#/lib/hooks/useNonReactiveCallback'
|
||||
import {cleanError} from '#/lib/strings/errors'
|
||||
import {logger} from '#/logger'
|
||||
import {isWeb} from '#/platform/detection'
|
||||
import {useModerationOpts} from '#/state/preferences/moderation-opts'
|
||||
import {useActorSearchPaginated} from '#/state/queries/actor-search'
|
||||
import {usePreferencesQuery} from '#/state/queries/preferences'
|
||||
import {useSuggestedFollowsByActorQuery} from '#/state/queries/suggested-follows'
|
||||
import {useSession} from '#/state/session'
|
||||
import {Follow10ProgressGuide} from '#/state/shell/progress-guide'
|
||||
import {ListMethods} from '#/view/com/util/List'
|
||||
import {
|
||||
popularInterests,
|
||||
useInterestsDisplayNames,
|
||||
} from '#/screens/Onboarding/state'
|
||||
import {
|
||||
atoms as a,
|
||||
native,
|
||||
tokens,
|
||||
useBreakpoints,
|
||||
useTheme,
|
||||
ViewStyleProp,
|
||||
web,
|
||||
} from '#/alf'
|
||||
import {Button, ButtonIcon, ButtonText} from '#/components/Button'
|
||||
import * as Dialog from '#/components/Dialog'
|
||||
import {useInteractionState} from '#/components/hooks/useInteractionState'
|
||||
import {MagnifyingGlass2_Stroke2_Corner0_Rounded as SearchIcon} from '#/components/icons/MagnifyingGlass2'
|
||||
import {PersonGroup_Stroke2_Corner2_Rounded as PersonGroupIcon} from '#/components/icons/Person'
|
||||
import {TimesLarge_Stroke2_Corner0_Rounded as X} from '#/components/icons/Times'
|
||||
import * as ProfileCard from '#/components/ProfileCard'
|
||||
import {Text} from '#/components/Typography'
|
||||
import {ListFooter} from '../Lists'
|
||||
import {ProgressGuideTask} from './Task'
|
||||
|
||||
type Item =
|
||||
| {
|
||||
type: 'profile'
|
||||
key: string
|
||||
profile: AppBskyActorDefs.ProfileView
|
||||
isSuggestion: boolean
|
||||
}
|
||||
| {
|
||||
type: 'empty'
|
||||
key: string
|
||||
message: string
|
||||
}
|
||||
| {
|
||||
type: 'placeholder'
|
||||
key: string
|
||||
}
|
||||
| {
|
||||
type: 'error'
|
||||
key: string
|
||||
}
|
||||
|
||||
export function FollowDialog({guide}: {guide: Follow10ProgressGuide}) {
|
||||
const {_} = useLingui()
|
||||
const control = Dialog.useDialogControl()
|
||||
const {gtMobile} = useBreakpoints()
|
||||
const {height: minHeight} = useWindowDimensions()
|
||||
|
||||
return (
|
||||
<>
|
||||
<Button
|
||||
label={_(msg`Find people to follow`)}
|
||||
onPress={control.open}
|
||||
size={gtMobile ? 'small' : 'large'}
|
||||
color="primary"
|
||||
variant="solid">
|
||||
<ButtonIcon icon={PersonGroupIcon} />
|
||||
<ButtonText>
|
||||
<Trans>Find people to follow</Trans>
|
||||
</ButtonText>
|
||||
</Button>
|
||||
<Dialog.Outer control={control} nativeOptions={{minHeight}}>
|
||||
<Dialog.Handle />
|
||||
<DialogInner guide={guide} />
|
||||
</Dialog.Outer>
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
||||
// Fine to keep this top-level.
|
||||
let lastSelectedInterest = ''
|
||||
let lastSearchText = ''
|
||||
|
||||
function DialogInner({guide}: {guide: Follow10ProgressGuide}) {
|
||||
const {_} = useLingui()
|
||||
const interestsDisplayNames = useInterestsDisplayNames()
|
||||
const {data: preferences} = usePreferencesQuery()
|
||||
const personalizedInterests = preferences?.interests?.tags
|
||||
const interests = Object.keys(interestsDisplayNames)
|
||||
.sort(boostInterests(popularInterests))
|
||||
.sort(boostInterests(personalizedInterests))
|
||||
const [selectedInterest, setSelectedInterest] = useState(
|
||||
() =>
|
||||
lastSelectedInterest ||
|
||||
(personalizedInterests && interests.includes(personalizedInterests[0])
|
||||
? personalizedInterests[0]
|
||||
: interests[0]),
|
||||
)
|
||||
const [searchText, setSearchText] = useState(lastSearchText)
|
||||
const moderationOpts = useModerationOpts()
|
||||
const listRef = useRef<ListMethods>(null)
|
||||
const inputRef = useRef<TextInput>(null)
|
||||
const [headerHeight, setHeaderHeight] = useState(0)
|
||||
const {currentAccount} = useSession()
|
||||
const [suggestedAccounts, setSuggestedAccounts] = useState<
|
||||
Map<string, AppBskyActorDefs.ProfileView[]>
|
||||
>(() => new Map())
|
||||
|
||||
useEffect(() => {
|
||||
lastSearchText = searchText
|
||||
lastSelectedInterest = selectedInterest
|
||||
}, [searchText, selectedInterest])
|
||||
|
||||
const query = searchText || selectedInterest
|
||||
const {
|
||||
data: searchResults,
|
||||
isFetching,
|
||||
error,
|
||||
isError,
|
||||
hasNextPage,
|
||||
isFetchingNextPage,
|
||||
fetchNextPage,
|
||||
} = useActorSearchPaginated({
|
||||
query,
|
||||
})
|
||||
|
||||
const hasSearchText = !!searchText
|
||||
|
||||
const items = useMemo(() => {
|
||||
const results = searchResults?.pages.flatMap(r => r.actors)
|
||||
let _items: Item[] = []
|
||||
const seen = new Set<string>()
|
||||
|
||||
if (isError) {
|
||||
_items.push({
|
||||
type: 'empty',
|
||||
key: 'empty',
|
||||
message: _(msg`We're having network issues, try again`),
|
||||
})
|
||||
} else if (results) {
|
||||
// First pass: search results
|
||||
for (const profile of results) {
|
||||
if (profile.did === currentAccount?.did) continue
|
||||
if (profile.viewer?.following) continue
|
||||
// my sincere apologies to Jake Gold - your bio is too keyword-filled and
|
||||
// your page-rank too high, so you're at the top of half the categories -sfn
|
||||
if (
|
||||
!hasSearchText &&
|
||||
profile.did === 'did:plc:tpg43qhh4lw4ksiffs4nbda3' &&
|
||||
// constrain to 'tech'
|
||||
selectedInterest !== 'tech'
|
||||
) {
|
||||
continue
|
||||
}
|
||||
seen.add(profile.did)
|
||||
_items.push({
|
||||
type: 'profile',
|
||||
// Don't share identity across tabs or typing attempts
|
||||
key: query + ':' + profile.did,
|
||||
profile,
|
||||
isSuggestion: false,
|
||||
})
|
||||
}
|
||||
// Second pass: suggestions
|
||||
_items = _items.flatMap(item => {
|
||||
if (item.type !== 'profile') {
|
||||
return item
|
||||
}
|
||||
const suggestions = suggestedAccounts.get(item.profile.did)
|
||||
if (!suggestions) {
|
||||
return item
|
||||
}
|
||||
const itemWithSuggestions = [item]
|
||||
for (const suggested of suggestions) {
|
||||
if (seen.has(suggested.did)) {
|
||||
// Skip search results from previous step or already seen suggestions
|
||||
continue
|
||||
}
|
||||
seen.add(suggested.did)
|
||||
itemWithSuggestions.push({
|
||||
type: 'profile',
|
||||
key: suggested.did,
|
||||
profile: suggested,
|
||||
isSuggestion: true,
|
||||
})
|
||||
if (itemWithSuggestions.length === 1 + 3) {
|
||||
break
|
||||
}
|
||||
}
|
||||
return itemWithSuggestions
|
||||
})
|
||||
} else {
|
||||
const placeholders: Item[] = Array(10)
|
||||
.fill(0)
|
||||
.map((__, i) => ({
|
||||
type: 'placeholder',
|
||||
key: i + '',
|
||||
}))
|
||||
|
||||
_items.push(...placeholders)
|
||||
}
|
||||
|
||||
return _items
|
||||
}, [
|
||||
_,
|
||||
searchResults,
|
||||
isError,
|
||||
currentAccount?.did,
|
||||
hasSearchText,
|
||||
selectedInterest,
|
||||
suggestedAccounts,
|
||||
query,
|
||||
])
|
||||
|
||||
if (searchText && !isFetching && !items.length && !isError) {
|
||||
items.push({type: 'empty', key: 'empty', message: _(msg`No results`)})
|
||||
}
|
||||
|
||||
const renderItems = useCallback(
|
||||
({item, index}: {item: Item; index: number}) => {
|
||||
switch (item.type) {
|
||||
case 'profile': {
|
||||
return (
|
||||
<FollowProfileCard
|
||||
profile={item.profile}
|
||||
isSuggestion={item.isSuggestion}
|
||||
moderationOpts={moderationOpts!}
|
||||
setSuggestedAccounts={setSuggestedAccounts}
|
||||
noBorder={index === 0}
|
||||
/>
|
||||
)
|
||||
}
|
||||
case 'placeholder': {
|
||||
return <ProfileCardSkeleton key={item.key} />
|
||||
}
|
||||
case 'empty': {
|
||||
return <Empty key={item.key} message={item.message} />
|
||||
}
|
||||
default:
|
||||
return null
|
||||
}
|
||||
},
|
||||
[moderationOpts],
|
||||
)
|
||||
|
||||
const onSelectTab = useCallback(
|
||||
(interest: string) => {
|
||||
setSelectedInterest(interest)
|
||||
inputRef.current?.clear()
|
||||
setSearchText('')
|
||||
listRef.current?.scrollToOffset({
|
||||
offset: 0,
|
||||
animated: false,
|
||||
})
|
||||
},
|
||||
[setSelectedInterest, setSearchText],
|
||||
)
|
||||
|
||||
const listHeader = (
|
||||
<Header
|
||||
guide={guide}
|
||||
inputRef={inputRef}
|
||||
listRef={listRef}
|
||||
searchText={searchText}
|
||||
onSelectTab={onSelectTab}
|
||||
setHeaderHeight={setHeaderHeight}
|
||||
setSearchText={setSearchText}
|
||||
interests={interests}
|
||||
selectedInterest={selectedInterest}
|
||||
interestsDisplayNames={interestsDisplayNames}
|
||||
/>
|
||||
)
|
||||
|
||||
const onEndReached = useCallback(async () => {
|
||||
if (isFetchingNextPage || !hasNextPage || isError) return
|
||||
try {
|
||||
await fetchNextPage()
|
||||
} catch (err) {
|
||||
logger.error('Failed to load more people to follow', {message: err})
|
||||
}
|
||||
}, [isFetchingNextPage, hasNextPage, isError, fetchNextPage])
|
||||
|
||||
return (
|
||||
<Dialog.InnerFlatList
|
||||
ref={listRef}
|
||||
data={items}
|
||||
renderItem={renderItems}
|
||||
ListHeaderComponent={listHeader}
|
||||
stickyHeaderIndices={[0]}
|
||||
keyExtractor={(item: Item) => item.key}
|
||||
style={[
|
||||
a.px_0,
|
||||
web([a.py_0, {height: '100vh', maxHeight: 600}]),
|
||||
native({height: '100%'}),
|
||||
]}
|
||||
webInnerContentContainerStyle={a.py_0}
|
||||
webInnerStyle={[a.py_0, {maxWidth: 500, minWidth: 200}]}
|
||||
keyboardDismissMode="on-drag"
|
||||
scrollIndicatorInsets={{top: headerHeight}}
|
||||
initialNumToRender={8}
|
||||
maxToRenderPerBatch={8}
|
||||
onEndReached={onEndReached}
|
||||
itemLayoutAnimation={LinearTransition}
|
||||
ListFooterComponent={
|
||||
<ListFooter
|
||||
isFetchingNextPage={isFetchingNextPage}
|
||||
error={cleanError(error)}
|
||||
onRetry={fetchNextPage}
|
||||
/>
|
||||
}
|
||||
/>
|
||||
)
|
||||
}
|
||||
|
||||
let Header = ({
|
||||
guide,
|
||||
inputRef,
|
||||
listRef,
|
||||
searchText,
|
||||
onSelectTab,
|
||||
setHeaderHeight,
|
||||
setSearchText,
|
||||
interests,
|
||||
selectedInterest,
|
||||
interestsDisplayNames,
|
||||
}: {
|
||||
guide: Follow10ProgressGuide
|
||||
inputRef: React.RefObject<TextInput>
|
||||
listRef: React.RefObject<ListMethods>
|
||||
onSelectTab: (v: string) => void
|
||||
searchText: string
|
||||
setHeaderHeight: (v: number) => void
|
||||
setSearchText: (v: string) => void
|
||||
interests: string[]
|
||||
selectedInterest: string
|
||||
interestsDisplayNames: Record<string, string>
|
||||
}): React.ReactNode => {
|
||||
const t = useTheme()
|
||||
const control = Dialog.useDialogContext()
|
||||
return (
|
||||
<View
|
||||
onLayout={evt => setHeaderHeight(evt.nativeEvent.layout.height)}
|
||||
style={[
|
||||
a.relative,
|
||||
web(a.pt_lg),
|
||||
native(a.pt_4xl),
|
||||
a.pb_xs,
|
||||
a.border_b,
|
||||
t.atoms.border_contrast_low,
|
||||
t.atoms.bg,
|
||||
]}>
|
||||
<HeaderTop guide={guide} />
|
||||
|
||||
<View style={[web(a.pt_xs), a.pb_xs]}>
|
||||
<SearchInput
|
||||
inputRef={inputRef}
|
||||
defaultValue={searchText}
|
||||
onChangeText={text => {
|
||||
setSearchText(text)
|
||||
listRef.current?.scrollToOffset({offset: 0, animated: false})
|
||||
}}
|
||||
onEscape={control.close}
|
||||
/>
|
||||
<Tabs
|
||||
onSelectTab={onSelectTab}
|
||||
interests={interests}
|
||||
selectedInterest={selectedInterest}
|
||||
hasSearchText={!!searchText}
|
||||
interestsDisplayNames={interestsDisplayNames}
|
||||
/>
|
||||
</View>
|
||||
</View>
|
||||
)
|
||||
}
|
||||
Header = memo(Header)
|
||||
|
||||
function HeaderTop({guide}: {guide: Follow10ProgressGuide}) {
|
||||
const {_} = useLingui()
|
||||
const t = useTheme()
|
||||
const control = Dialog.useDialogContext()
|
||||
return (
|
||||
<View
|
||||
style={[
|
||||
a.px_lg,
|
||||
a.relative,
|
||||
a.flex_row,
|
||||
a.justify_between,
|
||||
a.align_center,
|
||||
]}>
|
||||
<Text
|
||||
style={[
|
||||
a.z_10,
|
||||
a.text_lg,
|
||||
a.font_heavy,
|
||||
a.leading_tight,
|
||||
t.atoms.text_contrast_high,
|
||||
]}>
|
||||
<Trans>Find people to follow</Trans>
|
||||
</Text>
|
||||
<View style={isWeb && {paddingRight: 36}}>
|
||||
<ProgressGuideTask
|
||||
current={guide.numFollows + 1}
|
||||
total={10 + 1}
|
||||
title={`${guide.numFollows} / 10`}
|
||||
tabularNumsTitle
|
||||
/>
|
||||
</View>
|
||||
{isWeb ? (
|
||||
<Button
|
||||
label={_(msg`Close`)}
|
||||
size="small"
|
||||
shape="round"
|
||||
variant={isWeb ? 'ghost' : 'solid'}
|
||||
color="secondary"
|
||||
style={[
|
||||
a.absolute,
|
||||
a.z_20,
|
||||
web({right: -4}),
|
||||
native({right: 0}),
|
||||
native({height: 32, width: 32, borderRadius: 16}),
|
||||
]}
|
||||
onPress={() => control.close()}>
|
||||
<ButtonIcon icon={X} size="md" />
|
||||
</Button>
|
||||
) : null}
|
||||
</View>
|
||||
)
|
||||
}
|
||||
|
||||
let Tabs = ({
|
||||
onSelectTab,
|
||||
interests,
|
||||
selectedInterest,
|
||||
hasSearchText,
|
||||
interestsDisplayNames,
|
||||
}: {
|
||||
onSelectTab: (tab: string) => void
|
||||
interests: string[]
|
||||
selectedInterest: string
|
||||
hasSearchText: boolean
|
||||
interestsDisplayNames: Record<string, string>
|
||||
}): React.ReactNode => {
|
||||
const listRef = useRef<ScrollView>(null)
|
||||
const [scrollX, setScrollX] = useState(0)
|
||||
const [totalWidth, setTotalWidth] = useState(0)
|
||||
const pendingTabOffsets = useRef<{x: number; width: number}[]>([])
|
||||
const [tabOffsets, setTabOffsets] = useState<{x: number; width: number}[]>([])
|
||||
|
||||
const onInitialLayout = useNonReactiveCallback(() => {
|
||||
const index = interests.indexOf(selectedInterest)
|
||||
scrollIntoViewIfNeeded(index)
|
||||
})
|
||||
|
||||
useEffect(() => {
|
||||
if (tabOffsets) {
|
||||
onInitialLayout()
|
||||
}
|
||||
}, [tabOffsets, onInitialLayout])
|
||||
|
||||
function scrollIntoViewIfNeeded(index: number) {
|
||||
const btnLayout = tabOffsets[index]
|
||||
if (!btnLayout) return
|
||||
|
||||
const viewportLeftEdge = scrollX
|
||||
const viewportRightEdge = scrollX + totalWidth
|
||||
const shouldScrollToLeftEdge = viewportLeftEdge > btnLayout.x
|
||||
const shouldScrollToRightEdge =
|
||||
viewportRightEdge < btnLayout.x + btnLayout.width
|
||||
|
||||
if (shouldScrollToLeftEdge) {
|
||||
listRef.current?.scrollTo({
|
||||
x: btnLayout.x - tokens.space.lg,
|
||||
animated: true,
|
||||
})
|
||||
} else if (shouldScrollToRightEdge) {
|
||||
listRef.current?.scrollTo({
|
||||
x: btnLayout.x - totalWidth + btnLayout.width + tokens.space.lg,
|
||||
animated: true,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
function handleSelectTab(index: number) {
|
||||
const tab = interests[index]
|
||||
onSelectTab(tab)
|
||||
scrollIntoViewIfNeeded(index)
|
||||
}
|
||||
|
||||
function handleTabLayout(index: number, x: number, width: number) {
|
||||
if (!tabOffsets.length) {
|
||||
pendingTabOffsets.current[index] = {x, width}
|
||||
if (pendingTabOffsets.current.length === interests.length) {
|
||||
setTabOffsets(pendingTabOffsets.current)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return (
|
||||
<ScrollView
|
||||
ref={listRef}
|
||||
horizontal
|
||||
contentContainerStyle={[a.gap_sm, a.px_lg]}
|
||||
showsHorizontalScrollIndicator={false}
|
||||
decelerationRate="fast"
|
||||
snapToOffsets={
|
||||
tabOffsets.length === interests.length
|
||||
? tabOffsets.map(o => o.x - tokens.space.xl)
|
||||
: undefined
|
||||
}
|
||||
onLayout={evt => setTotalWidth(evt.nativeEvent.layout.width)}
|
||||
scrollEventThrottle={200} // big throttle
|
||||
onScroll={evt => setScrollX(evt.nativeEvent.contentOffset.x)}>
|
||||
{interests.map((interest, i) => {
|
||||
const active = interest === selectedInterest && !hasSearchText
|
||||
return (
|
||||
<Tab
|
||||
key={interest}
|
||||
onSelectTab={handleSelectTab}
|
||||
active={active}
|
||||
index={i}
|
||||
interest={interest}
|
||||
interestsDisplayName={interestsDisplayNames[interest]}
|
||||
onLayout={handleTabLayout}
|
||||
/>
|
||||
)
|
||||
})}
|
||||
</ScrollView>
|
||||
)
|
||||
}
|
||||
Tabs = memo(Tabs)
|
||||
|
||||
let Tab = ({
|
||||
onSelectTab,
|
||||
interest,
|
||||
active,
|
||||
index,
|
||||
interestsDisplayName,
|
||||
onLayout,
|
||||
}: {
|
||||
onSelectTab: (index: number) => void
|
||||
interest: string
|
||||
active: boolean
|
||||
index: number
|
||||
interestsDisplayName: string
|
||||
onLayout: (index: number, x: number, width: number) => void
|
||||
}): React.ReactNode => {
|
||||
const {_} = useLingui()
|
||||
const activeText = active ? _(msg` (active)`) : ''
|
||||
return (
|
||||
<View
|
||||
key={interest}
|
||||
onLayout={e =>
|
||||
onLayout(index, e.nativeEvent.layout.x, e.nativeEvent.layout.width)
|
||||
}>
|
||||
<Button
|
||||
label={_(msg`Search for "${interestsDisplayName}"${activeText}`)}
|
||||
variant={active ? 'solid' : 'outline'}
|
||||
color={active ? 'primary' : 'secondary'}
|
||||
size="small"
|
||||
onPress={() => onSelectTab(index)}>
|
||||
<ButtonIcon icon={SearchIcon} />
|
||||
<ButtonText>{interestsDisplayName}</ButtonText>
|
||||
</Button>
|
||||
</View>
|
||||
)
|
||||
}
|
||||
Tab = memo(Tab)
|
||||
|
||||
let FollowProfileCard = ({
|
||||
profile,
|
||||
moderationOpts,
|
||||
isSuggestion,
|
||||
setSuggestedAccounts,
|
||||
noBorder,
|
||||
}: {
|
||||
profile: AppBskyActorDefs.ProfileView
|
||||
moderationOpts: ModerationOpts
|
||||
isSuggestion: boolean
|
||||
setSuggestedAccounts: (
|
||||
updater: (
|
||||
v: Map<string, AppBskyActorDefs.ProfileView[]>,
|
||||
) => Map<string, AppBskyActorDefs.ProfileView[]>,
|
||||
) => void
|
||||
noBorder?: boolean
|
||||
}): React.ReactNode => {
|
||||
const [hasFollowed, setHasFollowed] = useState(false)
|
||||
const followupSuggestion = useSuggestedFollowsByActorQuery({
|
||||
did: profile.did,
|
||||
enabled: hasFollowed,
|
||||
})
|
||||
const candidates = followupSuggestion.data?.suggestions
|
||||
|
||||
useEffect(() => {
|
||||
// TODO: Move out of effect.
|
||||
if (hasFollowed && candidates && candidates.length > 0) {
|
||||
setSuggestedAccounts(suggestions => {
|
||||
const newSuggestions = new Map(suggestions)
|
||||
newSuggestions.set(profile.did, candidates)
|
||||
return newSuggestions
|
||||
})
|
||||
}
|
||||
}, [hasFollowed, profile.did, candidates, setSuggestedAccounts])
|
||||
|
||||
return (
|
||||
<LayoutAnimationConfig skipEntering={!isSuggestion}>
|
||||
<Animated.View entering={native(ZoomInEasyDown)}>
|
||||
<FollowProfileCardInner
|
||||
profile={profile}
|
||||
moderationOpts={moderationOpts}
|
||||
onFollow={() => setHasFollowed(true)}
|
||||
noBorder={noBorder}
|
||||
/>
|
||||
</Animated.View>
|
||||
</LayoutAnimationConfig>
|
||||
)
|
||||
}
|
||||
FollowProfileCard = memo(FollowProfileCard)
|
||||
|
||||
function FollowProfileCardInner({
|
||||
profile,
|
||||
moderationOpts,
|
||||
onFollow,
|
||||
noBorder,
|
||||
}: {
|
||||
profile: AppBskyActorDefs.ProfileView
|
||||
moderationOpts: ModerationOpts
|
||||
onFollow?: () => void
|
||||
noBorder?: boolean
|
||||
}) {
|
||||
const control = Dialog.useDialogContext()
|
||||
const t = useTheme()
|
||||
return (
|
||||
<ProfileCard.Link
|
||||
profile={profile}
|
||||
style={[a.flex_1]}
|
||||
onPress={() => control.close()}>
|
||||
{({hovered, pressed}) => (
|
||||
<CardOuter
|
||||
style={[
|
||||
a.flex_1,
|
||||
noBorder && a.border_t_0,
|
||||
(hovered || pressed) && t.atoms.border_contrast_high,
|
||||
]}>
|
||||
<ProfileCard.Outer>
|
||||
<ProfileCard.Header>
|
||||
<ProfileCard.Avatar
|
||||
profile={profile}
|
||||
moderationOpts={moderationOpts}
|
||||
/>
|
||||
<ProfileCard.NameAndHandle
|
||||
profile={profile}
|
||||
moderationOpts={moderationOpts}
|
||||
/>
|
||||
<ProfileCard.FollowButton
|
||||
profile={profile}
|
||||
moderationOpts={moderationOpts}
|
||||
logContext="PostOnboardingFindFollows"
|
||||
shape="round"
|
||||
onPress={onFollow}
|
||||
colorInverted
|
||||
/>
|
||||
</ProfileCard.Header>
|
||||
<ProfileCard.Description profile={profile} numberOfLines={2} />
|
||||
</ProfileCard.Outer>
|
||||
</CardOuter>
|
||||
)}
|
||||
</ProfileCard.Link>
|
||||
)
|
||||
}
|
||||
|
||||
function CardOuter({
|
||||
children,
|
||||
style,
|
||||
}: {children: React.ReactNode | React.ReactNode[]} & ViewStyleProp) {
|
||||
const t = useTheme()
|
||||
return (
|
||||
<View
|
||||
style={[
|
||||
a.w_full,
|
||||
a.py_md,
|
||||
a.px_lg,
|
||||
a.border_t,
|
||||
t.atoms.border_contrast_low,
|
||||
style,
|
||||
]}>
|
||||
{children}
|
||||
</View>
|
||||
)
|
||||
}
|
||||
|
||||
function SearchInput({
|
||||
onChangeText,
|
||||
onEscape,
|
||||
inputRef,
|
||||
defaultValue,
|
||||
}: {
|
||||
onChangeText: (text: string) => void
|
||||
onEscape: () => void
|
||||
inputRef: React.RefObject<TextInput>
|
||||
defaultValue: string
|
||||
}) {
|
||||
const t = useTheme()
|
||||
const {_} = useLingui()
|
||||
const {
|
||||
state: hovered,
|
||||
onIn: onMouseEnter,
|
||||
onOut: onMouseLeave,
|
||||
} = useInteractionState()
|
||||
const {state: focused, onIn: onFocus, onOut: onBlur} = useInteractionState()
|
||||
const interacted = hovered || focused
|
||||
|
||||
return (
|
||||
<View
|
||||
{...web({
|
||||
onMouseEnter,
|
||||
onMouseLeave,
|
||||
})}
|
||||
style={[a.flex_row, a.align_center, a.gap_sm, a.px_lg, a.py_xs]}>
|
||||
<SearchIcon
|
||||
size="md"
|
||||
fill={interacted ? t.palette.primary_500 : t.palette.contrast_300}
|
||||
/>
|
||||
|
||||
<TextInput
|
||||
ref={inputRef}
|
||||
placeholder={_(msg`Search`)}
|
||||
defaultValue={defaultValue}
|
||||
onChangeText={onChangeText}
|
||||
onFocus={onFocus}
|
||||
onBlur={onBlur}
|
||||
style={[a.flex_1, a.py_md, a.text_md, t.atoms.text]}
|
||||
placeholderTextColor={t.palette.contrast_500}
|
||||
keyboardAppearance={t.name === 'light' ? 'light' : 'dark'}
|
||||
returnKeyType="search"
|
||||
clearButtonMode="while-editing"
|
||||
maxLength={50}
|
||||
onKeyPress={({nativeEvent}) => {
|
||||
if (nativeEvent.key === 'Escape') {
|
||||
onEscape()
|
||||
}
|
||||
}}
|
||||
autoCorrect={false}
|
||||
autoComplete="off"
|
||||
autoCapitalize="none"
|
||||
accessibilityLabel={_(msg`Search profiles`)}
|
||||
accessibilityHint={_(msg`Search profiles`)}
|
||||
/>
|
||||
</View>
|
||||
)
|
||||
}
|
||||
|
||||
function ProfileCardSkeleton() {
|
||||
const t = useTheme()
|
||||
|
||||
return (
|
||||
<View
|
||||
style={[
|
||||
a.flex_1,
|
||||
a.py_md,
|
||||
a.px_lg,
|
||||
a.gap_md,
|
||||
a.align_center,
|
||||
a.flex_row,
|
||||
]}>
|
||||
<View
|
||||
style={[
|
||||
a.rounded_full,
|
||||
{width: 42, height: 42},
|
||||
t.atoms.bg_contrast_25,
|
||||
]}
|
||||
/>
|
||||
|
||||
<View style={[a.flex_1, a.gap_sm]}>
|
||||
<View
|
||||
style={[
|
||||
a.rounded_xs,
|
||||
{width: 80, height: 14},
|
||||
t.atoms.bg_contrast_25,
|
||||
]}
|
||||
/>
|
||||
<View
|
||||
style={[
|
||||
a.rounded_xs,
|
||||
{width: 120, height: 10},
|
||||
t.atoms.bg_contrast_25,
|
||||
]}
|
||||
/>
|
||||
</View>
|
||||
</View>
|
||||
)
|
||||
}
|
||||
|
||||
function Empty({message}: {message: string}) {
|
||||
const t = useTheme()
|
||||
return (
|
||||
<View style={[a.p_lg, a.py_xl, a.align_center, a.gap_md]}>
|
||||
<Text style={[a.text_sm, a.italic, t.atoms.text_contrast_high]}>
|
||||
{message}
|
||||
</Text>
|
||||
|
||||
<Text style={[a.text_xs, t.atoms.text_contrast_low]}>(╯°□°)╯︵ ┻━┻</Text>
|
||||
</View>
|
||||
)
|
||||
}
|
||||
|
||||
function boostInterests(boosts?: string[]) {
|
||||
return (_a: string, _b: string) => {
|
||||
const indexA = boosts?.indexOf(_a) ?? -1
|
||||
const indexB = boosts?.indexOf(_b) ?? -1
|
||||
const rankA = indexA === -1 ? Infinity : indexA
|
||||
const rankB = indexB === -1 ? Infinity : indexB
|
||||
return rankA - rankB
|
||||
}
|
||||
}
|
||||
@@ -10,12 +10,15 @@ import {atoms as a, useTheme} from '#/alf'
|
||||
import {Button, ButtonIcon} from '#/components/Button'
|
||||
import {TimesLarge_Stroke2_Corner0_Rounded as Times} from '#/components/icons/Times'
|
||||
import {Text} from '#/components/Typography'
|
||||
import {FollowDialog} from './FollowDialog'
|
||||
import {ProgressGuideTask} from './Task'
|
||||
|
||||
export function ProgressGuideList({style}: {style?: StyleProp<ViewStyle>}) {
|
||||
const t = useTheme()
|
||||
const {_} = useLingui()
|
||||
const guide = useProgressGuide('like-10-and-follow-7')
|
||||
const followProgressGuide = useProgressGuide('follow-10')
|
||||
const followAndLikeProgressGuide = useProgressGuide('like-10-and-follow-7')
|
||||
const guide = followProgressGuide || followAndLikeProgressGuide
|
||||
const {endProgressGuide} = useProgressGuideControls()
|
||||
|
||||
if (guide) {
|
||||
@@ -41,18 +44,33 @@ export function ProgressGuideList({style}: {style?: StyleProp<ViewStyle>}) {
|
||||
<ButtonIcon icon={Times} size="sm" />
|
||||
</Button>
|
||||
</View>
|
||||
<ProgressGuideTask
|
||||
current={guide.numLikes + 1}
|
||||
total={10 + 1}
|
||||
title={_(msg`Like 10 posts`)}
|
||||
subtitle={_(msg`Teach our algorithm what you like`)}
|
||||
/>
|
||||
<ProgressGuideTask
|
||||
current={guide.numFollows + 1}
|
||||
total={7 + 1}
|
||||
title={_(msg`Follow 7 accounts`)}
|
||||
subtitle={_(msg`Bluesky is better with friends!`)}
|
||||
/>
|
||||
{guide.guide === 'follow-10' && (
|
||||
<>
|
||||
<ProgressGuideTask
|
||||
current={guide.numFollows + 1}
|
||||
total={10 + 1}
|
||||
title={_(msg`Follow 10 accounts`)}
|
||||
subtitle={_(msg`Bluesky is better with friends!`)}
|
||||
/>
|
||||
<FollowDialog guide={guide} />
|
||||
</>
|
||||
)}
|
||||
{guide.guide === 'like-10-and-follow-7' && (
|
||||
<>
|
||||
<ProgressGuideTask
|
||||
current={guide.numLikes + 1}
|
||||
total={10 + 1}
|
||||
title={_(msg`Like 10 posts`)}
|
||||
subtitle={_(msg`Teach our algorithm what you like`)}
|
||||
/>
|
||||
<ProgressGuideTask
|
||||
current={guide.numFollows + 1}
|
||||
total={7 + 1}
|
||||
title={_(msg`Follow 7 accounts`)}
|
||||
subtitle={_(msg`Bluesky is better with friends!`)}
|
||||
/>
|
||||
</>
|
||||
)}
|
||||
</View>
|
||||
)
|
||||
}
|
||||
|
||||
@@ -10,11 +10,13 @@ export function ProgressGuideTask({
|
||||
total,
|
||||
title,
|
||||
subtitle,
|
||||
tabularNumsTitle,
|
||||
}: {
|
||||
current: number
|
||||
total: number
|
||||
title: string
|
||||
subtitle?: string
|
||||
tabularNumsTitle?: boolean
|
||||
}) {
|
||||
const t = useTheme()
|
||||
|
||||
@@ -33,8 +35,16 @@ export function ProgressGuideTask({
|
||||
/>
|
||||
)}
|
||||
|
||||
<View style={[a.flex_col, a.gap_2xs, {marginTop: -2}]}>
|
||||
<Text style={[a.text_sm, a.font_bold, a.leading_tight]}>{title}</Text>
|
||||
<View style={[a.flex_col, a.gap_2xs, subtitle && {marginTop: -2}]}>
|
||||
<Text
|
||||
style={[
|
||||
a.text_sm,
|
||||
a.font_bold,
|
||||
a.leading_tight,
|
||||
tabularNumsTitle && {fontVariant: ['tabular-nums']},
|
||||
]}>
|
||||
{title}
|
||||
</Text>
|
||||
{subtitle && (
|
||||
<Text
|
||||
style={[a.text_sm, t.atoms.text_contrast_medium, a.leading_tight]}>
|
||||
|
||||
@@ -63,6 +63,7 @@ export function SearchablePeopleList({
|
||||
const {_} = useLingui()
|
||||
const moderationOpts = useModerationOpts()
|
||||
const control = Dialog.useDialogContext()
|
||||
const [headerHeight, setHeaderHeight] = useState(0)
|
||||
const listRef = useRef<ListMethods>(null)
|
||||
const {currentAccount} = useSession()
|
||||
const inputRef = useRef<TextInput>(null)
|
||||
@@ -237,6 +238,7 @@ export function SearchablePeopleList({
|
||||
const listHeader = useMemo(() => {
|
||||
return (
|
||||
<View
|
||||
onLayout={evt => setHeaderHeight(evt.nativeEvent.layout.height)}
|
||||
style={[
|
||||
a.relative,
|
||||
web(a.pt_lg),
|
||||
@@ -315,6 +317,7 @@ export function SearchablePeopleList({
|
||||
]}
|
||||
webInnerContentContainerStyle={a.py_0}
|
||||
webInnerStyle={[a.py_0, {maxWidth: 500, minWidth: 200}]}
|
||||
scrollIndicatorInsets={{top: headerHeight}}
|
||||
keyboardDismissMode="on-drag"
|
||||
/>
|
||||
)
|
||||
|
||||
@@ -26,16 +26,16 @@ import {Text} from '#/components/Typography'
|
||||
|
||||
export function TrendingInterstitial() {
|
||||
const {enabled} = useTrendingConfig()
|
||||
const {trendingDiscoverHidden} = useTrendingSettings()
|
||||
return !enabled ? null : trendingDiscoverHidden ? null : <Inner />
|
||||
const {trendingDisabled} = useTrendingSettings()
|
||||
return enabled && !trendingDisabled ? <Inner /> : null
|
||||
}
|
||||
|
||||
export function Inner() {
|
||||
const t = useTheme()
|
||||
const {_} = useLingui()
|
||||
const gutters = useGutters(['base'])
|
||||
const gutters = useGutters(['wide', 'base'])
|
||||
const trendingPrompt = Prompt.usePromptControl()
|
||||
const {setTrendingDiscoverHidden} = useTrendingSettingsApi()
|
||||
const {setTrendingDisabled} = useTrendingSettingsApi()
|
||||
const {data: trending, error, isLoading} = useTrendingTopics()
|
||||
const noTopics = !isLoading && !error && !trending?.topics?.length
|
||||
|
||||
@@ -63,7 +63,7 @@ export function Inner() {
|
||||
</View>
|
||||
|
||||
<Button
|
||||
label={_(msg`Hide trending topics from your feed`)}
|
||||
label={_(msg`Hide trending topics`)}
|
||||
size="tiny"
|
||||
variant="outline"
|
||||
color="secondary"
|
||||
@@ -73,7 +73,7 @@ export function Inner() {
|
||||
</Button>
|
||||
</View>
|
||||
|
||||
<View style={[a.flex_row, a.flex_wrap, a.gap_sm]}>
|
||||
<View style={[a.flex_row, a.flex_wrap, {rowGap: 8, columnGap: 6}]}>
|
||||
{isLoading ? (
|
||||
Array(TRENDING_TOPICS_COUNT)
|
||||
.fill(0)
|
||||
@@ -101,12 +101,10 @@ export function Inner() {
|
||||
|
||||
<Prompt.Basic
|
||||
control={trendingPrompt}
|
||||
title={_(msg`Hide trending topics in your feed?`)}
|
||||
description={_(
|
||||
msg`This is a device setting, and will apply to all accounts on this device. You can update this later from your settings.`,
|
||||
)}
|
||||
title={_(msg`Hide trending topics?`)}
|
||||
description={_(msg`You can update this later from your settings.`)}
|
||||
confirmButtonCta={_(msg`Hide`)}
|
||||
onConfirm={() => setTrendingDiscoverHidden(true)}
|
||||
onConfirm={() => setTrendingDisabled(true)}
|
||||
/>
|
||||
</View>
|
||||
)
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import {init} from '@bitdrift/react-native'
|
||||
import {Statsig} from 'statsig-react-native-expo'
|
||||
export {debug, error, info, warn} from '@bitdrift/react-native'
|
||||
|
||||
import {initPromise} from './statsig/statsig'
|
||||
|
||||
|
||||
@@ -0,0 +1,4 @@
|
||||
export function debug() {}
|
||||
export function error() {}
|
||||
export function info() {}
|
||||
export function warn() {}
|
||||
@@ -23,16 +23,6 @@ export const STARTER_PACK_MAX_SIZE = 150
|
||||
// -prf
|
||||
export const JOINED_THIS_WEEK = 2880000 // estimate as of 11/26/24
|
||||
|
||||
export const DISCOVER_DEBUG_DIDS: Record<string, true> = {
|
||||
'did:plc:oisofpd7lj26yvgiivf3lxsi': true, // hailey.at
|
||||
'did:plc:fpruhuo22xkm5o7ttr2ktxdo': true, // danabra.mov
|
||||
'did:plc:p2cp5gopk7mgjegy6wadk3ep': true, // samuel.bsky.team
|
||||
'did:plc:ragtjsm2j2vknwkz3zp4oxrd': true, // pfrazee.com
|
||||
'did:plc:vpkhqolt662uhesyj6nxm7ys': true, // why.bsky.team
|
||||
'did:plc:3jpt2mvvsumj2r7eqk4gzzjz': true, // esb.lol
|
||||
'did:plc:vjug55kidv6sye7ykr5faxxn': true, // emilyliu.me
|
||||
}
|
||||
|
||||
const BASE_FEEDBACK_FORM_URL = `${HELP_DESK_URL}/requests/new`
|
||||
export function FEEDBACK_FORM_URL({
|
||||
email,
|
||||
|
||||
@@ -162,6 +162,7 @@ export type LogEvents = {
|
||||
| 'StarterPackProfilesList'
|
||||
| 'FeedInterstitial'
|
||||
| 'ProfileHeaderSuggestedFollows'
|
||||
| 'PostOnboardingFindFollows'
|
||||
}
|
||||
'profile:unfollow': {
|
||||
logContext:
|
||||
@@ -177,6 +178,7 @@ export type LogEvents = {
|
||||
| 'StarterPackProfilesList'
|
||||
| 'FeedInterstitial'
|
||||
| 'ProfileHeaderSuggestedFollows'
|
||||
| 'PostOnboardingFindFollows'
|
||||
}
|
||||
'chat:create': {
|
||||
logContext: 'ProfileHeader' | 'NewChatDialog' | 'SendViaChatDialog'
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
export type Gate =
|
||||
// Keep this alphabetic please.
|
||||
| 'debug_show_feedcontext' // DISABLED DUE TO EME
|
||||
| 'post_feed_lang_window' // DISABLED DUE TO EME
|
||||
| 'debug_show_feedcontext'
|
||||
| 'debug_subscriptions'
|
||||
| 'new_postonboarding'
|
||||
| 'remove_show_latest_button'
|
||||
| 'trending_topics_beta'
|
||||
|
||||
@@ -5,6 +5,7 @@ import {sha256} from 'js-sha256'
|
||||
import {Statsig, StatsigProvider} from 'statsig-react-native-expo'
|
||||
|
||||
import {BUNDLE_DATE, BUNDLE_IDENTIFIER, IS_TESTFLIGHT} from '#/lib/app-info'
|
||||
import * as bitdrift from '#/lib/bitdrift'
|
||||
import {logger} from '#/logger'
|
||||
import {isWeb} from '#/platform/detection'
|
||||
import * as persisted from '#/state/persisted'
|
||||
@@ -97,19 +98,38 @@ export function logEvent<E extends keyof LogEvents>(
|
||||
rawMetadata: LogEvents[E] & FlatJSONRecord,
|
||||
) {
|
||||
try {
|
||||
const fullMetadata = {
|
||||
...rawMetadata,
|
||||
} as Record<string, string> // Statsig typings are unnecessarily strict here.
|
||||
const fullMetadata = toStringRecord(rawMetadata)
|
||||
fullMetadata.routeName = getCurrentRouteName() ?? '(Uninitialized)'
|
||||
if (Statsig.initializeCalled()) {
|
||||
Statsig.logEvent(eventName, null, fullMetadata)
|
||||
}
|
||||
// Intentionally bypass the logger abstraction to log rich objects.
|
||||
console.groupCollapsed(eventName)
|
||||
console.log(fullMetadata)
|
||||
console.groupEnd()
|
||||
bitdrift.info(eventName, fullMetadata)
|
||||
} catch (e) {
|
||||
// A log should never interrupt the calling code, whatever happens.
|
||||
logger.error('Failed to log an event', {message: e})
|
||||
}
|
||||
}
|
||||
|
||||
function toStringRecord<E extends keyof LogEvents>(
|
||||
metadata: LogEvents[E] & FlatJSONRecord,
|
||||
): Record<string, string> {
|
||||
const record: Record<string, string> = {}
|
||||
for (let key in metadata) {
|
||||
if (metadata.hasOwnProperty(key)) {
|
||||
if (typeof metadata[key] === 'string') {
|
||||
record[key] = metadata[key]
|
||||
} else {
|
||||
record[key] = JSON.stringify(metadata[key])
|
||||
}
|
||||
}
|
||||
}
|
||||
return record
|
||||
}
|
||||
|
||||
// We roll our own cache in front of Statsig because it is a singleton
|
||||
// and it's been difficult to get it to behave in a predictable way.
|
||||
// Our own cache ensures consistent evaluation within a single session.
|
||||
|
||||
@@ -3613,7 +3613,7 @@ msgstr "Escribe la tuya clau"
|
||||
#~ msgstr "Escribe lo tuyo furnidor d'aloch preferiu"
|
||||
|
||||
#: src/screens/Signup/StepHandle.tsx:114
|
||||
msgid "Input your user handle"
|
||||
msgid "Type your desired username"
|
||||
msgstr "Escribe lo tuyo identificador"
|
||||
|
||||
#: src/view/com/composer/threadgate/ThreadgateBtn.tsx:49
|
||||
@@ -8877,7 +8877,7 @@ msgid "Your following feed is empty! Follow more users to see what's happening."
|
||||
msgstr "La tuya canal de Seguindo ye vueda! Sigue a mas usuarios pa veyer las suyas publicacions aquí."
|
||||
|
||||
#: src/screens/Signup/StepHandle.tsx:125
|
||||
msgid "Your full handle will be"
|
||||
msgid "Your full username will be"
|
||||
msgstr "Lo tuyo identificador completo será"
|
||||
|
||||
#: src/screens/Settings/components/ChangeHandleDialog.tsx:219
|
||||
@@ -8921,5 +8921,5 @@ msgid "Your report will be sent to the Bluesky Moderation Service"
|
||||
msgstr "Lo tuyo reporte s'ha ninviau a lo servicio de moderación de Bluesky"
|
||||
|
||||
#: src/screens/Signup/index.tsx:142
|
||||
msgid "Your user handle"
|
||||
msgid "Choose your username"
|
||||
msgstr "Lo tuyo identificador"
|
||||
|
||||
@@ -3509,7 +3509,7 @@ msgstr ""
|
||||
#~ msgstr ""
|
||||
|
||||
#: src/screens/Signup/StepHandle.tsx:114
|
||||
msgid "Input your user handle"
|
||||
msgid "Type your desired username"
|
||||
msgstr ""
|
||||
|
||||
#: src/view/com/composer/threadgate/ThreadgateBtn.tsx:49
|
||||
@@ -8543,7 +8543,7 @@ msgid "Your following feed is empty! Follow more users to see what's happening."
|
||||
msgstr "¡El feed siguiente ta baleru! Sigui a más usuarios pa ver qué pasó."
|
||||
|
||||
#: src/screens/Signup/StepHandle.tsx:125
|
||||
msgid "Your full handle will be"
|
||||
msgid "Your full username will be"
|
||||
msgstr "L'identificador completu va ser"
|
||||
|
||||
#: src/screens/Settings/components/ChangeHandleDialog.tsx:219
|
||||
@@ -8587,5 +8587,5 @@ msgid "Your report will be sent to the Bluesky Moderation Service"
|
||||
msgstr "L'informe unvióse al serviciu de moderación de Bluesky"
|
||||
|
||||
#: src/screens/Signup/index.tsx:142
|
||||
msgid "Your user handle"
|
||||
msgid "Choose your username"
|
||||
msgstr ""
|
||||
|
||||
@@ -4268,7 +4268,7 @@ msgstr "Introdueix la teva contrasenya"
|
||||
#~ msgstr "Introdueix el teu proveïdor d'allotjament preferit"
|
||||
|
||||
#: src/screens/Signup/StepHandle.tsx:114
|
||||
msgid "Input your user handle"
|
||||
msgid "Type your desired username"
|
||||
msgstr "Introdueix el teu identificador d'usuari"
|
||||
|
||||
#: src/view/com/composer/threadgate/ThreadgateBtn.tsx:49
|
||||
@@ -10404,7 +10404,7 @@ msgid "Your following feed is empty! Follow more users to see what's happening."
|
||||
msgstr "El teu canal de seguint està buit! Segueix a més usuaris per a saber què està passant."
|
||||
|
||||
#: src/screens/Signup/StepHandle.tsx:125
|
||||
msgid "Your full handle will be"
|
||||
msgid "Your full username will be"
|
||||
msgstr "El teu identificador complet serà"
|
||||
|
||||
#: src/screens/Settings/components/ChangeHandleDialog.tsx:219
|
||||
@@ -10458,5 +10458,5 @@ msgid "Your report will be sent to the Bluesky Moderation Service"
|
||||
msgstr "El teu informe s'enviarà al servei de moderació de Bluesky"
|
||||
|
||||
#: src/screens/Signup/index.tsx:142
|
||||
msgid "Your user handle"
|
||||
msgid "Choose your username"
|
||||
msgstr "El teu identificador d'usuari"
|
||||
|
||||
@@ -3742,7 +3742,7 @@ msgstr "Gib dein Passwort ein"
|
||||
#~ msgstr ""
|
||||
|
||||
#: src/screens/Signup/StepHandle.tsx:114
|
||||
msgid "Input your user handle"
|
||||
msgid "Type your desired username"
|
||||
msgstr "Gib deinen Handle ein"
|
||||
|
||||
#: src/view/com/composer/threadgate/ThreadgateBtn.tsx:49
|
||||
@@ -9663,7 +9663,7 @@ msgid "Your following feed is empty! Follow more users to see what's happening."
|
||||
msgstr "Dein Following-Feed ist leer! Folge mehr Benutzern, um auf dem Laufenden zu bleiben."
|
||||
|
||||
#: src/screens/Signup/StepHandle.tsx:125
|
||||
msgid "Your full handle will be"
|
||||
msgid "Your full username will be"
|
||||
msgstr "Dein vollständiger Handle lautet"
|
||||
|
||||
#: src/screens/Settings/components/ChangeHandleDialog.tsx:219
|
||||
@@ -9707,5 +9707,5 @@ msgid "Your report will be sent to the Bluesky Moderation Service"
|
||||
msgstr ""
|
||||
|
||||
#: src/screens/Signup/index.tsx:142
|
||||
msgid "Your user handle"
|
||||
msgid "Choose your username"
|
||||
msgstr "Dein Benutzerhandle"
|
||||
|
||||
@@ -3509,7 +3509,7 @@ msgstr ""
|
||||
#~ msgstr ""
|
||||
|
||||
#: src/screens/Signup/StepHandle.tsx:114
|
||||
msgid "Input your user handle"
|
||||
msgid "Type your desired username"
|
||||
msgstr ""
|
||||
|
||||
#: src/view/com/composer/threadgate/ThreadgateBtn.tsx:49
|
||||
@@ -8543,7 +8543,7 @@ msgid "Your following feed is empty! Follow more users to see what's happening."
|
||||
msgstr ""
|
||||
|
||||
#: src/screens/Signup/StepHandle.tsx:125
|
||||
msgid "Your full handle will be"
|
||||
msgid "Your full username will be"
|
||||
msgstr ""
|
||||
|
||||
#: src/screens/Settings/components/ChangeHandleDialog.tsx:219
|
||||
@@ -8587,5 +8587,5 @@ msgid "Your report will be sent to the Bluesky Moderation Service"
|
||||
msgstr ""
|
||||
|
||||
#: src/screens/Signup/index.tsx:142
|
||||
msgid "Your user handle"
|
||||
msgid "Choose your username"
|
||||
msgstr ""
|
||||
|
||||
@@ -3509,7 +3509,7 @@ msgstr ""
|
||||
#~ msgstr ""
|
||||
|
||||
#: src/screens/Signup/StepHandle.tsx:114
|
||||
msgid "Input your user handle"
|
||||
msgid "Type your desired username"
|
||||
msgstr ""
|
||||
|
||||
#: src/view/com/composer/threadgate/ThreadgateBtn.tsx:49
|
||||
@@ -8543,7 +8543,7 @@ msgid "Your following feed is empty! Follow more users to see what's happening."
|
||||
msgstr ""
|
||||
|
||||
#: src/screens/Signup/StepHandle.tsx:125
|
||||
msgid "Your full handle will be"
|
||||
msgid "Your full username will be"
|
||||
msgstr ""
|
||||
|
||||
#: src/screens/Settings/components/ChangeHandleDialog.tsx:219
|
||||
@@ -8587,5 +8587,5 @@ msgid "Your report will be sent to the Bluesky Moderation Service"
|
||||
msgstr ""
|
||||
|
||||
#: src/screens/Signup/index.tsx:142
|
||||
msgid "Your user handle"
|
||||
msgid "Choose your username"
|
||||
msgstr ""
|
||||
|
||||
@@ -3896,7 +3896,7 @@ msgstr "Introduce tu contraseña"
|
||||
#~ msgstr "Introduce tu proveedor de alojamiento preferido"
|
||||
|
||||
#: src/screens/Signup/StepHandle.tsx:114
|
||||
msgid "Input your user handle"
|
||||
msgid "Type your desired username"
|
||||
msgstr "Introduce tu nombre de usuario"
|
||||
|
||||
#: src/view/com/composer/threadgate/ThreadgateBtn.tsx:49
|
||||
@@ -9518,7 +9518,7 @@ msgid "Your following feed is empty! Follow more users to see what's happening."
|
||||
msgstr "¡Tu feed de Siguiendo esta vacío! Sigue a más usuarios para ver sus posts aquí."
|
||||
|
||||
#: src/screens/Signup/StepHandle.tsx:125
|
||||
msgid "Your full handle will be"
|
||||
msgid "Your full username will be"
|
||||
msgstr "Tu nombre de usuario completo será"
|
||||
|
||||
#: src/screens/Settings/components/ChangeHandleDialog.tsx:219
|
||||
@@ -9562,5 +9562,5 @@ msgid "Your report will be sent to the Bluesky Moderation Service"
|
||||
msgstr "Tu reporte ha sido enviado al servicio de moderación de Bluesky"
|
||||
|
||||
#: src/screens/Signup/index.tsx:142
|
||||
msgid "Your user handle"
|
||||
msgid "Choose your username"
|
||||
msgstr "Tu nombre de usuario"
|
||||
|
||||
@@ -3970,7 +3970,7 @@ msgstr "Syötä salasanasi"
|
||||
#~ msgstr "Syötä haluamasi palveluntarjoaja"
|
||||
|
||||
#: src/screens/Signup/StepHandle.tsx:114
|
||||
msgid "Input your user handle"
|
||||
msgid "Type your desired username"
|
||||
msgstr "Syötä käyttäjätunnuksesi"
|
||||
|
||||
#: src/view/com/composer/threadgate/ThreadgateBtn.tsx:49
|
||||
@@ -9627,7 +9627,7 @@ msgid "Your following feed is empty! Follow more users to see what's happening."
|
||||
msgstr "Seuraamiesi syöte on tyhjä! Seuraa lisää käyttäjiä nähdäksesi, mitä tapahtuu."
|
||||
|
||||
#: src/screens/Signup/StepHandle.tsx:125
|
||||
msgid "Your full handle will be"
|
||||
msgid "Your full username will be"
|
||||
msgstr "Käyttäjätunnuksesi tulee olemaan"
|
||||
|
||||
#: src/screens/Settings/components/ChangeHandleDialog.tsx:219
|
||||
@@ -9671,5 +9671,5 @@ msgid "Your report will be sent to the Bluesky Moderation Service"
|
||||
msgstr ""
|
||||
|
||||
#: src/screens/Signup/index.tsx:142
|
||||
msgid "Your user handle"
|
||||
msgid "Choose your username"
|
||||
msgstr "Käyttäjätunnuksesi"
|
||||
|
||||
@@ -3501,7 +3501,7 @@ msgstr "Entrez votre mot de passe"
|
||||
#~ msgstr "Entrez votre hébergeur préféré"
|
||||
|
||||
#: src/screens/Signup/StepHandle.tsx:114
|
||||
msgid "Input your user handle"
|
||||
msgid "Type your desired username"
|
||||
msgstr "Entrez votre pseudo"
|
||||
|
||||
#: src/view/com/composer/threadgate/ThreadgateBtn.tsx:49
|
||||
@@ -8519,7 +8519,7 @@ msgid "Your following feed is empty! Follow more users to see what's happening."
|
||||
msgstr "Votre fil d’actu des comptes suivis est vide ! Suivez plus de comptes pour voir ce qui se passe."
|
||||
|
||||
#: src/screens/Signup/StepHandle.tsx:125
|
||||
msgid "Your full handle will be"
|
||||
msgid "Your full username will be"
|
||||
msgstr "Votre nom complet sera"
|
||||
|
||||
#: src/screens/Settings/components/ChangeHandleDialog.tsx:219
|
||||
@@ -8563,5 +8563,5 @@ msgid "Your report will be sent to the Bluesky Moderation Service"
|
||||
msgstr "Votre rapport sera envoyé au Service de Modération de Bluesky"
|
||||
|
||||
#: src/screens/Signup/index.tsx:142
|
||||
msgid "Your user handle"
|
||||
msgid "Choose your username"
|
||||
msgstr "Votre pseudo"
|
||||
|
||||
@@ -3508,7 +3508,7 @@ msgstr "Cuir isteach do phasfhocal"
|
||||
#~ msgstr "Cuir isteach an soláthraí óstála is fearr leat"
|
||||
|
||||
#: src/screens/Signup/StepHandle.tsx:114
|
||||
msgid "Input your user handle"
|
||||
msgid "Type your desired username"
|
||||
msgstr "Cuir isteach do leasainm"
|
||||
|
||||
#: src/view/com/composer/threadgate/ThreadgateBtn.tsx:49
|
||||
@@ -8542,7 +8542,7 @@ msgid "Your following feed is empty! Follow more users to see what's happening."
|
||||
msgstr "Tá an fotha de na daoine a leanann tú folamh! Lean tuilleadh úsáideoirí le feiceáil céard atá ar siúl."
|
||||
|
||||
#: src/screens/Signup/StepHandle.tsx:125
|
||||
msgid "Your full handle will be"
|
||||
msgid "Your full username will be"
|
||||
msgstr "Do leasainm iomlán anseo:"
|
||||
|
||||
#: src/screens/Settings/components/ChangeHandleDialog.tsx:219
|
||||
@@ -8586,5 +8586,5 @@ msgid "Your report will be sent to the Bluesky Moderation Service"
|
||||
msgstr "Seolfar do thuairisc go dtí Seirbhís Modhnóireachta Bluesky"
|
||||
|
||||
#: src/screens/Signup/index.tsx:142
|
||||
msgid "Your user handle"
|
||||
msgid "Choose your username"
|
||||
msgstr "Do leasainm"
|
||||
|
||||
@@ -3896,7 +3896,7 @@ msgstr "Introduce o teu contrasinal"
|
||||
#~ msgstr "Introduce o teu proveedor de aloxamento preferido"
|
||||
|
||||
#: src/screens/Signup/StepHandle.tsx:114
|
||||
msgid "Input your user handle"
|
||||
msgid "Type your desired username"
|
||||
msgstr "Introduce o teu alcume"
|
||||
|
||||
#: src/view/com/composer/threadgate/ThreadgateBtn.tsx:49
|
||||
@@ -9523,7 +9523,7 @@ msgid "Your following feed is empty! Follow more users to see what's happening."
|
||||
msgstr "A seguinte canle está baleira! Sigue a máis persoas para ver o que está a acontecer."
|
||||
|
||||
#: src/screens/Signup/StepHandle.tsx:125
|
||||
msgid "Your full handle will be"
|
||||
msgid "Your full username will be"
|
||||
msgstr "O teu alcume completo será"
|
||||
|
||||
#: src/screens/Settings/components/ChangeHandleDialog.tsx:219
|
||||
@@ -9567,5 +9567,5 @@ msgid "Your report will be sent to the Bluesky Moderation Service"
|
||||
msgstr "O teu informe enviarase ao Servizo de moderación de Bluesky"
|
||||
|
||||
#: src/screens/Signup/index.tsx:142
|
||||
msgid "Your user handle"
|
||||
msgid "Choose your username"
|
||||
msgstr "O teu alcume"
|
||||
|
||||
@@ -3780,7 +3780,7 @@ msgstr "अपना पासवर्ड दर्ज करें"
|
||||
#~ msgstr "अपना पसंदीदा होस्टिंग प्रदाता दर्ज करें"
|
||||
|
||||
#: src/screens/Signup/StepHandle.tsx:114
|
||||
msgid "Input your user handle"
|
||||
msgid "Type your desired username"
|
||||
msgstr "अपना उपयोगकर्ता हैंडल दर्ज करें"
|
||||
|
||||
#: src/view/com/composer/threadgate/ThreadgateBtn.tsx:49
|
||||
@@ -9026,7 +9026,7 @@ msgid "Your following feed is empty! Follow more users to see what's happening."
|
||||
msgstr "आपका फ़ॉलोइंग फ़ीड खाली है! क्या चल रहा है जानने के लिए और उपयोगकर्ताओं को फ़ॉलो करें।"
|
||||
|
||||
#: src/screens/Signup/StepHandle.tsx:125
|
||||
msgid "Your full handle will be"
|
||||
msgid "Your full username will be"
|
||||
msgstr "आपका पूरा हैंडल होगा"
|
||||
|
||||
#: src/screens/Settings/components/ChangeHandleDialog.tsx:219
|
||||
@@ -9070,5 +9070,5 @@ msgid "Your report will be sent to the Bluesky Moderation Service"
|
||||
msgstr "आपके शिकायत को Bluesky मॉडरेशन सेवा को भेजा जाएगा।"
|
||||
|
||||
#: src/screens/Signup/index.tsx:142
|
||||
msgid "Your user handle"
|
||||
msgid "Choose your username"
|
||||
msgstr "आपका उपयोगकर्ता हैंडल"
|
||||
|
||||
@@ -3465,7 +3465,7 @@ msgstr "Jelszó megadása"
|
||||
#~ msgstr "Kívánt tárhelyszolgáltató megadása"
|
||||
|
||||
#: src/screens/Signup/StepHandle.tsx:114
|
||||
msgid "Input your user handle"
|
||||
msgid "Type your desired username"
|
||||
msgstr "Felhasználónév megadása"
|
||||
|
||||
#: src/view/com/composer/threadgate/ThreadgateBtn.tsx:49
|
||||
@@ -8439,7 +8439,7 @@ msgid "Your following feed is empty! Follow more users to see what's happening."
|
||||
msgstr "A Követett hírfolyamod üres. Kövess több felhasználót, hogy lásd, mi történik!"
|
||||
|
||||
#: src/screens/Signup/StepHandle.tsx:125
|
||||
msgid "Your full handle will be"
|
||||
msgid "Your full username will be"
|
||||
msgstr "A teljes felhasználóneved:"
|
||||
|
||||
#: src/screens/Settings/components/ChangeHandleDialog.tsx:219
|
||||
@@ -8482,5 +8482,5 @@ msgid "Your report will be sent to the Bluesky Moderation Service"
|
||||
msgstr "Ez a jelentés a Bluesky moderálási szolgáltatásának lesz elküldve"
|
||||
|
||||
#: src/screens/Signup/index.tsx:142
|
||||
msgid "Your user handle"
|
||||
msgid "Choose your username"
|
||||
msgstr "Saját felhasználónév"
|
||||
|
||||
@@ -4007,7 +4007,7 @@ msgstr "Masukkan kata sandi Anda"
|
||||
#~ msgstr "Masukkan penyedia hosting pilihan Anda"
|
||||
|
||||
#: src/screens/Signup/StepHandle.tsx:114
|
||||
msgid "Input your user handle"
|
||||
msgid "Type your desired username"
|
||||
msgstr "Masukkan panggilan Anda"
|
||||
|
||||
#: src/view/com/composer/threadgate/ThreadgateBtn.tsx:49
|
||||
@@ -9678,7 +9678,7 @@ msgid "Your following feed is empty! Follow more users to see what's happening."
|
||||
msgstr "Feed mengikuti Anda kosong! Ikuti lebih banyak pengguna untuk melihat apa yang terjadi."
|
||||
|
||||
#: src/screens/Signup/StepHandle.tsx:125
|
||||
msgid "Your full handle will be"
|
||||
msgid "Your full username will be"
|
||||
msgstr "Panggilan lengkap Anda akan menjadi"
|
||||
|
||||
#: src/screens/Settings/components/ChangeHandleDialog.tsx:219
|
||||
@@ -9722,5 +9722,5 @@ msgid "Your report will be sent to the Bluesky Moderation Service"
|
||||
msgstr "Laporan Anda akan dikirim ke Layanan Moderasi Bluesky"
|
||||
|
||||
#: src/screens/Signup/index.tsx:142
|
||||
msgid "Your user handle"
|
||||
msgid "Choose your username"
|
||||
msgstr "Panggilan Anda"
|
||||
|
||||
@@ -3318,7 +3318,7 @@ msgid "Input your password"
|
||||
msgstr "Inserisci la tua password"
|
||||
|
||||
#: src/screens/Signup/StepHandle.tsx:114
|
||||
msgid "Input your user handle"
|
||||
msgid "Type your desired username"
|
||||
msgstr "Inserisci il tuo nome utente"
|
||||
|
||||
#: src/view/com/composer/threadgate/ThreadgateBtn.tsx:49
|
||||
@@ -8016,7 +8016,7 @@ msgid "Your following feed is empty! Follow more users to see what's happening."
|
||||
msgstr "Il tuo feed seguente è vuoto! Segui più utenti per vedere cosa sta succedendo."
|
||||
|
||||
#: src/screens/Signup/StepHandle.tsx:125
|
||||
msgid "Your full handle will be"
|
||||
msgid "Your full username will be"
|
||||
msgstr "Il tuo nome utente completo sarà"
|
||||
|
||||
#: src/screens/Settings/components/ChangeHandleDialog.tsx:219
|
||||
@@ -8056,5 +8056,5 @@ msgid "Your report will be sent to the Bluesky Moderation Service"
|
||||
msgstr "La tua segnalazione verrà inviata al Servizio Moderazione di Bluesky"
|
||||
|
||||
#: src/screens/Signup/index.tsx:142
|
||||
msgid "Your user handle"
|
||||
msgid "Choose your username"
|
||||
msgstr "Il tuo nome utente"
|
||||
|
||||
@@ -3317,7 +3317,7 @@ msgid "Input your password"
|
||||
msgstr "あなたのパスワードを入力"
|
||||
|
||||
#: src/screens/Signup/StepHandle.tsx:114
|
||||
msgid "Input your user handle"
|
||||
msgid "Type your desired username"
|
||||
msgstr "あなたのユーザーハンドルを入力"
|
||||
|
||||
#: src/view/com/composer/threadgate/ThreadgateBtn.tsx:49
|
||||
@@ -8047,7 +8047,7 @@ msgid "Your following feed is empty! Follow more users to see what's happening."
|
||||
msgstr "Followingフィードは空です!もっと多くのユーザーをフォローして、近況を確認しましょう。"
|
||||
|
||||
#: src/screens/Signup/StepHandle.tsx:125
|
||||
msgid "Your full handle will be"
|
||||
msgid "Your full username will be"
|
||||
msgstr "フルハンドルは"
|
||||
|
||||
#: src/screens/Settings/components/ChangeHandleDialog.tsx:219
|
||||
@@ -8087,5 +8087,5 @@ msgid "Your report will be sent to the Bluesky Moderation Service"
|
||||
msgstr "あなたの報告はBluesky Moderation Serviceに送られます"
|
||||
|
||||
#: src/screens/Signup/index.tsx:142
|
||||
msgid "Your user handle"
|
||||
msgid "Choose your username"
|
||||
msgstr "あなたのユーザーハンドル"
|
||||
|
||||
@@ -3317,7 +3317,7 @@ msgid "Input your password"
|
||||
msgstr "비밀번호를 입력합니다"
|
||||
|
||||
#: src/screens/Signup/StepHandle.tsx:114
|
||||
msgid "Input your user handle"
|
||||
msgid "Type your desired username"
|
||||
msgstr "사용자 핸들을 입력합니다"
|
||||
|
||||
#: src/view/com/composer/threadgate/ThreadgateBtn.tsx:49
|
||||
@@ -8019,7 +8019,7 @@ msgid "Your following feed is empty! Follow more users to see what's happening."
|
||||
msgstr "팔로우 중 피드가 비어 있습니다. 더 많은 사용자를 팔로우하여 무슨 일이 일어나고 있는지 확인하세요."
|
||||
|
||||
#: src/screens/Signup/StepHandle.tsx:125
|
||||
msgid "Your full handle will be"
|
||||
msgid "Your full username will be"
|
||||
msgstr "내 전체 핸들:"
|
||||
|
||||
#: src/screens/Settings/components/ChangeHandleDialog.tsx:219
|
||||
@@ -8059,5 +8059,5 @@ msgid "Your report will be sent to the Bluesky Moderation Service"
|
||||
msgstr "신고가 Bluesky Moderation Service로 보내집니다."
|
||||
|
||||
#: src/screens/Signup/index.tsx:142
|
||||
msgid "Your user handle"
|
||||
msgid "Choose your username"
|
||||
msgstr "내 사용자 핸들"
|
||||
|
||||
@@ -3496,7 +3496,7 @@ msgstr "Vul je wachtwoord in"
|
||||
#~ msgstr "Vul je voorkeurshostingprovider in"
|
||||
|
||||
#: src/screens/Signup/StepHandle.tsx:114
|
||||
msgid "Input your user handle"
|
||||
msgid "Type your desired username"
|
||||
msgstr "Vul je gebruikershandle in."
|
||||
|
||||
#: src/view/com/composer/threadgate/ThreadgateBtn.tsx:49
|
||||
@@ -8506,7 +8506,7 @@ msgid "Your following feed is empty! Follow more users to see what's happening."
|
||||
msgstr "Je volgende feed is leeg! Volg meer gebruikers om te zien wat er gebeurt."
|
||||
|
||||
#: src/screens/Signup/StepHandle.tsx:125
|
||||
msgid "Your full handle will be"
|
||||
msgid "Your full username will be"
|
||||
msgstr "Je volledige handle wordt"
|
||||
|
||||
#: src/screens/Settings/components/ChangeHandleDialog.tsx:219
|
||||
@@ -8550,5 +8550,5 @@ msgid "Your report will be sent to the Bluesky Moderation Service"
|
||||
msgstr "Je melding wordt verzonden naar de Bluesky-moderatieservice"
|
||||
|
||||
#: src/screens/Signup/index.tsx:142
|
||||
msgid "Your user handle"
|
||||
msgid "Choose your username"
|
||||
msgstr "Je gebruikershandle"
|
||||
|
||||
@@ -3317,7 +3317,7 @@ msgid "Input your password"
|
||||
msgstr "Wpisz hasło"
|
||||
|
||||
#: src/screens/Signup/StepHandle.tsx:114
|
||||
msgid "Input your user handle"
|
||||
msgid "Type your desired username"
|
||||
msgstr "Wpisz nazwę użytkownika"
|
||||
|
||||
#: src/view/com/composer/threadgate/ThreadgateBtn.tsx:49
|
||||
@@ -8031,7 +8031,7 @@ msgid "Your following feed is empty! Follow more users to see what's happening."
|
||||
msgstr "Twój kanał osób obserwowanych jest pusty! Zaobserwuj trochę więcej osób."
|
||||
|
||||
#: src/screens/Signup/StepHandle.tsx:125
|
||||
msgid "Your full handle will be"
|
||||
msgid "Your full username will be"
|
||||
msgstr "Twoją pełna nazwa będzie"
|
||||
|
||||
#: src/screens/Settings/components/ChangeHandleDialog.tsx:219
|
||||
@@ -8071,5 +8071,5 @@ msgid "Your report will be sent to the Bluesky Moderation Service"
|
||||
msgstr "Zgłoszenie będzie wysłane do usługi moderacji Bluesky"
|
||||
|
||||
#: src/screens/Signup/index.tsx:142
|
||||
msgid "Your user handle"
|
||||
msgid "Choose your username"
|
||||
msgstr "Twoja nazwa użytkownika"
|
||||
|
||||
@@ -4003,7 +4003,7 @@ msgstr "Insira sua senha"
|
||||
#~ msgstr "Insira seu provedor de hospedagem"
|
||||
|
||||
#: src/screens/Signup/StepHandle.tsx:114
|
||||
msgid "Input your user handle"
|
||||
msgid "Type your desired username"
|
||||
msgstr "Insira o usuário"
|
||||
|
||||
#: src/view/com/composer/threadgate/ThreadgateBtn.tsx:49
|
||||
@@ -9667,7 +9667,7 @@ msgid "Your following feed is empty! Follow more users to see what's happening."
|
||||
msgstr "Seu feed inicial está vazio! Siga mais usuários para acompanhar o que está acontecendo."
|
||||
|
||||
#: src/screens/Signup/StepHandle.tsx:125
|
||||
msgid "Your full handle will be"
|
||||
msgid "Your full username will be"
|
||||
msgstr "Seu identificador completo será"
|
||||
|
||||
#: src/screens/Settings/components/ChangeHandleDialog.tsx:219
|
||||
@@ -9711,5 +9711,5 @@ msgid "Your report will be sent to the Bluesky Moderation Service"
|
||||
msgstr "Sua denúncia será enviada para o serviço de moderação do Bluesky"
|
||||
|
||||
#: src/screens/Signup/index.tsx:142
|
||||
msgid "Your user handle"
|
||||
msgid "Choose your username"
|
||||
msgstr "Seu identificador de usuário"
|
||||
|
||||
@@ -3578,7 +3578,7 @@ msgstr "Введите ваш пароль"
|
||||
#~ msgstr "Введите желаемого хостинг-провайдера"
|
||||
|
||||
#: src/screens/Signup/StepHandle.tsx:114
|
||||
msgid "Input your user handle"
|
||||
msgid "Type your desired username"
|
||||
msgstr "Введите ваш псевдоним"
|
||||
|
||||
#: src/view/com/composer/threadgate/ThreadgateBtn.tsx:49
|
||||
@@ -8768,7 +8768,7 @@ msgid "Your following feed is empty! Follow more users to see what's happening."
|
||||
msgstr "Ваша домашняя лента пуста! Подпишитесь на больше пользователей чтобы получать больше постов."
|
||||
|
||||
#: src/screens/Signup/StepHandle.tsx:125
|
||||
msgid "Your full handle will be"
|
||||
msgid "Your full username will be"
|
||||
msgstr "Ваш полный псевдоним будет"
|
||||
|
||||
#: src/screens/Settings/components/ChangeHandleDialog.tsx:219
|
||||
@@ -8812,5 +8812,5 @@ msgid "Your report will be sent to the Bluesky Moderation Service"
|
||||
msgstr "Ваш отчет будет отправлен в Службу Модерации Bluesky."
|
||||
|
||||
#: src/screens/Signup/index.tsx:142
|
||||
msgid "Your user handle"
|
||||
msgid "Choose your username"
|
||||
msgstr "Ваш псевдоним"
|
||||
|
||||
@@ -3994,7 +3994,7 @@ msgstr "ป้อนรหัสผ่านของคุณ"
|
||||
#~ msgstr "กรอกผู้ให้บริการโฮสติ้งที่คุณต้องการ"
|
||||
|
||||
#: src/screens/Signup/StepHandle.tsx:114
|
||||
msgid "Input your user handle"
|
||||
msgid "Type your desired username"
|
||||
msgstr "กรอกชื่อผู้ใช้ของคุณ"
|
||||
|
||||
#: src/view/com/composer/threadgate/ThreadgateBtn.tsx:49
|
||||
@@ -9665,7 +9665,7 @@ msgid "Your following feed is empty! Follow more users to see what's happening."
|
||||
msgstr "ฟีดผู้ติดตามของคุณว่างเปล่า! ติดตามผู้ใช้เพิ่มเติมเพื่อดูความเคลื่อนไหว"
|
||||
|
||||
#: src/screens/Signup/StepHandle.tsx:125
|
||||
msgid "Your full handle will be"
|
||||
msgid "Your full username will be"
|
||||
msgstr "ชื่อผู้ใช้เต็มของคุณจะเป็น"
|
||||
|
||||
#: src/screens/Settings/components/ChangeHandleDialog.tsx:219
|
||||
@@ -9709,5 +9709,5 @@ msgid "Your report will be sent to the Bluesky Moderation Service"
|
||||
msgstr "รายงานของคุณจะถูกส่งไปยัง Bluesky Moderation Service"
|
||||
|
||||
#: src/screens/Signup/index.tsx:142
|
||||
msgid "Your user handle"
|
||||
msgid "Choose your username"
|
||||
msgstr "แฮนด์เดิลผู้ใช้ของคุณ"
|
||||
|
||||
@@ -4211,7 +4211,7 @@ msgstr "Şifrenizi girin"
|
||||
#~ msgstr ""
|
||||
|
||||
#: src/screens/Signup/StepHandle.tsx:114
|
||||
msgid "Input your user handle"
|
||||
msgid "Type your desired username"
|
||||
msgstr "Kullanıcı adınızı girin"
|
||||
|
||||
#: src/view/com/composer/threadgate/ThreadgateBtn.tsx:49
|
||||
@@ -10212,7 +10212,7 @@ msgid "Your following feed is empty! Follow more users to see what's happening."
|
||||
msgstr "Takip ettiğiniz besleme boş! Neler olduğunu görmek için daha fazla kullanıcı takip edin."
|
||||
|
||||
#: src/screens/Signup/StepHandle.tsx:125
|
||||
msgid "Your full handle will be"
|
||||
msgid "Your full username will be"
|
||||
msgstr "Tam kullanıcı adınız"
|
||||
|
||||
#: src/screens/Settings/components/ChangeHandleDialog.tsx:219
|
||||
@@ -10261,5 +10261,5 @@ msgid "Your report will be sent to the Bluesky Moderation Service"
|
||||
msgstr ""
|
||||
|
||||
#: src/screens/Signup/index.tsx:142
|
||||
msgid "Your user handle"
|
||||
msgid "Choose your username"
|
||||
msgstr "Kullanıcı adınız"
|
||||
|
||||
@@ -4007,7 +4007,7 @@ msgstr "Введіть ваш пароль"
|
||||
#~ msgstr "Введіть бажаного хостинг-провайдера"
|
||||
|
||||
#: src/screens/Signup/StepHandle.tsx:114
|
||||
msgid "Input your user handle"
|
||||
msgid "Type your desired username"
|
||||
msgstr "Введіть ваш псевдонім"
|
||||
|
||||
#: src/view/com/composer/threadgate/ThreadgateBtn.tsx:49
|
||||
@@ -9678,7 +9678,7 @@ msgid "Your following feed is empty! Follow more users to see what's happening."
|
||||
msgstr "Ваша домашня стрічка порожня! Підпишіться на більше користувачів щоб отримувати більше постів."
|
||||
|
||||
#: src/screens/Signup/StepHandle.tsx:125
|
||||
msgid "Your full handle will be"
|
||||
msgid "Your full username will be"
|
||||
msgstr "Ваш повний псевдонім буде"
|
||||
|
||||
#: src/screens/Settings/components/ChangeHandleDialog.tsx:219
|
||||
@@ -9722,5 +9722,5 @@ msgid "Your report will be sent to the Bluesky Moderation Service"
|
||||
msgstr ""
|
||||
|
||||
#: src/screens/Signup/index.tsx:142
|
||||
msgid "Your user handle"
|
||||
msgid "Choose your username"
|
||||
msgstr "Ваш псевдонім"
|
||||
|
||||
@@ -3509,7 +3509,7 @@ msgstr "Nhập mật khẩu của bạn"
|
||||
#~ msgstr "Nhập nhà cung cấp lưu trữ theo lựa chọn của bạn"
|
||||
|
||||
#: src/screens/Signup/StepHandle.tsx:114
|
||||
msgid "Input your user handle"
|
||||
msgid "Type your desired username"
|
||||
msgstr "Nhập tên người dùng của bạn"
|
||||
|
||||
#: src/view/com/composer/threadgate/ThreadgateBtn.tsx:49
|
||||
@@ -8543,7 +8543,7 @@ msgid "Your following feed is empty! Follow more users to see what's happening."
|
||||
msgstr "Bảng tin theo dõi còn trống! Hãy theo dõi thêm người dùng để biết có gì đang diễn ra."
|
||||
|
||||
#: src/screens/Signup/StepHandle.tsx:125
|
||||
msgid "Your full handle will be"
|
||||
msgid "Your full username will be"
|
||||
msgstr "Tên người dùng đầy đủ của bạn sẽ là"
|
||||
|
||||
#: src/screens/Settings/components/ChangeHandleDialog.tsx:219
|
||||
@@ -8587,5 +8587,5 @@ msgid "Your report will be sent to the Bluesky Moderation Service"
|
||||
msgstr "Báo cáo của bạn sẽ được gởi đến dịch vụ kiểm duyệt của Bluesky"
|
||||
|
||||
#: src/screens/Signup/index.tsx:142
|
||||
msgid "Your user handle"
|
||||
msgid "Choose your username"
|
||||
msgstr "Tên người dùng của bạn"
|
||||
|
||||
@@ -3314,7 +3314,7 @@ msgid "Input your password"
|
||||
msgstr "输入你的密码"
|
||||
|
||||
#: src/screens/Signup/StepHandle.tsx:114
|
||||
msgid "Input your user handle"
|
||||
msgid "Type your desired username"
|
||||
msgstr "输入你的账户代码"
|
||||
|
||||
#: src/view/com/composer/threadgate/ThreadgateBtn.tsx:49
|
||||
@@ -8004,7 +8004,7 @@ msgid "Your following feed is empty! Follow more users to see what's happening."
|
||||
msgstr "你的“Following”动态源是空的!关注更多用户去看看他们发了什么。"
|
||||
|
||||
#: src/screens/Signup/StepHandle.tsx:125
|
||||
msgid "Your full handle will be"
|
||||
msgid "Your full username will be"
|
||||
msgstr "你的完整账户代码将修改为"
|
||||
|
||||
#: src/screens/Settings/components/ChangeHandleDialog.tsx:219
|
||||
@@ -8044,5 +8044,5 @@ msgid "Your report will be sent to the Bluesky Moderation Service"
|
||||
msgstr "你的举报将发送至 Bluesky 内容审核服务"
|
||||
|
||||
#: src/screens/Signup/index.tsx:142
|
||||
msgid "Your user handle"
|
||||
msgid "Choose your username"
|
||||
msgstr "你的账户代码"
|
||||
|
||||
@@ -3314,7 +3314,7 @@ msgid "Input your password"
|
||||
msgstr "輸入你嘅密碼"
|
||||
|
||||
#: src/screens/Signup/StepHandle.tsx:114
|
||||
msgid "Input your user handle"
|
||||
msgid "Type your desired username"
|
||||
msgstr "輸入你嘅帳號頭銜"
|
||||
|
||||
#: src/view/com/composer/threadgate/ThreadgateBtn.tsx:49
|
||||
@@ -8004,7 +8004,7 @@ msgid "Your following feed is empty! Follow more users to see what's happening."
|
||||
msgstr "你嘅「Following」動態源得個吉!跟多啲用戶睇吓發生緊啲咩事。"
|
||||
|
||||
#: src/screens/Signup/StepHandle.tsx:125
|
||||
msgid "Your full handle will be"
|
||||
msgid "Your full username will be"
|
||||
msgstr "你嘅完整帳號頭銜會係"
|
||||
|
||||
#: src/screens/Settings/components/ChangeHandleDialog.tsx:219
|
||||
@@ -8044,5 +8044,5 @@ msgid "Your report will be sent to the Bluesky Moderation Service"
|
||||
msgstr "你嘅上報會傳送去 Bluesky 審核服務"
|
||||
|
||||
#: src/screens/Signup/index.tsx:142
|
||||
msgid "Your user handle"
|
||||
msgid "Choose your username"
|
||||
msgstr "你嘅帳號頭銜"
|
||||
|
||||
@@ -3314,7 +3314,7 @@ msgid "Input your password"
|
||||
msgstr "輸入您的密碼"
|
||||
|
||||
#: src/screens/Signup/StepHandle.tsx:114
|
||||
msgid "Input your user handle"
|
||||
msgid "Type your desired username"
|
||||
msgstr "輸入您的帳號代碼"
|
||||
|
||||
#: src/view/com/composer/threadgate/ThreadgateBtn.tsx:49
|
||||
@@ -8004,7 +8004,7 @@ msgid "Your following feed is empty! Follow more users to see what's happening."
|
||||
msgstr "您的「Following」動態源是空的!跟隨更多用戶來看看發生了什麼事情。"
|
||||
|
||||
#: src/screens/Signup/StepHandle.tsx:125
|
||||
msgid "Your full handle will be"
|
||||
msgid "Your full username will be"
|
||||
msgstr "您的完整帳號代碼將修改為"
|
||||
|
||||
#: src/screens/Settings/components/ChangeHandleDialog.tsx:219
|
||||
@@ -8044,5 +8044,5 @@ msgid "Your report will be sent to the Bluesky Moderation Service"
|
||||
msgstr "您的檢舉將傳送至 Bluesky 內容管理服務"
|
||||
|
||||
#: src/screens/Signup/index.tsx:142
|
||||
msgid "Your user handle"
|
||||
msgid "Choose your username"
|
||||
msgstr "您的帳號代碼"
|
||||
|
||||
@@ -3,8 +3,7 @@ import {
|
||||
error as bdError,
|
||||
info as bdInfo,
|
||||
warn as bdWarn,
|
||||
} from '@bitdrift/react-native'
|
||||
|
||||
} from '../lib/bitdrift'
|
||||
import {LogLevel, Transport} from './types'
|
||||
|
||||
export function createBitdriftTransport(): Transport {
|
||||
@@ -18,6 +17,6 @@ export function createBitdriftTransport(): Transport {
|
||||
|
||||
return (level, message) => {
|
||||
const log = logFunctions[level]
|
||||
log(message.toString())
|
||||
log('' + message)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,7 +0,0 @@
|
||||
import {Transport} from './index'
|
||||
|
||||
export function createBitdriftTransport(): Transport {
|
||||
return (_level, _message) => {
|
||||
// noop
|
||||
}
|
||||
}
|
||||
@@ -14,7 +14,7 @@ import {
|
||||
TIMELINE_SAVED_FEED,
|
||||
} from '#/lib/constants'
|
||||
import {useRequestNotificationsPermission} from '#/lib/notifications/notifications'
|
||||
import {logEvent} from '#/lib/statsig/statsig'
|
||||
import {logEvent, useGate} from '#/lib/statsig/statsig'
|
||||
import {logger} from '#/logger'
|
||||
import {useSetHasCheckedForStarterPack} from '#/state/preferences/used-starter-packs'
|
||||
import {getAllListMembers} from '#/state/queries/list-members'
|
||||
@@ -57,6 +57,7 @@ export function StepFinished() {
|
||||
const setActiveStarterPack = useSetActiveStarterPack()
|
||||
const setHasCheckedForStarterPack = useSetHasCheckedForStarterPack()
|
||||
const {startProgressGuide} = useProgressGuideControls()
|
||||
const gate = useGate()
|
||||
|
||||
const finishOnboarding = React.useCallback(async () => {
|
||||
setSaving(true)
|
||||
@@ -190,7 +191,9 @@ export function StepFinished() {
|
||||
setSaving(false)
|
||||
setActiveStarterPack(undefined)
|
||||
setHasCheckedForStarterPack(true)
|
||||
startProgressGuide('like-10-and-follow-7')
|
||||
startProgressGuide(
|
||||
gate('new_postonboarding') ? 'follow-10' : 'like-10-and-follow-7',
|
||||
)
|
||||
dispatch({type: 'finish'})
|
||||
onboardDispatch({type: 'finish'})
|
||||
logEvent('onboarding:finished:nextPressed', {
|
||||
@@ -221,6 +224,7 @@ export function StepFinished() {
|
||||
setActiveStarterPack,
|
||||
setHasCheckedForStarterPack,
|
||||
startProgressGuide,
|
||||
gate,
|
||||
])
|
||||
|
||||
return (
|
||||
|
||||
@@ -72,6 +72,19 @@ export type ApiResponseMap = {
|
||||
}
|
||||
}
|
||||
|
||||
// most popular selected interests
|
||||
export const popularInterests = [
|
||||
'art',
|
||||
'gaming',
|
||||
'sports',
|
||||
'comics',
|
||||
'music',
|
||||
'politics',
|
||||
'photography',
|
||||
'science',
|
||||
'news',
|
||||
]
|
||||
|
||||
export function useInterestsDisplayNames() {
|
||||
const {_} = useLingui()
|
||||
|
||||
|
||||
@@ -0,0 +1,95 @@
|
||||
import {View} from 'react-native'
|
||||
import {Trans} from '@lingui/macro'
|
||||
|
||||
import {isWeb} from '#/platform/detection'
|
||||
import {useTrendingSettings} from '#/state/preferences/trending'
|
||||
import {
|
||||
DEFAULT_LIMIT as RECOMMENDATIONS_COUNT,
|
||||
useTrendingTopics,
|
||||
} from '#/state/queries/trending/useTrendingTopics'
|
||||
import {useTrendingConfig} from '#/state/trending-config'
|
||||
import {atoms as a, useGutters, useTheme} from '#/alf'
|
||||
import {Hashtag_Stroke2_Corner0_Rounded} from '#/components/icons/Hashtag'
|
||||
import {
|
||||
TrendingTopic,
|
||||
TrendingTopicLink,
|
||||
TrendingTopicSkeleton,
|
||||
} from '#/components/TrendingTopics'
|
||||
import {Text} from '#/components/Typography'
|
||||
|
||||
export function ExploreRecommendations() {
|
||||
const {enabled} = useTrendingConfig()
|
||||
const {trendingDisabled} = useTrendingSettings()
|
||||
return enabled && !trendingDisabled ? <Inner /> : null
|
||||
}
|
||||
|
||||
function Inner() {
|
||||
const t = useTheme()
|
||||
const gutters = useGutters([0, 'compact'])
|
||||
const {data: trending, error, isLoading} = useTrendingTopics()
|
||||
const noRecs = !isLoading && !error && !trending?.suggested?.length
|
||||
|
||||
return error || noRecs ? null : (
|
||||
<>
|
||||
<View
|
||||
style={[
|
||||
isWeb
|
||||
? [a.flex_row, a.px_lg, a.py_lg, a.pt_2xl, a.gap_md]
|
||||
: [{flexDirection: 'row-reverse'}, a.p_lg, a.pt_2xl, a.gap_md],
|
||||
a.border_b,
|
||||
t.atoms.border_contrast_low,
|
||||
]}>
|
||||
<View style={[a.flex_1, a.gap_sm]}>
|
||||
<View style={[a.flex_row, a.align_center, a.gap_sm]}>
|
||||
<Hashtag_Stroke2_Corner0_Rounded
|
||||
size="lg"
|
||||
fill={t.palette.primary_500}
|
||||
style={{marginLeft: -2}}
|
||||
/>
|
||||
<Text style={[a.text_2xl, a.font_heavy, t.atoms.text]}>
|
||||
<Trans>Recommended</Trans>
|
||||
</Text>
|
||||
</View>
|
||||
<Text style={[t.atoms.text_contrast_high, a.leading_snug]}>
|
||||
<Trans>Feeds we think you might like.</Trans>
|
||||
</Text>
|
||||
</View>
|
||||
</View>
|
||||
|
||||
<View style={[a.pt_md, a.pb_lg]}>
|
||||
<View
|
||||
style={[
|
||||
a.flex_row,
|
||||
a.justify_start,
|
||||
a.flex_wrap,
|
||||
{rowGap: 8, columnGap: 6},
|
||||
gutters,
|
||||
]}>
|
||||
{isLoading ? (
|
||||
Array(RECOMMENDATIONS_COUNT)
|
||||
.fill(0)
|
||||
.map((_, i) => <TrendingTopicSkeleton key={i} index={i} />)
|
||||
) : !trending?.suggested ? null : (
|
||||
<>
|
||||
{trending.suggested.map(topic => (
|
||||
<TrendingTopicLink key={topic.link} topic={topic}>
|
||||
{({hovered}) => (
|
||||
<TrendingTopic
|
||||
topic={topic}
|
||||
style={[
|
||||
hovered && [
|
||||
t.atoms.border_contrast_high,
|
||||
t.atoms.bg_contrast_25,
|
||||
],
|
||||
]}
|
||||
/>
|
||||
)}
|
||||
</TrendingTopicLink>
|
||||
))}
|
||||
</>
|
||||
)}
|
||||
</View>
|
||||
</View>
|
||||
</>
|
||||
)
|
||||
}
|
||||
@@ -2,6 +2,7 @@ import {View} from 'react-native'
|
||||
import {Trans} from '@lingui/macro'
|
||||
|
||||
import {isWeb} from '#/platform/detection'
|
||||
import {useTrendingSettings} from '#/state/preferences/trending'
|
||||
import {
|
||||
DEFAULT_LIMIT as TRENDING_TOPICS_COUNT,
|
||||
useTrendingTopics,
|
||||
@@ -19,7 +20,8 @@ import {Text} from '#/components/Typography'
|
||||
|
||||
export function ExploreTrendingTopics() {
|
||||
const {enabled} = useTrendingConfig()
|
||||
return enabled ? <Inner /> : null
|
||||
const {trendingDisabled} = useTrendingSettings()
|
||||
return enabled && !trendingDisabled ? <Inner /> : null
|
||||
}
|
||||
|
||||
function Inner() {
|
||||
@@ -56,14 +58,20 @@ function Inner() {
|
||||
</View>
|
||||
</View>
|
||||
<Text style={[t.atoms.text_contrast_high, a.leading_snug]}>
|
||||
<Trans>What people are posting about now</Trans>
|
||||
<Trans>What people are posting about.</Trans>
|
||||
</Text>
|
||||
</View>
|
||||
</View>
|
||||
|
||||
<View style={[a.pt_md, a.pb_lg]}>
|
||||
<View
|
||||
style={[a.flex_row, a.justify_start, a.flex_wrap, a.gap_sm, gutters]}>
|
||||
style={[
|
||||
a.flex_row,
|
||||
a.justify_start,
|
||||
a.flex_wrap,
|
||||
{rowGap: 8, columnGap: 6},
|
||||
gutters,
|
||||
]}>
|
||||
{isLoading ? (
|
||||
Array(TRENDING_TOPICS_COUNT)
|
||||
.fill(0)
|
||||
|
||||
@@ -5,11 +5,11 @@ import {useLingui} from '@lingui/react'
|
||||
import * as DynamicAppIcon from '@mozzius/expo-dynamic-app-icon'
|
||||
import {NativeStackScreenProps} from '@react-navigation/native-stack'
|
||||
|
||||
import {DISCOVER_DEBUG_DIDS} from '#/lib/constants'
|
||||
import {IS_INTERNAL} from '#/lib/app-info'
|
||||
import {PressableScale} from '#/lib/custom-animations/PressableScale'
|
||||
import {CommonNavigatorParams} from '#/lib/routes/types'
|
||||
import {useGate} from '#/lib/statsig/statsig'
|
||||
import {isAndroid} from '#/platform/detection'
|
||||
import {useSession} from '#/state/session'
|
||||
import {AppIconImage} from '#/screens/Settings/AppIconSettings/AppIconImage'
|
||||
import {AppIconSet} from '#/screens/Settings/AppIconSettings/types'
|
||||
import {useAppIconSets} from '#/screens/Settings/AppIconSettings/useAppIconSets'
|
||||
@@ -23,7 +23,7 @@ export function AppIconSettingsScreen({}: Props) {
|
||||
const t = useTheme()
|
||||
const {_} = useLingui()
|
||||
const sets = useAppIconSets()
|
||||
const {currentAccount} = useSession()
|
||||
const gate = useGate()
|
||||
const [currentAppIcon, setCurrentAppIcon] = useState(() =>
|
||||
getAppIconName(DynamicAppIcon.getAppIcon()),
|
||||
)
|
||||
@@ -86,7 +86,7 @@ export function AppIconSettingsScreen({}: Props) {
|
||||
))}
|
||||
</Group>
|
||||
|
||||
{DISCOVER_DEBUG_DIDS[currentAccount?.did ?? ''] && (
|
||||
{IS_INTERNAL && gate('debug_subscriptions') && (
|
||||
<>
|
||||
<Text
|
||||
style={[
|
||||
|
||||
@@ -8,10 +8,10 @@ import Animated, {
|
||||
import {msg, Trans} from '@lingui/macro'
|
||||
import {useLingui} from '@lingui/react'
|
||||
|
||||
import {DISCOVER_DEBUG_DIDS} from '#/lib/constants'
|
||||
import {IS_INTERNAL} from '#/lib/app-info'
|
||||
import {CommonNavigatorParams, NativeStackScreenProps} from '#/lib/routes/types'
|
||||
import {useGate} from '#/lib/statsig/statsig'
|
||||
import {isNative} from '#/platform/detection'
|
||||
import {useSession} from '#/state/session'
|
||||
import {useSetThemePrefs, useThemePrefs} from '#/state/shell'
|
||||
import {SettingsListItem as AppIconSettingsListItem} from '#/screens/Settings/AppIconSettings/SettingsListItem'
|
||||
import {atoms as a, native, useAlf, useTheme} from '#/alf'
|
||||
@@ -29,6 +29,7 @@ type Props = NativeStackScreenProps<CommonNavigatorParams, 'AppearanceSettings'>
|
||||
export function AppearanceSettingsScreen({}: Props) {
|
||||
const {_} = useLingui()
|
||||
const {fonts} = useAlf()
|
||||
const gate = useGate()
|
||||
|
||||
const {colorMode, darkTheme} = useThemePrefs()
|
||||
const {setColorMode, setDarkTheme} = useSetThemePrefs()
|
||||
@@ -74,8 +75,6 @@ export function AppearanceSettingsScreen({}: Props) {
|
||||
[fonts],
|
||||
)
|
||||
|
||||
const {currentAccount} = useSession()
|
||||
|
||||
return (
|
||||
<LayoutAnimationConfig skipExiting skipEntering>
|
||||
<Layout.Screen testID="preferencesThreadsScreen">
|
||||
@@ -178,7 +177,7 @@ export function AppearanceSettingsScreen({}: Props) {
|
||||
onChange={onChangeFontScale}
|
||||
/>
|
||||
|
||||
{isNative && DISCOVER_DEBUG_DIDS[currentAccount?.did ?? ''] && (
|
||||
{isNative && IS_INTERNAL && gate('debug_subscriptions') && (
|
||||
<>
|
||||
<SettingsList.Divider />
|
||||
<AppIconSettingsListItem />
|
||||
|
||||
@@ -36,9 +36,8 @@ export function ContentAndMediaSettingsScreen({}: Props) {
|
||||
const inAppBrowserPref = useInAppBrowser()
|
||||
const setUseInAppBrowser = useSetInAppBrowser()
|
||||
const {enabled: trendingEnabled} = useTrendingConfig()
|
||||
const {trendingSidebarHidden, trendingDiscoverHidden} = useTrendingSettings()
|
||||
const {setTrendingSidebarHidden, setTrendingDiscoverHidden} =
|
||||
useTrendingSettingsApi()
|
||||
const {trendingDisabled} = useTrendingSettings()
|
||||
const {setTrendingDisabled} = useTrendingSettingsApi()
|
||||
|
||||
return (
|
||||
<Layout.Screen>
|
||||
@@ -117,30 +116,15 @@ export function ContentAndMediaSettingsScreen({}: Props) {
|
||||
{trendingEnabled && (
|
||||
<>
|
||||
<SettingsList.Divider />
|
||||
{!isNative && (
|
||||
<Toggle.Item
|
||||
name="show_trending_topics_sidebar"
|
||||
label={_(msg`Show trending topics in your sidebar`)}
|
||||
value={!trendingSidebarHidden}
|
||||
onChange={value => setTrendingSidebarHidden(!value)}>
|
||||
<SettingsList.Item>
|
||||
<SettingsList.ItemIcon icon={Graph} />
|
||||
<SettingsList.ItemText>
|
||||
<Trans>Show trending topics in your sidebar</Trans>
|
||||
</SettingsList.ItemText>
|
||||
<Toggle.Platform />
|
||||
</SettingsList.Item>
|
||||
</Toggle.Item>
|
||||
)}
|
||||
<Toggle.Item
|
||||
name="show_trending_topics_discover"
|
||||
label={_(msg`Show trending topics in your feed`)}
|
||||
value={!trendingDiscoverHidden}
|
||||
onChange={value => setTrendingDiscoverHidden(!value)}>
|
||||
name="show_trending_topics"
|
||||
label={_(msg`Enable trending topics`)}
|
||||
value={!trendingDisabled}
|
||||
onChange={value => setTrendingDisabled(!value)}>
|
||||
<SettingsList.Item>
|
||||
<SettingsList.ItemIcon icon={Graph} />
|
||||
<SettingsList.ItemText>
|
||||
<Trans>Show trending topics in your feed</Trans>
|
||||
<Trans>Enable trending topics</Trans>
|
||||
</SettingsList.ItemText>
|
||||
<Toggle.Platform />
|
||||
</SettingsList.Item>
|
||||
|
||||
@@ -111,7 +111,7 @@ export function StepHandle() {
|
||||
handleValueRef.current = val
|
||||
setDraftValue(val)
|
||||
}}
|
||||
label={_(msg`Input your user handle`)}
|
||||
label={_(msg`Type your desired username`)}
|
||||
defaultValue={draftValue}
|
||||
autoCapitalize="none"
|
||||
autoCorrect={false}
|
||||
@@ -122,7 +122,7 @@ export function StepHandle() {
|
||||
</View>
|
||||
{draftValue !== '' && (
|
||||
<Text style={[a.text_md]}>
|
||||
<Trans>Your full handle will be</Trans>{' '}
|
||||
<Trans>Your full username will be</Trans>{' '}
|
||||
<Text style={[a.text_md, a.font_bold]}>
|
||||
@{createFullHandle(draftValue, state.userDomain)}
|
||||
</Text>
|
||||
|
||||
@@ -139,7 +139,7 @@ export function Signup({onPressBack}: {onPressBack: () => void}) {
|
||||
{state.activeStep === SignupStep.INFO ? (
|
||||
<Trans>Your account</Trans>
|
||||
) : state.activeStep === SignupStep.HANDLE ? (
|
||||
<Trans>Your user handle</Trans>
|
||||
<Trans>Choose your username</Trans>
|
||||
) : (
|
||||
<Trans>Complete the challenge</Trans>
|
||||
)}
|
||||
|
||||
@@ -24,6 +24,7 @@ const _emitter = new EventEmitter()
|
||||
|
||||
export async function init() {
|
||||
broadcast.onmessage = onBroadcastMessage
|
||||
window.onstorage = onStorage
|
||||
const stored = readFromStorage()
|
||||
if (stored) {
|
||||
_state = stored
|
||||
@@ -90,6 +91,17 @@ export async function clearStorage() {
|
||||
}
|
||||
clearStorage satisfies PersistedApi['clearStorage']
|
||||
|
||||
function onStorage() {
|
||||
const next = readFromStorage()
|
||||
if (next === _state) {
|
||||
return
|
||||
}
|
||||
if (next) {
|
||||
_state = next
|
||||
_emitter.emit('update')
|
||||
}
|
||||
}
|
||||
|
||||
async function onBroadcastMessage({data}: MessageEvent) {
|
||||
if (
|
||||
typeof data === 'object' &&
|
||||
|
||||
@@ -125,12 +125,7 @@ const schema = z.object({
|
||||
subtitlesEnabled: z.boolean().optional(),
|
||||
/** @deprecated */
|
||||
mutedThreads: z.array(z.string()),
|
||||
|
||||
/*
|
||||
* Trending
|
||||
*/
|
||||
trendingSidebarHidden: z.boolean().optional(),
|
||||
trendingDiscoverHidden: z.boolean().optional(),
|
||||
trendingDisabled: z.boolean().optional(),
|
||||
})
|
||||
export type Schema = z.infer<typeof schema>
|
||||
|
||||
@@ -176,12 +171,7 @@ export const defaults: Schema = {
|
||||
kawaii: false,
|
||||
hasCheckedForStarterPack: false,
|
||||
subtitlesEnabled: true,
|
||||
|
||||
/*
|
||||
* Trending
|
||||
*/
|
||||
trendingSidebarHidden: false,
|
||||
trendingDiscoverHidden: false,
|
||||
trendingDisabled: false,
|
||||
}
|
||||
|
||||
export function tryParse(rawData: string): Schema | undefined {
|
||||
|
||||
@@ -3,31 +3,19 @@ import React from 'react'
|
||||
import * as persisted from '#/state/persisted'
|
||||
|
||||
type StateContext = {
|
||||
trendingSidebarHidden: Exclude<
|
||||
persisted.Schema['trendingSidebarHidden'],
|
||||
undefined
|
||||
>
|
||||
trendingDiscoverHidden: Exclude<
|
||||
persisted.Schema['trendingDiscoverHidden'],
|
||||
undefined
|
||||
>
|
||||
trendingDisabled: Exclude<persisted.Schema['trendingDisabled'], undefined>
|
||||
}
|
||||
type ApiContext = {
|
||||
setTrendingSidebarHidden(
|
||||
hidden: Exclude<persisted.Schema['trendingSidebarHidden'], undefined>,
|
||||
): void
|
||||
setTrendingDiscoverHidden(
|
||||
hidden: Exclude<persisted.Schema['trendingDiscoverHidden'], undefined>,
|
||||
setTrendingDisabled(
|
||||
hidden: Exclude<persisted.Schema['trendingDisabled'], undefined>,
|
||||
): void
|
||||
}
|
||||
|
||||
const StateContext = React.createContext<StateContext>({
|
||||
trendingSidebarHidden: Boolean(persisted.defaults.trendingSidebarHidden),
|
||||
trendingDiscoverHidden: Boolean(persisted.defaults.trendingDiscoverHidden),
|
||||
trendingDisabled: Boolean(persisted.defaults.trendingDisabled),
|
||||
})
|
||||
const ApiContext = React.createContext<ApiContext>({
|
||||
setTrendingSidebarHidden() {},
|
||||
setTrendingDiscoverHidden() {},
|
||||
setTrendingDisabled() {},
|
||||
})
|
||||
|
||||
function usePersistedBooleanValue<T extends keyof persisted.Schema>(key: T) {
|
||||
@@ -53,21 +41,16 @@ function usePersistedBooleanValue<T extends keyof persisted.Schema>(key: T) {
|
||||
}
|
||||
|
||||
export function Provider({children}: React.PropsWithChildren<{}>) {
|
||||
const [trendingSidebarHidden, setTrendingSidebarHidden] =
|
||||
usePersistedBooleanValue('trendingSidebarHidden')
|
||||
const [trendingDiscoverHidden, setTrendingDiscoverHidden] =
|
||||
usePersistedBooleanValue('trendingDiscoverHidden')
|
||||
const [trendingDisabled, setTrendingDisabled] =
|
||||
usePersistedBooleanValue('trendingDisabled')
|
||||
|
||||
/*
|
||||
* Context
|
||||
*/
|
||||
const state = React.useMemo(
|
||||
() => ({trendingSidebarHidden, trendingDiscoverHidden}),
|
||||
[trendingSidebarHidden, trendingDiscoverHidden],
|
||||
)
|
||||
const state = React.useMemo(() => ({trendingDisabled}), [trendingDisabled])
|
||||
const api = React.useMemo(
|
||||
() => ({setTrendingSidebarHidden, setTrendingDiscoverHidden}),
|
||||
[setTrendingSidebarHidden, setTrendingDiscoverHidden],
|
||||
() => ({setTrendingDisabled}),
|
||||
[setTrendingDisabled],
|
||||
)
|
||||
|
||||
return (
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import {AppBskyActorDefs, AppBskyActorSearchActors} from '@atproto/api'
|
||||
import {
|
||||
InfiniteData,
|
||||
keepPreviousData,
|
||||
QueryClient,
|
||||
QueryKey,
|
||||
useInfiniteQuery,
|
||||
@@ -13,10 +14,8 @@ import {useAgent} from '#/state/session'
|
||||
const RQKEY_ROOT = 'actor-search'
|
||||
export const RQKEY = (query: string) => [RQKEY_ROOT, query]
|
||||
|
||||
export const RQKEY_PAGINATED = (query: string) => [
|
||||
`${RQKEY_ROOT}_paginated`,
|
||||
query,
|
||||
]
|
||||
const RQKEY_ROOT_PAGINATED = `${RQKEY_ROOT}_paginated`
|
||||
export const RQKEY_PAGINATED = (query: string) => [RQKEY_ROOT_PAGINATED, query]
|
||||
|
||||
export function useActorSearch({
|
||||
query,
|
||||
@@ -42,9 +41,11 @@ export function useActorSearch({
|
||||
export function useActorSearchPaginated({
|
||||
query,
|
||||
enabled,
|
||||
maintainData,
|
||||
}: {
|
||||
query: string
|
||||
enabled?: boolean
|
||||
maintainData?: boolean
|
||||
}) {
|
||||
const agent = useAgent()
|
||||
return useInfiniteQuery<
|
||||
@@ -67,6 +68,7 @@ export function useActorSearchPaginated({
|
||||
enabled: enabled && !!query,
|
||||
initialPageParam: undefined,
|
||||
getNextPageParam: lastPage => lastPage.cursor,
|
||||
placeholderData: maintainData ? keepPreviousData : undefined,
|
||||
})
|
||||
}
|
||||
|
||||
@@ -89,4 +91,20 @@ export function* findAllProfilesInQueryData(
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const queryDatasPaginated = queryClient.getQueriesData<
|
||||
InfiniteData<AppBskyActorSearchActors.OutputSchema>
|
||||
>({
|
||||
queryKey: [RQKEY_ROOT_PAGINATED],
|
||||
})
|
||||
for (const [_queryKey, queryData] of queryDatasPaginated) {
|
||||
if (!queryData) {
|
||||
continue
|
||||
}
|
||||
for (const actor of queryData.pages.flatMap(page => page.actors)) {
|
||||
if (actor.did === did) {
|
||||
yield actor
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -144,11 +144,8 @@ export function usePostFeedQuery(
|
||||
/**
|
||||
* The number of posts to fetch in a single request. Because we filter
|
||||
* unwanted content, we may over-fetch here to try and fill pages by
|
||||
* `MIN_POSTS`.
|
||||
* `MIN_POSTS`. But if you're doing this, ask @why if it's ok first.
|
||||
*/
|
||||
|
||||
// TEMPORARILY DISABLING GATE TO PREVENT EVENT CONSUMPTION @TODO EME-GATE
|
||||
// const fetchLimit = gate('post_feed_lang_window') ? 100 : MIN_POSTS
|
||||
const fetchLimit = MIN_POSTS
|
||||
|
||||
// Make sure this doesn't invalidate unless really needed.
|
||||
|
||||
@@ -103,7 +103,13 @@ export function useSuggestedFollowsQuery(options?: SuggestedFollowsOptions) {
|
||||
})
|
||||
}
|
||||
|
||||
export function useSuggestedFollowsByActorQuery({did}: {did: string}) {
|
||||
export function useSuggestedFollowsByActorQuery({
|
||||
did,
|
||||
enabled,
|
||||
}: {
|
||||
did: string
|
||||
enabled?: boolean
|
||||
}) {
|
||||
const agent = useAgent()
|
||||
return useQuery({
|
||||
queryKey: suggestedFollowsByActorQueryKey(did),
|
||||
@@ -116,6 +122,7 @@ export function useSuggestedFollowsByActorQuery({did}: {did: string}) {
|
||||
: res.data.suggestions.filter(profile => !profile.viewer?.following)
|
||||
return {suggestions}
|
||||
},
|
||||
enabled,
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
@@ -4,25 +4,12 @@ import {describe, expect, it, jest} from '@jest/globals'
|
||||
import {agentToSessionAccountOrThrow} from '../agent'
|
||||
import {Action, getInitialState, reducer, State} from '../reducer'
|
||||
|
||||
jest.mock('statsig-react-native-expo', () => ({
|
||||
Statsig: {
|
||||
initialize() {},
|
||||
initializeCalled() {
|
||||
return false
|
||||
},
|
||||
},
|
||||
}))
|
||||
|
||||
jest.mock('jwt-decode', () => ({
|
||||
jwtDecode(_token: string) {
|
||||
return {}
|
||||
},
|
||||
}))
|
||||
|
||||
jest.mock('expo-localization', () => ({
|
||||
getLocales: () => [],
|
||||
}))
|
||||
|
||||
describe('session', () => {
|
||||
it('can log in and out', () => {
|
||||
let state = getInitialState([])
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import React from 'react'
|
||||
import React, {useMemo} from 'react'
|
||||
import {msg} from '@lingui/macro'
|
||||
import {useLingui} from '@lingui/react'
|
||||
|
||||
@@ -16,20 +16,32 @@ export enum ProgressGuideAction {
|
||||
Follow = 'follow',
|
||||
}
|
||||
|
||||
type ProgressGuideName = 'like-10-and-follow-7'
|
||||
type ProgressGuideName = 'like-10-and-follow-7' | 'follow-10'
|
||||
|
||||
/**
|
||||
* Progress Guides that extend this interface must specify their name in the `guide` field, so it can be used as a discriminated union
|
||||
*/
|
||||
interface BaseProgressGuide {
|
||||
guide: string
|
||||
guide: ProgressGuideName
|
||||
isComplete: boolean
|
||||
[key: string]: any
|
||||
}
|
||||
|
||||
interface Like10AndFollow7ProgressGuide extends BaseProgressGuide {
|
||||
export interface Like10AndFollow7ProgressGuide extends BaseProgressGuide {
|
||||
guide: 'like-10-and-follow-7'
|
||||
numLikes: number
|
||||
numFollows: number
|
||||
}
|
||||
|
||||
type ProgressGuide = Like10AndFollow7ProgressGuide | undefined
|
||||
export interface Follow10ProgressGuide extends BaseProgressGuide {
|
||||
guide: 'follow-10'
|
||||
numFollows: number
|
||||
}
|
||||
|
||||
export type ProgressGuide =
|
||||
| Like10AndFollow7ProgressGuide
|
||||
| Follow10ProgressGuide
|
||||
| undefined
|
||||
|
||||
const ProgressGuideContext = React.createContext<ProgressGuide>(undefined)
|
||||
|
||||
@@ -61,15 +73,28 @@ export function Provider({children}: React.PropsWithChildren<{}>) {
|
||||
const {mutateAsync, variables, isPending} =
|
||||
useSetActiveProgressGuideMutation()
|
||||
|
||||
const activeProgressGuide = (
|
||||
isPending ? variables : preferences?.bskyAppState?.activeProgressGuide
|
||||
) as ProgressGuide
|
||||
const activeProgressGuide = useMemo(() => {
|
||||
const rawProgressGuide = (
|
||||
isPending ? variables : preferences?.bskyAppState?.activeProgressGuide
|
||||
) as ProgressGuide
|
||||
|
||||
// ensure the unspecced attributes have the correct types
|
||||
if (activeProgressGuide?.guide === 'like-10-and-follow-7') {
|
||||
activeProgressGuide.numLikes = Number(activeProgressGuide.numLikes) || 0
|
||||
activeProgressGuide.numFollows = Number(activeProgressGuide.numFollows) || 0
|
||||
}
|
||||
if (!rawProgressGuide) return undefined
|
||||
|
||||
// ensure the unspecced attributes have the correct types
|
||||
// clone then mutate
|
||||
const {...maybeWronglyTypedProgressGuide} = rawProgressGuide
|
||||
if (maybeWronglyTypedProgressGuide?.guide === 'like-10-and-follow-7') {
|
||||
maybeWronglyTypedProgressGuide.numLikes =
|
||||
Number(maybeWronglyTypedProgressGuide.numLikes) || 0
|
||||
maybeWronglyTypedProgressGuide.numFollows =
|
||||
Number(maybeWronglyTypedProgressGuide.numFollows) || 0
|
||||
} else if (maybeWronglyTypedProgressGuide?.guide === 'follow-10') {
|
||||
maybeWronglyTypedProgressGuide.numFollows =
|
||||
Number(maybeWronglyTypedProgressGuide.numFollows) || 0
|
||||
}
|
||||
|
||||
return maybeWronglyTypedProgressGuide
|
||||
}, [isPending, variables, preferences])
|
||||
|
||||
const [localGuideState, setLocalGuideState] =
|
||||
React.useState<ProgressGuide>(undefined)
|
||||
@@ -82,7 +107,9 @@ export function Provider({children}: React.PropsWithChildren<{}>) {
|
||||
const firstLikeToastRef = React.useRef<ProgressGuideToastRef | null>(null)
|
||||
const fifthLikeToastRef = React.useRef<ProgressGuideToastRef | null>(null)
|
||||
const tenthLikeToastRef = React.useRef<ProgressGuideToastRef | null>(null)
|
||||
const guideCompleteToastRef = React.useRef<ProgressGuideToastRef | null>(null)
|
||||
|
||||
const fifthFollowToastRef = React.useRef<ProgressGuideToastRef | null>(null)
|
||||
const tenthFollowToastRef = React.useRef<ProgressGuideToastRef | null>(null)
|
||||
|
||||
const controls = React.useMemo(() => {
|
||||
return {
|
||||
@@ -93,7 +120,15 @@ export function Provider({children}: React.PropsWithChildren<{}>) {
|
||||
numLikes: 0,
|
||||
numFollows: 0,
|
||||
isComplete: false,
|
||||
}
|
||||
} satisfies ProgressGuide
|
||||
setLocalGuideState(guideObj)
|
||||
mutateAsync(guideObj)
|
||||
} else if (guide === 'follow-10') {
|
||||
const guideObj = {
|
||||
guide: 'follow-10',
|
||||
numFollows: 0,
|
||||
isComplete: false,
|
||||
} satisfies ProgressGuide
|
||||
setLocalGuideState(guideObj)
|
||||
mutateAsync(guideObj)
|
||||
}
|
||||
@@ -137,6 +172,26 @@ export function Provider({children}: React.PropsWithChildren<{}>) {
|
||||
isComplete: true,
|
||||
}
|
||||
}
|
||||
} else if (guide?.guide === 'follow-10') {
|
||||
if (action === ProgressGuideAction.Follow) {
|
||||
guide = {
|
||||
...guide,
|
||||
numFollows: (Number(guide.numFollows) || 0) + count,
|
||||
}
|
||||
|
||||
if (guide.numFollows === 5) {
|
||||
fifthFollowToastRef.current?.open()
|
||||
}
|
||||
if (guide.numFollows === 10) {
|
||||
tenthFollowToastRef.current?.open()
|
||||
}
|
||||
}
|
||||
if (Number(guide.numFollows) >= 10) {
|
||||
guide = {
|
||||
...guide,
|
||||
isComplete: true,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
setLocalGuideState(guide)
|
||||
@@ -167,9 +222,14 @@ export function Provider({children}: React.PropsWithChildren<{}>) {
|
||||
subtitle={_(msg`The Discover feed now knows what you like`)}
|
||||
/>
|
||||
<ProgressGuideToast
|
||||
ref={guideCompleteToastRef}
|
||||
title={_(msg`Algorithm training complete!`)}
|
||||
subtitle={_(msg`The Discover feed now knows what you like`)}
|
||||
ref={fifthFollowToastRef}
|
||||
title={_(msg`Half way there!`)}
|
||||
subtitle={_(msg`Follow 10 accounts`)}
|
||||
/>
|
||||
<ProgressGuideToast
|
||||
ref={tenthFollowToastRef}
|
||||
title={_(msg`Task complete - 10 follows!`)}
|
||||
subtitle={_(msg`You've found some people to follow`)}
|
||||
/>
|
||||
</>
|
||||
)}
|
||||
|
||||
@@ -134,7 +134,7 @@ export function NotificationFeed({
|
||||
highlightUnread={filter === 'all'}
|
||||
item={item}
|
||||
moderationOpts={moderationOpts!}
|
||||
hideTopBorder={index === 0 && item.notification.isRead}
|
||||
hideTopBorder={index === 0}
|
||||
/>
|
||||
)
|
||||
},
|
||||
|
||||
@@ -145,7 +145,7 @@ export function TabBar({
|
||||
const desktopStyles = StyleSheet.create({
|
||||
outer: {
|
||||
flexDirection: 'row',
|
||||
width: 598,
|
||||
width: 600,
|
||||
},
|
||||
contentContainer: {
|
||||
flexGrow: 1,
|
||||
|
||||
@@ -35,6 +35,7 @@ import {
|
||||
} from '#/state/queries/post-feed'
|
||||
import {useSession} from '#/state/session'
|
||||
import {useProgressGuide} from '#/state/shell/progress-guide'
|
||||
import {useBreakpoints} from '#/alf'
|
||||
import {ProgressGuide, SuggestedFollows} from '#/components/FeedInterstitials'
|
||||
import {TrendingInterstitial} from '#/components/interstitials/Trending'
|
||||
import {List, ListRef} from '../util/List'
|
||||
@@ -162,6 +163,7 @@ let PostFeed = ({
|
||||
const checkForNewRef = React.useRef<(() => void) | null>(null)
|
||||
const lastFetchRef = React.useRef<number>(Date.now())
|
||||
const [feedType, feedUri, feedTab] = feed.split('|')
|
||||
const {gtTablet} = useBreakpoints()
|
||||
|
||||
const opts = React.useMemo(
|
||||
() => ({enabled, ignoreFilterFor}),
|
||||
@@ -193,12 +195,16 @@ let PostFeed = ({
|
||||
}
|
||||
try {
|
||||
if (await pollLatest(data.pages[0])) {
|
||||
onHasNew(true)
|
||||
if (isEmpty) {
|
||||
refetch()
|
||||
} else {
|
||||
onHasNew(true)
|
||||
}
|
||||
}
|
||||
} catch (e) {
|
||||
logger.error('Poll latest failed', {feed, message: String(e)})
|
||||
}
|
||||
}, [feed, data, isFetching, onHasNew, enabled, disablePoll])
|
||||
}, [feed, data, isFetching, isEmpty, onHasNew, enabled, disablePoll, refetch])
|
||||
|
||||
const myDid = currentAccount?.did || ''
|
||||
const onPostCreated = React.useCallback(() => {
|
||||
@@ -226,20 +232,15 @@ let PostFeed = ({
|
||||
React.useEffect(() => {
|
||||
if (enabled && !disablePoll) {
|
||||
const timeSinceFirstLoad = Date.now() - lastFetchRef.current
|
||||
// DISABLED need to check if this is causing random feed refreshes -prf
|
||||
/*if (timeSinceFirstLoad > REFRESH_AFTER) {
|
||||
// do a full refresh
|
||||
scrollElRef?.current?.scrollToOffset({offset: 0, animated: false})
|
||||
queryClient.resetQueries({queryKey: RQKEY(feed)})
|
||||
} else*/ if (
|
||||
timeSinceFirstLoad > CHECK_LATEST_AFTER &&
|
||||
if (
|
||||
(isEmpty || timeSinceFirstLoad > CHECK_LATEST_AFTER) &&
|
||||
checkForNewRef.current
|
||||
) {
|
||||
// check for new on enable (aka on focus)
|
||||
checkForNewRef.current()
|
||||
}
|
||||
}
|
||||
}, [enabled, disablePoll, feed, queryClient, scrollElRef])
|
||||
}, [enabled, disablePoll, feed, queryClient, scrollElRef, isEmpty])
|
||||
React.useEffect(() => {
|
||||
let cleanup1: () => void | undefined, cleanup2: () => void | undefined
|
||||
const subscription = AppState.addEventListener('change', nextAppState => {
|
||||
@@ -260,11 +261,13 @@ let PostFeed = ({
|
||||
}
|
||||
}, [pollInterval])
|
||||
|
||||
const progressGuide = useProgressGuide('like-10-and-follow-7')
|
||||
const followProgressGuide = useProgressGuide('follow-10')
|
||||
const followAndLikeProgressGuide = useProgressGuide('like-10-and-follow-7')
|
||||
const {isDesktop} = useWebMediaQueries()
|
||||
const showProgressIntersitial = progressGuide && !isDesktop
|
||||
const showProgressIntersitial =
|
||||
(followProgressGuide || followAndLikeProgressGuide) && !isDesktop
|
||||
|
||||
const {trendingDiscoverHidden} = useTrendingSettings()
|
||||
const {trendingDisabled} = useTrendingSettings()
|
||||
|
||||
const feedItems: FeedRow[] = React.useMemo(() => {
|
||||
let feedKind: 'following' | 'discover' | 'profile' | undefined
|
||||
@@ -311,7 +314,11 @@ let PostFeed = ({
|
||||
type: 'interstitialProgressGuide',
|
||||
key: 'interstitial-' + sliceIndex + '-' + lastFetchedAt,
|
||||
})
|
||||
} else if (sliceIndex === 15 && !trendingDiscoverHidden) {
|
||||
} else if (
|
||||
sliceIndex === 15 &&
|
||||
!gtTablet &&
|
||||
!trendingDisabled
|
||||
) {
|
||||
arr.push({
|
||||
type: 'interstitialTrending',
|
||||
key: 'interstitial-' + sliceIndex + '-' + lastFetchedAt,
|
||||
@@ -402,7 +409,8 @@ let PostFeed = ({
|
||||
feedTab,
|
||||
hasSession,
|
||||
showProgressIntersitial,
|
||||
trendingDiscoverHidden,
|
||||
trendingDisabled,
|
||||
gtTablet,
|
||||
])
|
||||
|
||||
// events
|
||||
|
||||
@@ -53,6 +53,7 @@ let List = React.forwardRef<ListMethods, ListProps>(
|
||||
headerOffset,
|
||||
style,
|
||||
progressViewOffset,
|
||||
automaticallyAdjustsScrollIndicatorInsets = false,
|
||||
...props
|
||||
},
|
||||
ref,
|
||||
@@ -151,7 +152,14 @@ let List = React.forwardRef<ListMethods, ListProps>(
|
||||
return (
|
||||
<FlatList_INTERNAL
|
||||
{...props}
|
||||
scrollIndicatorInsets={{top: headerOffset, right: 1}}
|
||||
automaticallyAdjustsScrollIndicatorInsets={
|
||||
automaticallyAdjustsScrollIndicatorInsets
|
||||
}
|
||||
scrollIndicatorInsets={{
|
||||
top: headerOffset,
|
||||
right: 1,
|
||||
...props.scrollIndicatorInsets,
|
||||
}}
|
||||
contentOffset={contentOffset}
|
||||
refreshControl={refreshControl}
|
||||
onScroll={scrollHandler}
|
||||
|
||||
@@ -196,7 +196,9 @@ function Toast({
|
||||
/>
|
||||
</View>
|
||||
<View style={[a.h_full, a.justify_center, a.flex_1]}>
|
||||
<Text style={a.text_md}>{message}</Text>
|
||||
<Text style={a.text_md} emoji>
|
||||
{message}
|
||||
</Text>
|
||||
</View>
|
||||
</View>
|
||||
</GestureDetector>
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import {StyleSheet, View} from 'react-native'
|
||||
import {View} from 'react-native'
|
||||
import {
|
||||
FontAwesomeIcon,
|
||||
FontAwesomeIconStyle,
|
||||
@@ -7,12 +7,11 @@ import {msg, Trans} from '@lingui/macro'
|
||||
import {useLingui} from '@lingui/react'
|
||||
|
||||
import {usePalette} from '#/lib/hooks/usePalette'
|
||||
import {useWebMediaQueries} from '#/lib/hooks/useWebMediaQueries'
|
||||
import {useTheme} from '#/lib/ThemeContext'
|
||||
import {ViewHeader} from '#/view/com/util/ViewHeader'
|
||||
import {Button} from '../forms/Button'
|
||||
import {Text} from '../text/Text'
|
||||
import {CenteredView} from '../Views'
|
||||
import {atoms as a, useTheme} from '#/alf'
|
||||
import {Button, ButtonIcon, ButtonText} from '#/components/Button'
|
||||
import {ArrowRotateCounterClockwise_Stroke2_Corner0_Rounded as ArrowRotateCounterClockwiseIcon} from '#/components/icons/ArrowRotateCounterClockwise'
|
||||
import * as Layout from '#/components/Layout'
|
||||
import {Text} from '#/components/Typography'
|
||||
|
||||
export function ErrorScreen({
|
||||
title,
|
||||
@@ -29,22 +28,32 @@ export function ErrorScreen({
|
||||
testID?: string
|
||||
showHeader?: boolean
|
||||
}) {
|
||||
const theme = useTheme()
|
||||
const {isMobile} = useWebMediaQueries()
|
||||
const t = useTheme()
|
||||
const pal = usePalette('default')
|
||||
const {_} = useLingui()
|
||||
|
||||
return (
|
||||
<>
|
||||
{showHeader && isMobile && (
|
||||
<ViewHeader title={_(msg`Error`)} showBorder />
|
||||
<Layout.Center testID={testID}>
|
||||
{showHeader && (
|
||||
<Layout.Header.Outer>
|
||||
<Layout.Header.BackButton />
|
||||
<Layout.Header.Content>
|
||||
<Layout.Header.TitleText>
|
||||
<Trans>Error</Trans>
|
||||
</Layout.Header.TitleText>
|
||||
</Layout.Header.Content>
|
||||
<Layout.Header.Slot />
|
||||
</Layout.Header.Outer>
|
||||
)}
|
||||
<CenteredView testID={testID} style={[styles.outer, pal.view]}>
|
||||
<View style={styles.errorIconContainer}>
|
||||
<View style={[a.px_xl, a.py_2xl]}>
|
||||
<View style={[a.mb_md, a.align_center]}>
|
||||
<View
|
||||
style={[
|
||||
styles.errorIcon,
|
||||
{backgroundColor: theme.palette.inverted.background},
|
||||
a.rounded_full,
|
||||
{width: 50, height: 50},
|
||||
a.align_center,
|
||||
a.justify_center,
|
||||
{backgroundColor: t.palette.contrast_950},
|
||||
]}>
|
||||
<FontAwesomeIcon
|
||||
icon="exclamation"
|
||||
@@ -53,86 +62,50 @@ export function ErrorScreen({
|
||||
/>
|
||||
</View>
|
||||
</View>
|
||||
<Text type="title-lg" style={[styles.title, pal.text]}>
|
||||
<Text style={[a.text_center, a.font_heavy, a.text_2xl, a.mb_md]}>
|
||||
{title}
|
||||
</Text>
|
||||
<Text style={[styles.message, pal.text]}>{message}</Text>
|
||||
<Text style={[a.text_center, a.text_md, a.mb_xl]}>{message}</Text>
|
||||
{details && (
|
||||
<Text
|
||||
testID={`${testID}-details`}
|
||||
style={[styles.details, pal.text, pal.viewLight]}>
|
||||
{details}
|
||||
</Text>
|
||||
<View
|
||||
style={[
|
||||
a.w_full,
|
||||
a.border,
|
||||
t.atoms.border_contrast_medium,
|
||||
t.atoms.bg_contrast_25,
|
||||
a.mb_xl,
|
||||
a.py_sm,
|
||||
a.px_lg,
|
||||
a.rounded_xs,
|
||||
a.overflow_hidden,
|
||||
]}>
|
||||
<Text
|
||||
testID={`${testID}-details`}
|
||||
style={[a.text_center, a.text_md, t.atoms.text_contrast_high]}>
|
||||
{details}
|
||||
</Text>
|
||||
</View>
|
||||
)}
|
||||
{onPressTryAgain && (
|
||||
<View style={styles.btnContainer}>
|
||||
<View style={[a.align_center]}>
|
||||
<Button
|
||||
testID="errorScreenTryAgainButton"
|
||||
type="default"
|
||||
style={[styles.btn]}
|
||||
onPress={onPressTryAgain}
|
||||
accessibilityLabel={_(msg`Retry`)}
|
||||
variant="solid"
|
||||
color="secondary_inverted"
|
||||
size="small"
|
||||
label={_(msg`Retry`)}
|
||||
accessibilityHint={_(
|
||||
msg`Retries the last action, which errored out`,
|
||||
)}>
|
||||
<FontAwesomeIcon
|
||||
icon="arrows-rotate"
|
||||
style={pal.link as FontAwesomeIconStyle}
|
||||
size={16}
|
||||
/>
|
||||
<Text type="button" style={[styles.btnText, pal.link]}>
|
||||
<ButtonIcon icon={ArrowRotateCounterClockwiseIcon} />
|
||||
<ButtonText>
|
||||
<Trans context="action">Try again</Trans>
|
||||
</Text>
|
||||
</ButtonText>
|
||||
</Button>
|
||||
</View>
|
||||
)}
|
||||
</CenteredView>
|
||||
</>
|
||||
</View>
|
||||
</Layout.Center>
|
||||
)
|
||||
}
|
||||
|
||||
const styles = StyleSheet.create({
|
||||
outer: {
|
||||
flex: 1,
|
||||
paddingVertical: 30,
|
||||
paddingHorizontal: 14,
|
||||
},
|
||||
title: {
|
||||
textAlign: 'center',
|
||||
marginBottom: 10,
|
||||
},
|
||||
message: {
|
||||
textAlign: 'center',
|
||||
marginBottom: 20,
|
||||
},
|
||||
details: {
|
||||
textAlign: 'center',
|
||||
paddingVertical: 10,
|
||||
paddingHorizontal: 14,
|
||||
overflow: 'hidden',
|
||||
marginBottom: 20,
|
||||
},
|
||||
btnContainer: {
|
||||
alignItems: 'center',
|
||||
},
|
||||
btn: {
|
||||
flexDirection: 'row',
|
||||
alignItems: 'center',
|
||||
paddingHorizontal: 16,
|
||||
paddingVertical: 10,
|
||||
},
|
||||
btnText: {
|
||||
marginLeft: 5,
|
||||
},
|
||||
errorIconContainer: {
|
||||
alignItems: 'center',
|
||||
marginBottom: 10,
|
||||
},
|
||||
errorIcon: {
|
||||
borderRadius: 25,
|
||||
width: 50,
|
||||
height: 50,
|
||||
alignItems: 'center',
|
||||
justifyContent: 'center',
|
||||
},
|
||||
})
|
||||
|
||||
@@ -18,12 +18,13 @@ import {msg, plural} from '@lingui/macro'
|
||||
import {useLingui} from '@lingui/react'
|
||||
|
||||
import {IS_INTERNAL} from '#/lib/app-info'
|
||||
import {DISCOVER_DEBUG_DIDS, POST_CTRL_HITSLOP} from '#/lib/constants'
|
||||
import {POST_CTRL_HITSLOP} from '#/lib/constants'
|
||||
import {CountWheel} from '#/lib/custom-animations/CountWheel'
|
||||
import {AnimatedLikeIcon} from '#/lib/custom-animations/LikeIcon'
|
||||
import {useHaptics} from '#/lib/haptics'
|
||||
import {makeProfileLink} from '#/lib/routes/links'
|
||||
import {shareUrl} from '#/lib/sharing'
|
||||
import {useGate} from '#/lib/statsig/statsig'
|
||||
import {toShareUrl} from '#/lib/strings/url-helpers'
|
||||
import {Shadow} from '#/state/cache/types'
|
||||
import {useFeedFeedbackContext} from '#/state/feed-feedback'
|
||||
@@ -85,8 +86,8 @@ let PostCtrls = ({
|
||||
const {sendInteraction} = useFeedFeedbackContext()
|
||||
const {captureAction} = useProgressGuideControls()
|
||||
const playHaptic = useHaptics()
|
||||
const isDiscoverDebugUser =
|
||||
IS_INTERNAL || DISCOVER_DEBUG_DIDS[currentAccount?.did ?? '']
|
||||
const gate = useGate()
|
||||
const isDiscoverDebugUser = IS_INTERNAL || gate('debug_show_feedcontext')
|
||||
const isBlocked = Boolean(
|
||||
post.author.viewer?.blocking ||
|
||||
post.author.viewer?.blockedBy ||
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import React, {useCallback, useMemo} from 'react'
|
||||
import {StyleSheet} from 'react-native'
|
||||
import {SafeAreaView} from 'react-native-safe-area-context'
|
||||
import {
|
||||
AppBskyActorDefs,
|
||||
AppBskyGraphGetActorStarterPacks,
|
||||
@@ -122,13 +123,15 @@ function ProfileScreenInner({route}: Props) {
|
||||
}
|
||||
if (resolveError || profileError) {
|
||||
return (
|
||||
<ErrorScreen
|
||||
testID="profileErrorScreen"
|
||||
title={profileError ? _(msg`Not Found`) : _(msg`Oops!`)}
|
||||
message={cleanError(resolveError || profileError)}
|
||||
onPressTryAgain={onPressTryAgain}
|
||||
showHeader
|
||||
/>
|
||||
<SafeAreaView style={[a.flex_1]}>
|
||||
<ErrorScreen
|
||||
testID="profileErrorScreen"
|
||||
title={profileError ? _(msg`Not Found`) : _(msg`Oops!`)}
|
||||
message={cleanError(resolveError || profileError)}
|
||||
onPressTryAgain={onPressTryAgain}
|
||||
showHeader
|
||||
/>
|
||||
</SafeAreaView>
|
||||
)
|
||||
}
|
||||
if (profile && moderationOpts) {
|
||||
@@ -144,13 +147,15 @@ function ProfileScreenInner({route}: Props) {
|
||||
}
|
||||
// should never happen
|
||||
return (
|
||||
<ErrorScreen
|
||||
testID="profileErrorScreen"
|
||||
title="Oops!"
|
||||
message="Something went wrong and we're not sure what."
|
||||
onPressTryAgain={onPressTryAgain}
|
||||
showHeader
|
||||
/>
|
||||
<SafeAreaView style={[a.flex_1]}>
|
||||
<ErrorScreen
|
||||
testID="profileErrorScreen"
|
||||
title="Oops!"
|
||||
message="Something went wrong and we're not sure what."
|
||||
onPressTryAgain={onPressTryAgain}
|
||||
showHeader
|
||||
/>
|
||||
</SafeAreaView>
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
@@ -24,6 +24,7 @@ import {
|
||||
ProfileCardFeedLoadingPlaceholder,
|
||||
} from '#/view/com/util/LoadingPlaceholder'
|
||||
import {UserAvatar} from '#/view/com/util/UserAvatar'
|
||||
import {ExploreRecommendations} from '#/screens/Search/components/ExploreRecommendations'
|
||||
import {ExploreTrendingTopics} from '#/screens/Search/components/ExploreTrendingTopics'
|
||||
import {atoms as a, useTheme, ViewStyleProp} from '#/alf'
|
||||
import {Button} from '#/components/Button'
|
||||
@@ -244,6 +245,10 @@ type ExploreScreenItems =
|
||||
type: 'trendingTopics'
|
||||
key: string
|
||||
}
|
||||
| {
|
||||
type: 'recommendations'
|
||||
key: string
|
||||
}
|
||||
| {
|
||||
type: 'profile'
|
||||
key: string
|
||||
@@ -337,6 +342,11 @@ export function Explore() {
|
||||
key: `trending-topics`,
|
||||
})
|
||||
|
||||
i.push({
|
||||
type: 'recommendations',
|
||||
key: `recommendations`,
|
||||
})
|
||||
|
||||
i.push({
|
||||
type: 'header',
|
||||
key: 'suggested-follows-header',
|
||||
@@ -503,6 +513,9 @@ export function Explore() {
|
||||
case 'trendingTopics': {
|
||||
return <ExploreTrendingTopics />
|
||||
}
|
||||
case 'recommendations': {
|
||||
return <ExploreRecommendations />
|
||||
}
|
||||
case 'profile': {
|
||||
return (
|
||||
<View style={[a.border_b, t.atoms.border_contrast_low]}>
|
||||
|
||||
@@ -14,7 +14,7 @@ import {createStaticClick, InlineLinkText} from '#/components/Link'
|
||||
export function DesktopFeeds() {
|
||||
const t = useTheme()
|
||||
const {_} = useLingui()
|
||||
const {data: pinnedFeedInfos} = usePinnedFeedsInfos()
|
||||
const {data: pinnedFeedInfos, error, isLoading} = usePinnedFeedsInfos()
|
||||
const selectedFeed = useSelectedFeed()
|
||||
const setSelectedFeed = useSetSelectedFeed()
|
||||
const navigation = useNavigation<NavigationProp>()
|
||||
@@ -25,7 +25,34 @@ export function DesktopFeeds() {
|
||||
return getCurrentRoute(state)
|
||||
})
|
||||
|
||||
if (!pinnedFeedInfos) {
|
||||
if (isLoading) {
|
||||
return (
|
||||
<View
|
||||
style={[
|
||||
{
|
||||
gap: 12,
|
||||
},
|
||||
]}>
|
||||
{Array(5)
|
||||
.fill(0)
|
||||
.map((_, i) => (
|
||||
<View
|
||||
key={i}
|
||||
style={[
|
||||
a.rounded_sm,
|
||||
t.atoms.bg_contrast_25,
|
||||
{
|
||||
height: 16,
|
||||
width: i % 2 === 0 ? '60%' : '80%',
|
||||
},
|
||||
]}
|
||||
/>
|
||||
))}
|
||||
</View>
|
||||
)
|
||||
}
|
||||
|
||||
if (error || !pinnedFeedInfos) {
|
||||
return null
|
||||
}
|
||||
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
import React from 'react'
|
||||
import {View} from 'react-native'
|
||||
import {msg, Trans} from '@lingui/macro'
|
||||
import {useLingui} from '@lingui/react'
|
||||
import {useNavigation} from '@react-navigation/core'
|
||||
|
||||
import {FEEDBACK_FORM_URL, HELP_DESK_URL} from '#/lib/constants'
|
||||
import {useWebMediaQueries} from '#/lib/hooks/useWebMediaQueries'
|
||||
@@ -15,6 +17,24 @@ import {InlineLinkText} from '#/components/Link'
|
||||
import {ProgressGuideList} from '#/components/ProgressGuide/List'
|
||||
import {Text} from '#/components/Typography'
|
||||
|
||||
function useWebQueryParams() {
|
||||
const navigation = useNavigation()
|
||||
const [params, setParams] = React.useState<Record<string, string>>({})
|
||||
|
||||
React.useEffect(() => {
|
||||
return navigation.addListener('state', e => {
|
||||
try {
|
||||
const {state} = e.data
|
||||
const lastRoute = state.routes[state.routes.length - 1]
|
||||
const {params} = lastRoute
|
||||
setParams(params)
|
||||
} catch (e) {}
|
||||
})
|
||||
}, [navigation, setParams])
|
||||
|
||||
return params
|
||||
}
|
||||
|
||||
export function DesktopRightNav({routeName}: {routeName: string}) {
|
||||
const t = useTheme()
|
||||
const {_} = useLingui()
|
||||
@@ -22,6 +42,8 @@ export function DesktopRightNav({routeName}: {routeName: string}) {
|
||||
const kawaii = useKawaiiMode()
|
||||
const gutters = useGutters(['base', 0, 'base', 'wide'])
|
||||
const isSearchScreen = routeName === 'Search'
|
||||
const {q: searchQuery} = useWebQueryParams()
|
||||
const showTrending = !isSearchScreen || (isSearchScreen && !!searchQuery)
|
||||
|
||||
const {isTablet} = useWebMediaQueries()
|
||||
if (isTablet) {
|
||||
@@ -57,7 +79,7 @@ export function DesktopRightNav({routeName}: {routeName: string}) {
|
||||
</>
|
||||
)}
|
||||
|
||||
{!isSearchScreen && <SidebarTrendingTopics />}
|
||||
{showTrending && <SidebarTrendingTopics />}
|
||||
|
||||
<Text style={[a.leading_snug, t.atoms.text_contrast_low]}>
|
||||
{hasSession && (
|
||||
|
||||
@@ -6,10 +6,7 @@ import {
|
||||
useTrendingSettings,
|
||||
useTrendingSettingsApi,
|
||||
} from '#/state/preferences/trending'
|
||||
import {
|
||||
DEFAULT_LIMIT as TRENDING_TOPICS_COUNT,
|
||||
useTrendingTopics,
|
||||
} from '#/state/queries/trending/useTrendingTopics'
|
||||
import {useTrendingTopics} from '#/state/queries/trending/useTrendingTopics'
|
||||
import {useTrendingConfig} from '#/state/trending-config'
|
||||
import {atoms as a, useTheme} from '#/alf'
|
||||
import {Button, ButtonIcon} from '#/components/Button'
|
||||
@@ -24,23 +21,25 @@ import {
|
||||
} from '#/components/TrendingTopics'
|
||||
import {Text} from '#/components/Typography'
|
||||
|
||||
const TRENDING_LIMIT = 6
|
||||
|
||||
export function SidebarTrendingTopics() {
|
||||
const {enabled} = useTrendingConfig()
|
||||
const {trendingSidebarHidden} = useTrendingSettings()
|
||||
return !enabled ? null : trendingSidebarHidden ? null : <Inner />
|
||||
const {trendingDisabled} = useTrendingSettings()
|
||||
return !enabled ? null : trendingDisabled ? null : <Inner />
|
||||
}
|
||||
|
||||
function Inner() {
|
||||
const t = useTheme()
|
||||
const {_} = useLingui()
|
||||
const trendingPrompt = Prompt.usePromptControl()
|
||||
const {setTrendingSidebarHidden} = useTrendingSettingsApi()
|
||||
const {setTrendingDisabled} = useTrendingSettingsApi()
|
||||
const {data: trending, error, isLoading} = useTrendingTopics()
|
||||
const noTopics = !isLoading && !error && !trending?.topics?.length
|
||||
|
||||
return error || noTopics ? null : (
|
||||
<>
|
||||
<View style={[a.gap_md]}>
|
||||
<View style={[a.gap_sm, {paddingBottom: 2}]}>
|
||||
<View style={[a.flex_row, a.align_center, a.gap_xs]}>
|
||||
<Graph size="sm" />
|
||||
<Text
|
||||
@@ -53,7 +52,7 @@ function Inner() {
|
||||
<Trans>Trending</Trans>
|
||||
</Text>
|
||||
<Button
|
||||
label={_(msg`Hide trending topics from your sidebar`)}
|
||||
label={_(msg`Hide trending topics`)}
|
||||
size="tiny"
|
||||
variant="ghost"
|
||||
color="secondary"
|
||||
@@ -63,16 +62,16 @@ function Inner() {
|
||||
</Button>
|
||||
</View>
|
||||
|
||||
<View style={[a.flex_row, a.flex_wrap, a.gap_xs]}>
|
||||
<View style={[a.flex_row, a.flex_wrap, {gap: '6px 4px'}]}>
|
||||
{isLoading ? (
|
||||
Array(TRENDING_TOPICS_COUNT)
|
||||
Array(TRENDING_LIMIT)
|
||||
.fill(0)
|
||||
.map((_n, i) => (
|
||||
<TrendingTopicSkeleton key={i} size="small" index={i} />
|
||||
))
|
||||
) : !trending?.topics ? null : (
|
||||
<>
|
||||
{trending.topics.map(topic => (
|
||||
{trending.topics.slice(0, TRENDING_LIMIT).map(topic => (
|
||||
<TrendingTopicLink key={topic.link} topic={topic}>
|
||||
{({hovered}) => (
|
||||
<TrendingTopic
|
||||
@@ -95,11 +94,9 @@ function Inner() {
|
||||
<Prompt.Basic
|
||||
control={trendingPrompt}
|
||||
title={_(msg`Hide trending topics?`)}
|
||||
description={_(
|
||||
msg`This is a device setting, and will apply to all accounts on this device. You can update this later from your settings.`,
|
||||
)}
|
||||
description={_(msg`You can update this later from your settings.`)}
|
||||
confirmButtonCta={_(msg`Hide`)}
|
||||
onConfirm={() => setTrendingSidebarHidden(true)}
|
||||
onConfirm={() => setTrendingDisabled(true)}
|
||||
/>
|
||||
<Divider />
|
||||
</>
|
||||
|
||||
@@ -3696,10 +3696,10 @@
|
||||
mv "~2"
|
||||
safe-json-stringify "~1"
|
||||
|
||||
"@expo/cli@0.22.3":
|
||||
version "0.22.3"
|
||||
resolved "https://registry.yarnpkg.com/@expo/cli/-/cli-0.22.3.tgz#8dbcc9396abf01b2dd91fe7f34fc23fdd2d8cc7f"
|
||||
integrity sha512-1HBtqInFDFHUJWzTJ1CJj5MR3JwvOiozmRUWF2kVQAeq/bKzSYM6We6B3XoZBM5XP6z6WtnrG87C7BjeW5E/cA==
|
||||
"@expo/cli@0.22.6":
|
||||
version "0.22.6"
|
||||
resolved "https://registry.yarnpkg.com/@expo/cli/-/cli-0.22.6.tgz#dc27b685d2252027549d839437c5285c7814ec3f"
|
||||
integrity sha512-eDjCnc3uHl2+SJ6aZ5seK0FkMp0W12oAdSI4A/yV8ecYtXzG8X87sfKAISEWt44B4DqJ0a1LEqCD6Vtvc783Mg==
|
||||
dependencies:
|
||||
"@0no-co/graphql.web" "^1.0.8"
|
||||
"@babel/runtime" "^7.20.0"
|
||||
@@ -3710,15 +3710,15 @@
|
||||
"@expo/env" "~0.4.0"
|
||||
"@expo/image-utils" "^0.6.0"
|
||||
"@expo/json-file" "^9.0.0"
|
||||
"@expo/metro-config" "~0.19.0"
|
||||
"@expo/metro-config" "~0.19.8"
|
||||
"@expo/osascript" "^2.0.31"
|
||||
"@expo/package-manager" "^1.5.0"
|
||||
"@expo/plist" "^0.2.0"
|
||||
"@expo/prebuild-config" "^8.0.22"
|
||||
"@expo/prebuild-config" "^8.0.23"
|
||||
"@expo/rudder-sdk-node" "^1.1.1"
|
||||
"@expo/spawn-async" "^1.7.2"
|
||||
"@expo/xcpretty" "^4.3.0"
|
||||
"@react-native/dev-middleware" "0.76.3"
|
||||
"@react-native/dev-middleware" "0.76.5"
|
||||
"@urql/core" "^5.0.6"
|
||||
"@urql/exchange-retry" "^1.3.0"
|
||||
accepts "^1.3.8"
|
||||
@@ -3894,10 +3894,10 @@
|
||||
dotenv-expand "~11.0.6"
|
||||
getenv "^1.0.0"
|
||||
|
||||
"@expo/fingerprint@0.11.3":
|
||||
version "0.11.3"
|
||||
resolved "https://registry.yarnpkg.com/@expo/fingerprint/-/fingerprint-0.11.3.tgz#e370ae8f83e0642f752b058e2102e984a0a5bc98"
|
||||
integrity sha512-9lgXmcIePvZ7Wef63XtvuN3HfCUevF4E4tQPdEbH9/dUWwpOvvwQ3KT4OJ9jdh8JJ3nTdO9eDQ/8k8xr1aQ5Kg==
|
||||
"@expo/fingerprint@0.11.4":
|
||||
version "0.11.4"
|
||||
resolved "https://registry.yarnpkg.com/@expo/fingerprint/-/fingerprint-0.11.4.tgz#f40bbc784e10a065b783091e0d060a7428d41a7c"
|
||||
integrity sha512-FfcvHjrWjOJ17wiMfr1iQ1YDyjlj8qfxG+GDce0khrjNSkzRjVdCOIFsMvfVSBPnOPX5NuZlgMRvMkcPUtGClA==
|
||||
dependencies:
|
||||
"@expo/spawn-async" "^1.7.2"
|
||||
arg "^5.0.2"
|
||||
@@ -3949,34 +3949,10 @@
|
||||
json5 "^2.2.3"
|
||||
write-file-atomic "^2.3.0"
|
||||
|
||||
"@expo/metro-config@0.19.6":
|
||||
version "0.19.6"
|
||||
resolved "https://registry.yarnpkg.com/@expo/metro-config/-/metro-config-0.19.6.tgz#81bbe70c77a322d6c688738fd7b736a3cbb7c5bd"
|
||||
integrity sha512-pRwZyOstsQa1+Ecss3wOqC28wjyjq9qxvJaQL3LH4G8Sef9x2PX+ySRApeQ01nl4ZN5nlyez6iVDF51tn/WhOw==
|
||||
dependencies:
|
||||
"@babel/core" "^7.20.0"
|
||||
"@babel/generator" "^7.20.5"
|
||||
"@babel/parser" "^7.20.0"
|
||||
"@babel/types" "^7.20.0"
|
||||
"@expo/config" "~10.0.4"
|
||||
"@expo/env" "~0.4.0"
|
||||
"@expo/json-file" "~9.0.0"
|
||||
"@expo/spawn-async" "^1.7.2"
|
||||
chalk "^4.1.0"
|
||||
debug "^4.3.2"
|
||||
fs-extra "^9.1.0"
|
||||
getenv "^1.0.0"
|
||||
glob "^10.4.2"
|
||||
jsc-safe-url "^0.2.4"
|
||||
lightningcss "~1.27.0"
|
||||
minimatch "^3.0.4"
|
||||
postcss "~8.4.32"
|
||||
resolve-from "^5.0.0"
|
||||
|
||||
"@expo/metro-config@~0.19.0":
|
||||
version "0.19.4"
|
||||
resolved "https://registry.yarnpkg.com/@expo/metro-config/-/metro-config-0.19.4.tgz#940b6fad7809a92a8ffdb1bbe87aa805f5822c6b"
|
||||
integrity sha512-2SWwYN8MZvMIRawWEr+1RBYncitPwu2VMACRYig+wBycJ9fsPb6BMVmBYi+3MHDUlJHNy/Bqfw++jn1eqBFETQ==
|
||||
"@expo/metro-config@0.19.8", "@expo/metro-config@~0.19.8":
|
||||
version "0.19.8"
|
||||
resolved "https://registry.yarnpkg.com/@expo/metro-config/-/metro-config-0.19.8.tgz#f1ea552b6fa5217093fe364ff5ca78a7e261a28b"
|
||||
integrity sha512-dVAOetouQYuOTEJ2zR0OTLNPOH6zPkeEt5fY53TK0Wxi1QmtsmH6vEWg05U4zkSJ6f1aXmQ0Za77R8QxuukESA==
|
||||
dependencies:
|
||||
"@babel/core" "^7.20.0"
|
||||
"@babel/generator" "^7.20.5"
|
||||
@@ -4032,17 +4008,17 @@
|
||||
base64-js "^1.2.3"
|
||||
xmlbuilder "^14.0.0"
|
||||
|
||||
"@expo/prebuild-config@8.0.22", "@expo/prebuild-config@^8.0.22":
|
||||
version "8.0.22"
|
||||
resolved "https://registry.yarnpkg.com/@expo/prebuild-config/-/prebuild-config-8.0.22.tgz#6e2762a5f333a0519f83ae05b69da45e3e26a913"
|
||||
integrity sha512-Kwlf3ymHH37W2nuNA9FzYgZvrImJScLA98939kapnOxfNGAPhmhEw26sfIGmBWAa8ymdL6p+HXQ3+b/xJ74bOg==
|
||||
"@expo/prebuild-config@^8.0.23":
|
||||
version "8.0.23"
|
||||
resolved "https://registry.yarnpkg.com/@expo/prebuild-config/-/prebuild-config-8.0.23.tgz#2ec6d5464f35d308bdb94ba75b7e6aba0ebb507d"
|
||||
integrity sha512-Zf01kFiN2PISmLb0DhIAJh76v3J2oYUKSjiAtGZLOH0HUz59by/qdyU4mGHWdeyRdCCrLUA21Rct2MBykvRMsg==
|
||||
dependencies:
|
||||
"@expo/config" "~10.0.4"
|
||||
"@expo/config-plugins" "~9.0.10"
|
||||
"@expo/config-types" "^52.0.0"
|
||||
"@expo/image-utils" "^0.6.0"
|
||||
"@expo/json-file" "^9.0.0"
|
||||
"@react-native/normalize-colors" "0.76.3"
|
||||
"@react-native/normalize-colors" "0.76.5"
|
||||
debug "^4.3.1"
|
||||
fs-extra "^9.0.0"
|
||||
resolve-from "^5.0.0"
|
||||
@@ -5617,7 +5593,7 @@
|
||||
dependencies:
|
||||
"@react-native/codegen" "0.76.1"
|
||||
|
||||
"@react-native/babel-preset@0.76.1", "@react-native/babel-preset@0.76.3":
|
||||
"@react-native/babel-preset@0.76.1", "@react-native/babel-preset@0.76.3", "@react-native/babel-preset@0.76.5":
|
||||
version "0.76.1"
|
||||
resolved "https://registry.yarnpkg.com/@react-native/babel-preset/-/babel-preset-0.76.1.tgz#2b9fd113e7c7889c1e87d6a36b7cb0f36118e7a6"
|
||||
integrity sha512-b6YRmA13CmVuTQKHRen/Q0glHwmZFZoEDs+MJ1NL0UNHq9V5ytvdwTW1ntkmjtXuTnPMzkwYvumJBN9UTZjkBA==
|
||||
@@ -5718,6 +5694,11 @@
|
||||
resolved "https://registry.yarnpkg.com/@react-native/debugger-frontend/-/debugger-frontend-0.76.3.tgz#531e616f6dad159a58117efc69cec20422d15b0d"
|
||||
integrity sha512-pMHQ3NpPB28RxXciSvm2yD+uDx3pkhzfuWkc7VFgOduyzPSIr0zotUiOJzsAtrj8++bPbOsAraCeQhCqoOTWQw==
|
||||
|
||||
"@react-native/debugger-frontend@0.76.5":
|
||||
version "0.76.5"
|
||||
resolved "https://registry.yarnpkg.com/@react-native/debugger-frontend/-/debugger-frontend-0.76.5.tgz#0e89940543fb5029506690b83f12547d0bf42cc4"
|
||||
integrity sha512-5gtsLfBaSoa9WP8ToDb/8NnDBLZjv4sybQQj7rDKytKOdsXm3Pr2y4D7x7GQQtP1ZQRqzU0X0OZrhRz9xNnOqA==
|
||||
|
||||
"@react-native/dev-middleware@0.76.3":
|
||||
version "0.76.3"
|
||||
resolved "https://registry.yarnpkg.com/@react-native/dev-middleware/-/dev-middleware-0.76.3.tgz#52edc76c88e0c2c436eb989551b827bf69f2a56f"
|
||||
@@ -5735,6 +5716,23 @@
|
||||
serve-static "^1.13.1"
|
||||
ws "^6.2.3"
|
||||
|
||||
"@react-native/dev-middleware@0.76.5":
|
||||
version "0.76.5"
|
||||
resolved "https://registry.yarnpkg.com/@react-native/dev-middleware/-/dev-middleware-0.76.5.tgz#10d02fcc6c3c9d24f6dc147c2ef95d6fa6bd3787"
|
||||
integrity sha512-f8eimsxpkvMgJia7POKoUu9uqjGF6KgkxX4zqr/a6eoR1qdEAWUd6PonSAqtag3PAqvEaJpB99gLH2ZJI1nDGg==
|
||||
dependencies:
|
||||
"@isaacs/ttlcache" "^1.4.1"
|
||||
"@react-native/debugger-frontend" "0.76.5"
|
||||
chrome-launcher "^0.15.2"
|
||||
chromium-edge-launcher "^0.2.0"
|
||||
connect "^3.6.5"
|
||||
debug "^2.2.0"
|
||||
nullthrows "^1.1.1"
|
||||
open "^7.0.3"
|
||||
selfsigned "^2.4.1"
|
||||
serve-static "^1.13.1"
|
||||
ws "^6.2.3"
|
||||
|
||||
"@react-native/eslint-config@^0.76.2":
|
||||
version "0.76.2"
|
||||
resolved "https://registry.yarnpkg.com/@react-native/eslint-config/-/eslint-config-0.76.2.tgz#2741eee69ff194b8adc15281c0cb9695ba015ef0"
|
||||
@@ -5779,7 +5777,7 @@
|
||||
hermes-parser "0.23.1"
|
||||
nullthrows "^1.1.1"
|
||||
|
||||
"@react-native/normalize-colors@0.76.1", "@react-native/normalize-colors@0.76.3", "@react-native/normalize-colors@^0.73.0", "@react-native/normalize-colors@^0.74.1":
|
||||
"@react-native/normalize-colors@0.76.1", "@react-native/normalize-colors@0.76.3", "@react-native/normalize-colors@0.76.5", "@react-native/normalize-colors@^0.73.0", "@react-native/normalize-colors@^0.74.1":
|
||||
version "0.76.1"
|
||||
resolved "https://registry.yarnpkg.com/@react-native/normalize-colors/-/normalize-colors-0.76.1.tgz#df8d54d78917a9f075283382fec834f5ccaecefd"
|
||||
integrity sha512-/+CUk/wGWIdXbJYVLw/q6Fs8Z0x91zzfXIbNiZUdSW1TNEDmytkF371H8a1/Nx3nWa1RqCMVsaZHCG4zqxeDvg==
|
||||
@@ -8089,10 +8087,10 @@ babel-preset-expo@^12.0.2:
|
||||
babel-plugin-react-native-web "~0.19.13"
|
||||
react-refresh "^0.14.2"
|
||||
|
||||
babel-preset-expo@~12.0.3:
|
||||
version "12.0.3"
|
||||
resolved "https://registry.yarnpkg.com/babel-preset-expo/-/babel-preset-expo-12.0.3.tgz#2ad62fe007517704841788cfea38b333e307663f"
|
||||
integrity sha512-1695e8y3U/HjifKx33vcNnFMSUSXwPWwhFxRlL6NRx2TENN6gySH82gPOWgxcra6gi+EJgXx52xG3PcqTjwW6w==
|
||||
babel-preset-expo@~12.0.4:
|
||||
version "12.0.4"
|
||||
resolved "https://registry.yarnpkg.com/babel-preset-expo/-/babel-preset-expo-12.0.4.tgz#ec965530d866c8905aac1fa478562cb08ab32a55"
|
||||
integrity sha512-SAzAwqpyjA+/OFrU95OOioj6oTeCv4+rRfrNmBTy5S/gJswrZKBSPJioFudIaJBy43W+BL7HA5AspBIF6tO/aA==
|
||||
dependencies:
|
||||
"@babel/plugin-proposal-decorators" "^7.12.9"
|
||||
"@babel/plugin-transform-export-namespace-from" "^7.22.11"
|
||||
@@ -8100,7 +8098,7 @@ babel-preset-expo@~12.0.3:
|
||||
"@babel/plugin-transform-parameters" "^7.22.15"
|
||||
"@babel/preset-react" "^7.22.15"
|
||||
"@babel/preset-typescript" "^7.23.0"
|
||||
"@react-native/babel-preset" "0.76.3"
|
||||
"@react-native/babel-preset" "0.76.5"
|
||||
babel-plugin-react-native-web "~0.19.13"
|
||||
react-refresh "^0.14.2"
|
||||
|
||||
@@ -10456,10 +10454,10 @@ expo-eas-client@~0.13.0:
|
||||
resolved "https://registry.yarnpkg.com/expo-eas-client/-/expo-eas-client-0.13.1.tgz#ebca627f3f58a54906394eb3f5d22f41a1822618"
|
||||
integrity sha512-IyeDiM6YSJG0c45kbuEo0qt76z0KTEZtisEFEtle+b+vfn9I3N+r3jbPscaI4yS3P6gpuoDyHv81YDVC6Dmkhw==
|
||||
|
||||
expo-file-system@^18.0.4, expo-file-system@~18.0.4:
|
||||
version "18.0.4"
|
||||
resolved "https://registry.yarnpkg.com/expo-file-system/-/expo-file-system-18.0.4.tgz#eecf8dc0b3b545e9ac5cd00352665afe2d57732f"
|
||||
integrity sha512-aAWEDwnu0XHOBYvQ9Q0+QIa+483vYJaC4IDsXyWQ73Rtsg273NZh5kYowY+cAocvoSmA99G6htrLBn11ax2bTQ==
|
||||
expo-file-system@^18.0.6, expo-file-system@~18.0.6:
|
||||
version "18.0.6"
|
||||
resolved "https://registry.yarnpkg.com/expo-file-system/-/expo-file-system-18.0.6.tgz#43f7718530d0e2aa1f49bca7ccb721007acabf2c"
|
||||
integrity sha512-gGEwIJCXV3/wpIJ/wRyhmieLOSAY7HeFFjb+wEfHs04aE63JYR+rXXV4b7rBpEh1ZgNV9U91zfet/iQG7J8HBQ==
|
||||
dependencies:
|
||||
web-streams-polyfill "^3.3.2"
|
||||
|
||||
@@ -10542,10 +10540,10 @@ expo-media-library@~17.0.3:
|
||||
resolved "https://registry.yarnpkg.com/expo-media-library/-/expo-media-library-17.0.3.tgz#4ee3e6a8a2544887d910a72eaf2a15858b78cc0e"
|
||||
integrity sha512-vo8AqWxv1C8+U8dA5W43qs8+3dgD3VZDvcCkZBQTBnGr/2Rs7x6nNQD5s7UfYyr6qmW6102JB3+OUKHpkwEssg==
|
||||
|
||||
expo-modules-autolinking@2.0.3:
|
||||
version "2.0.3"
|
||||
resolved "https://registry.yarnpkg.com/expo-modules-autolinking/-/expo-modules-autolinking-2.0.3.tgz#c0de0129bedf1b6f9aa36093e435d00509f27fcd"
|
||||
integrity sha512-Q/ALJ54eS7Cr7cmbP+unEDTkHFQivQerWWrqZxuXOrSFYGCYU22+/xAZXaJOpZwseOVsP74zSkoRY/wBimVs7w==
|
||||
expo-modules-autolinking@2.0.4:
|
||||
version "2.0.4"
|
||||
resolved "https://registry.yarnpkg.com/expo-modules-autolinking/-/expo-modules-autolinking-2.0.4.tgz#28fcd12fb0d066a2933cca3bf3b597da0f6b2f2a"
|
||||
integrity sha512-e0p+19NhmD50U7s7BV7kWIypWmTNC9n/VlJKlXS05hM/zX7pe6JKmXyb+BFnXJq3SLBalLCUY0tu2gEUF3XeVg==
|
||||
dependencies:
|
||||
"@expo/spawn-async" "^1.7.2"
|
||||
chalk "^4.1.0"
|
||||
@@ -10556,7 +10554,14 @@ expo-modules-autolinking@2.0.3:
|
||||
require-from-string "^2.0.2"
|
||||
resolve-from "^5.0.0"
|
||||
|
||||
expo-modules-core@2.1.1, expo-modules-core@^2.1.1:
|
||||
expo-modules-core@2.1.2:
|
||||
version "2.1.2"
|
||||
resolved "https://registry.yarnpkg.com/expo-modules-core/-/expo-modules-core-2.1.2.tgz#258be4fbd162b69eb4ad2789131ac2dc7e85fc08"
|
||||
integrity sha512-0OhMU5S8zf9c/CRh1MwiXfOInI9wzz6yiIh5RuR/9J7N6xHRum68hInsPbaSc1UQpo08ZZLM4MPsbpoNRUoqIg==
|
||||
dependencies:
|
||||
invariant "^2.2.4"
|
||||
|
||||
expo-modules-core@^2.1.1:
|
||||
version "2.1.1"
|
||||
resolved "https://registry.yarnpkg.com/expo-modules-core/-/expo-modules-core-2.1.1.tgz#970af4cfd70c8aa6fc0096dd0a6578aa003a479f"
|
||||
integrity sha512-yQzYCLR2mre4BNMXuqkeJ0oSNgmGEMI6BcmIzeNZbC2NFEjiaDpKvlV9bclYCtyVhUEVNbJcEPYMr6c1Y4eR4w==
|
||||
@@ -10599,12 +10604,12 @@ expo-sharing@^13.0.0:
|
||||
resolved "https://registry.yarnpkg.com/expo-sharing/-/expo-sharing-13.0.0.tgz#fbc46f4afdaa265a2811fe88c2a589aae2d2de0f"
|
||||
integrity sha512-b23ymicRmYn/Pjj05sl9tFZHN5cH9I1f0yiqY1Yk8Q3oCx0Aznri82DnTYA4T/J6D9vrkraX0wQ4jWVMOffmlg==
|
||||
|
||||
expo-splash-screen@~0.29.16:
|
||||
version "0.29.16"
|
||||
resolved "https://registry.yarnpkg.com/expo-splash-screen/-/expo-splash-screen-0.29.16.tgz#80b49af2605213e661a40022d724caa1ea48ccb3"
|
||||
integrity sha512-1WnExDA23hEJhz+djUthVUWxUvVtDT9sqRrpCgU4srG2OfBN0NryJ+Fbnoc1V2xw2uYc4Ij3ru0nH9a1TNvW9w==
|
||||
expo-splash-screen@~0.29.18:
|
||||
version "0.29.18"
|
||||
resolved "https://registry.yarnpkg.com/expo-splash-screen/-/expo-splash-screen-0.29.18.tgz#96ccce3d5a03389a9061743903b0a77c22a16796"
|
||||
integrity sha512-bTBY+LF6YtYen2j60yGNh2SX/tG4UXZAyBCMMriOSiZZ7LSCs3ARyEufaSiWk+ckWShTeMqItOnaAN/CAF8MJA==
|
||||
dependencies:
|
||||
"@expo/prebuild-config" "^8.0.22"
|
||||
"@expo/prebuild-config" "^8.0.23"
|
||||
|
||||
expo-status-bar@~2.0.0:
|
||||
version "2.0.0"
|
||||
@@ -10661,26 +10666,26 @@ expo-web-browser@~14.0.1:
|
||||
resolved "https://registry.yarnpkg.com/expo-web-browser/-/expo-web-browser-14.0.1.tgz#97f3f141b0897364bc8364d90d6e29df0beec8aa"
|
||||
integrity sha512-QM9F3ie+UyIOoBvqFmT6CZojb1vMc2H+7ZlMT5dEu1PL2jtYyOeK2hLfbt/EMt7CBm/w+P29H9W9Y9gdebOkuQ==
|
||||
|
||||
expo@~52.0.17:
|
||||
version "52.0.17"
|
||||
resolved "https://registry.yarnpkg.com/expo/-/expo-52.0.17.tgz#8a3edc20dabdb69a47f7b4b92e1bb96284044c14"
|
||||
integrity sha512-f0WBD2T6p9r/a8v8MqkoWQq7TmbbAgPUg2zZtOp+kBrSCb3obHeNAsPDAUFzh+jEgug2qDVVkauBJa6ACe9AMg==
|
||||
expo@~52.0.19:
|
||||
version "52.0.19"
|
||||
resolved "https://registry.yarnpkg.com/expo/-/expo-52.0.19.tgz#1b881c96ea595da0c5f3c13f578bc368c5261f4a"
|
||||
integrity sha512-wOb/wbiQa0xqQRhgVBuOhLRus05TSw6fgThVMrPQgdLo24EPuT/ZAiRVcVRdjrEbwOqCDumgQCB7636B9J+jKg==
|
||||
dependencies:
|
||||
"@babel/runtime" "^7.20.0"
|
||||
"@expo/cli" "0.22.3"
|
||||
"@expo/cli" "0.22.6"
|
||||
"@expo/config" "~10.0.6"
|
||||
"@expo/config-plugins" "~9.0.12"
|
||||
"@expo/fingerprint" "0.11.3"
|
||||
"@expo/metro-config" "0.19.6"
|
||||
"@expo/fingerprint" "0.11.4"
|
||||
"@expo/metro-config" "0.19.8"
|
||||
"@expo/vector-icons" "^14.0.0"
|
||||
babel-preset-expo "~12.0.3"
|
||||
babel-preset-expo "~12.0.4"
|
||||
expo-asset "~11.0.1"
|
||||
expo-constants "~17.0.3"
|
||||
expo-file-system "~18.0.4"
|
||||
expo-file-system "~18.0.6"
|
||||
expo-font "~13.0.1"
|
||||
expo-keep-awake "~14.0.1"
|
||||
expo-modules-autolinking "2.0.3"
|
||||
expo-modules-core "2.1.1"
|
||||
expo-modules-autolinking "2.0.4"
|
||||
expo-modules-core "2.1.2"
|
||||
fbemitter "^3.0.0"
|
||||
web-streams-polyfill "^3.3.2"
|
||||
whatwg-url-without-unicode "8.0.0-3"
|
||||
|
||||
Reference in New Issue
Block a user